Policy Export
ivyx✓
Export the workspace policy as something an external agent gateway can enforce
Policy Export
You wrote the agent here, and it ran under the rules you set. This turns those rules into the files a production gateway needs, so the same policy governs the agent after it leaves your machine.
What you can do
- Choose which gateway you are exporting for
- Preview the policy before anything is written
- Read exactly which rules could not cross, and why
- Write the export into your workspace
- Write the hook service that enforces it, next to the export
Requirements
An external agent gateway to enforce the result — Obot or Arcade today. The export itself needs nothing installed.
Getting started
Open Policy Export from the activity bar, choose the gateway you are exporting for, and press Preview. The panel then names every rule that had to be tightened, refused or left out. When the notes read the way you expect, press Write and confirm the files.
Nothing is written while anything is refused. A partial policy is worse than none: it leaves you believing the rest is enforced.
Both gateways decide by calling a hook, so something has to answer it. The header
button — or Policy Export: Write Hook Service — puts that service in
.punica/policy-export/hook, next to the bundle it evaluates, and shows you every
file first. Its README carries the one command that starts it.
Status
The export, its adapters, the panel and the hook service that enforces the result all work, and the service ships in the package rather than only in this repository.
The hook is deployed next to your gateway, not inside the editor. It evaluates the exported policy with the editor's own matcher, so the same rules produce the same verdicts in both places; a round-trip test asserts exactly that.
Obot is measured on the wire. A running Obot blocked the call this policy
denies, with the rule's reason reaching the caller, using the generated filter
unedited. e2e/ reproduces that run in three commands, and it is worth
re-running after an Obot upgrade — Obot accepted an earlier version of the
generated filter and then enforced nothing.
Arcade is measured on the wire too. Arcade's hosted engine denied
Math.Divide{b: 0} with the rule's own reason reaching the caller, performed the
same tool with b: 2, and dropped an unconditionally denied tool out of the
user's listing — from the generated artefacts, registered by
hook/register-arcade.mjs. The adapter needed no change for the engine's
requests, because it was written against ArcadeAI/schemas —
logic_extensions/http/1.0/schema.yaml — rather than against the prose. What the
run found was six things around the requests, including that a hidden tool is
refused without its reason and that Arcade sends every tool to the hook.
docs/SCHEMA_MAPPING.md §4 has the table and §4b has the registration API.
The rule behind every refusal
An exported policy is never more permissive than the local one. Where a construct cannot cross, the export tightens it, or refuses and names the rule, the field and the target that could not carry it.
Two constructs cannot cross today:
- An approval gate. Both gateways decide synchronously and neither can ask a
human, so a rule that means "ask me first" has nowhere to go. Refused by
default; pass
requireAs: "deny"to export it as a refusal instead. - An allow rule that tests a secret field. Here the matcher sees a redacted value, so the rule never fires and the call still needs approval. At a gateway the real value is visible and the same rule would let the call through with nobody asked.
Targets
Pass the base URL of your deployed hook as webhookUrl and the export composes
the rest of the address for you.
| Target | Files |
|---|---|
bundle |
ivyx-policy-bundle.json — the rules, plus the semantics an evaluator must implement to agree with this editor |
obot |
the bundle, an MCPWebhookValidation resource, and access rules when you supply subjects |
arcade |
the bundle, arcade/plugin.json to POST at /v1/admin/plugins, and a document explaining it |
Neither gateway can express a condition on a tool's arguments in configuration — both call a hook and let it decide. So every target ships the same bundle and adds a few lines telling the platform which calls to send to it. A fourth target is a mapping, not a rewrite.
Arcade's three hooks are all live webhooks, including the one that decides which
tools a user may see, so there is nothing file-shaped to generate for it: the
service answers POST /access against the catalogue Arcade is about to show.
Registering them is one request, and hook/register-arcade.mjs sends it:
ARCADE_API_KEY=arc_… ARCADE_HOOK_TOKEN=… \
node hook/register-arcade.mjs .punica/policy-export/arcade/plugin.json
Two things to know before you do. Arcade sends every tool in the project to
the hook, so IVYX_UNKNOWN_TOOL decides for your whole gateway rather than for
the exported tools. And the hooks are registered fail_closed, so while they are
active every tool call in that project depends on the hook being reachable.
Capabilities
For wiring an agent or another extension:
| Capability | Input | Answer |
|---|---|---|
policyExport.targets |
— | the emittable targets and the files each writes |
policyExport.preview |
target, optional requireAs, webhookUrl, subjects |
the artifacts, the notes, a summary, and the audit run id |
policyExport.write |
the same, plus optional dir |
the paths written, or the refusals that stopped it |
Two commands, for a person rather than an agent: Policy Export: Open and Policy Export: Write Hook Service.
const preview = await punica.runtime.capabilities.gateway.call(
"policyExport.preview",
{ target: "obot", webhookUrl: "https://policy.example/hook" },
ctx
);
Preview and write are separate on purpose: reading what will be enforced before enforcing it is the whole promise in miniature.
Every governed call one export makes shares a single run id, so the whole export reads back out of the audit trail as one run rather than as scattered writes.
What it is not
The authoring side of a gateway, never a gateway. Nothing here sits in a request path, and it calls no vendor API — an adapter names an endpoint, you register it.