← All extensions
Policy Export banner

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/schemaslogic_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.