Plugin System

Extend the gateway with custom harnesses, tools, hooks, and compactors. One packaging model, four execution contracts, language-agnostic authoring.

Architecture

Plugins extend behavior without becoming the source of truth for chain state. Harnesses act through delegated API credentials, tools return results, hooks react asynchronously, and compactors produce derived chain seeds.

  • Package — Immutable versioned OCI artifact containing manifest, binaries, schemas, and assets. Distributed via container registries. Never contains tenant secrets.

  • Binding — Control-plane record that binds a package export to org or project scope. Supplies config, secret slots, rollout state, and policy overrides.

  • Invocation — One runtime execution of a bound export under a deadline and resource budget. Produces runtime events and a terminal outcome.

  • Delegated Credentials — Short-lived, scope-limited gateway API tokens injected into plugin invocations. Plugins call VAI APIs without broad ambient credentials.

Export Kinds

A single package can export multiple items across these four kinds.

  • harness — Orchestration programs that coordinate multi-step AI workflows. Act through delegated VAI API credentials to manage sessions, chains, and runs. Default class: isolated . Examples: composite bug triage, multi-agent coding pipelines, research and synthesis workflows.

  • tool — Result-producing functions invoked during model execution. Return structured output without directly mutating chain history. Default class: isolated (third-party) . Examples: Jira search, database queries, web scraping, code execution.

  • hook — Async event handlers triggered by semantic events like run completion or chain compaction. Idempotent and retried by delivery ID. Default class: worker . Examples: Slack notifications, metrics collection, audit logging, webhook forwarding.

  • compactor — Transform plugins that produce derived chain seeds for context compaction. Never rewrite existing chains, only output new seed data. Default class: worker . Examples: sliding window summarization, task-aware compaction, code-context preservation.

Execution Classes

inlineLowestVery restrictedTrusted first-party or tightly reviewed code
workerModerateBackground on gateway-workersDefault for hooks and compactors
isolatedHigherIsolated execution planeThird-party tools and hosted harnesses

Package Manifest

Every plugin package requires a vai-plugin.toml manifest at the root of the OCI artifact. The manifest declares package metadata, runtime configuration, and one or more exports.

shell
1/
2  vai-plugin.toml
3  bin/
4  schemas/
5  assets/
6  README.md

Manifest Example

A complete manifest with all four export kinds. Each export declares its execution class, permissions, timeout, and kind-specific configuration.

toml
1api_version = "vai.plugin.v1"
2package_id = "acme/devkit"
3version = "0.1.0"
4name = "Acme Dev Kit"
5description = "Harnesses, tools, hooks, and compaction strategies"
6license = "Apache-2.0"
7source = "oci://ghcr.io/acme/devkit:0.1.0"
8
9[runtime]
10protocol = "vai.rpc.v1"
11transport = "stdio"
12entrypoint = ["./bin/plugin"]
13os = ["linux", "darwin"]
14arch = ["amd64", "arm64"]
15
16# --- Harness export ---
17[[exports]]
18id = "triage"
19kind = "harness"
20display_name = "Triage Harness"
21description = "Composite bug triage harness"
22execution_class = "isolated"
23default_timeout_ms = 1800000
24max_concurrency = 4
25config_schema = "schemas/triage_config.json"
26
27[exports.permissions]
28network = "egress"
29filesystem = "scratch"
30gateway_api_scopes = [
31  "sessions:read", "chains:read",
32  "chains:write", "runs:write",
33  "compactions:write"
34]
35secret_slots = ["anthropic_key", "openai_key"]
36
37[exports.harness]
38mode = "composite"
39fidelity = "checkpoint"
40supports = ["continue", "branch_session", "compact", "tool"]
41
42# --- Tool export ---
43[[exports]]
44id = "jira_search"
45kind = "tool"
46display_name = "Jira Search"
47execution_class = "isolated"
48default_timeout_ms = 20000
49config_schema = "schemas/jira_search_config.json"
50
51[exports.permissions]
52network = "egress"
53filesystem = "none"
54gateway_api_scopes = []
55secret_slots = ["jira_token"]
56
57[exports.tool]
58effect_class = "read_only"
59streaming = false
60input_schema = "schemas/jira_search_input.json"
61output_schema = "schemas/jira_search_output.json"
62
63# --- Hook export ---
64[[exports]]
65id = "slack_notify"
66kind = "hook"
67display_name = "Slack Notifier"
68execution_class = "worker"
69default_timeout_ms = 15000
70config_schema = "schemas/slack_notify_config.json"
71
72[exports.permissions]
73network = "egress"
74filesystem = "none"
75gateway_api_scopes = ["runs:read"]
76secret_slots = ["slack_webhook"]
77
78[exports.hook]
79subscribes_to = ["run.completed", "chain.compacted"]
80
81# --- Compactor export ---
82[[exports]]
83id = "coder_window"
84kind = "compactor"
85display_name = "Coder Window"
86execution_class = "worker"
87default_timeout_ms = 120000
88config_schema = "schemas/coder_window_config.json"
89
90[exports.permissions]
91network = "none"
92filesystem = "scratch"
93gateway_api_scopes = []
94secret_slots = []
95
96[exports.compactor]
97target_context = "coding"
98supports_pinned_items = true

Permissions Model

The manifest declares capability needs. The host decides the final granted permissions. The effective permission set may be narrower than what the package requests.

networknone \| egressNetwork access policy for the invocation
filesystemnone \| scratch \| assets_roFilesystem access level
gateway_api_scopesstring[]Delegated VAI API scopes for this export
secret_slotsstring[]Operator-bindable named secrets

Runtime Protocol

The vai.rpc.v1 protocol uses framed JSON over stdio, language-agnostic, easy to debug locally, and independent of language-specific ABI issues. Framing follows a content-length envelope similar to LSP.

shell
1Content-Length: <n>\r\n
2\r\n
3<json bytes>

Protocol Frames

Every frame contains protocol and type fields. The lifecycle follows: hello -> request/event/response -> shutdown.

Hello

Exchanged at startup. The host verifies that the plugin's reported identity matches the installed artifact.

Host -> Plugin:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "hello",
4  "role": "host",
5  "host": {
6    "name": "vai-runner",
7    "version": "0.1.0"
8  }
9}

Plugin -> Host:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "hello",
4  "role": "plugin",
5  "plugin": {
6    "package_id": "acme/devkit",
7    "version": "0.1.0"
8  }
9}

Request

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "request",
4  "id": "req_123",
5  "op": "tool.invoke",
6  "body": {}
7}

Event

Zero or more events may be emitted for a given request before its terminal response.

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "event",
4  "request_id": "req_123",
5  "event": {
6    "type": "progress",
7    "message": "working"
8  }
9}

Response

ok=false is for transport or protocol failure, not for plugin business outcomes. Semantic failures use ok=true with a structured status in the body.

Success:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "response",
4  "id": "req_123",
5  "ok": true,
6  "body": {}
7}

Protocol Error:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "response",
4  "id": "req_123",
5  "ok": false,
6  "error": {
7    "code": "protocol.invalid_frame",
8    "message": "missing body"
9  }
10}

Cancel

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "cancel",
4  "request_id": "req_123",
5  "reason": "deadline_exceeded"
6}

Shutdown

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "shutdown"
4}

Invocation Context

Every request body includes a context object with identity, scope, tracing, deadline, scratch directory, and delegated gateway credentials.

json
1{
2  "invocation_id": "inv_123",
3  "trace_id": "tr_123",
4  "org_id": "org_123",
5  "project_id": "proj_123",
6  "session_id": "sess_123",
7  "chain_id": "ch_123",
8  "run_id": "run_123",
9  "deadline_at": "2026-03-12T20:00:00Z",
10  "scratch_dir": "/sandbox/scratch/inv_123",
11  "gateway": {
12    "base_url": "https://api.rhone.dev",
13    "delegation_token": "vai_dlg_...",
14    "scopes": ["chains:read", "runs:write"]
15  }
16}

Execution Contracts

Tool Contract

Tools are result-producing functions. They do not mutate chain history directly; the host records the execution and inserts the canonical tool_result.

Request:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "request",
4  "id": "req_123",
5  "op": "tool.invoke",
6  "body": {
7    "export_id": "jira_search",
8    "context": { "..." : "..." },
9    "tool_execution_id": "toolx_123",
10    "input": {
11      "query": "billing outage",
12      "project": "ENG"
13    }
14  }
15}

Response:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "response",
4  "id": "req_123",
5  "ok": true,
6  "body": {
7    "status": "succeeded",
8    "result": {
9      "content": [
10        {"type": "text", "text": "Found 3 issues."}
11      ],
12      "structured_output": {
13        "issues": [{"key": "ENG-123"}]
14      },
15      "is_error": false,
16      "retryable": false
17    }
18  }
19}

Effect classes: read_only, idempotent_write, non_idempotent_write, unknown

Hook Contract

Hooks are async-first event handlers. They are delivered from durable semantic events, retried by delivery_id, and should be idempotent.

Request:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "request",
4  "id": "req_123",
5  "op": "hook.handle",
6  "body": {
7    "export_id": "slack_notify",
8    "context": { "..." : "..." },
9    "delivery_id": "del_123",
10    "attempt": 2,
11    "event": {
12      "type": "run.completed",
13      "occurred_at": "2026-03-12T19:00:00Z",
14      "subject": {"kind": "run", "id": "run_123"},
15      "summary": {
16        "task": "Fix auth redirect bug",
17        "output": "Patched route handler"
18      }
19    }
20  }
21}

Response:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "response",
4  "id": "req_123",
5  "ok": true,
6  "body": {
7    "status": "ack"
8  }
9}

Compactor Contract

Compactors are transform plugins. They produce derived chain seeds, never rewrite existing chains. Large projections can be delivered inline or via artifact references in the scratch directory.

Request:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "request",
4  "id": "req_123",
5  "op": "compactor.compact",
6  "body": {
7    "export_id": "coder_window",
8    "context": { "..." : "..." },
9    "compaction_id": "cmp_123",
10    "source": {
11      "chain_id": "ch_123",
12      "projection": {
13        "transport": "inline",
14        "blocks": [],
15        "pinned_items": [],
16        "token_estimate": 182000
17      }
18    },
19    "target": {
20      "max_input_tokens": 32000,
21      "target_model": "gpt-5.4"
22    },
23    "policy": {
24      "retain_system": true,
25      "retain_open_tasks": true
26    }
27  }
28}

Response:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "response",
4  "id": "req_123",
5  "ok": true,
6  "body": {
7    "status": "succeeded",
8    "result": {
9      "seed_blocks": [
10        {
11          "kind": "system",
12          "content": [{"type": "text", "text": "You are a coding agent."}]
13        },
14        {
15          "kind": "input",
16          "content": [{"type": "text", "text": "Summary of prior work."}]
17        }
18      ],
19      "summary": {
20        "text": "Auth redirect fix in progress. Tests pending."
21      },
22      "diagnostics": {
23        "input_tokens_estimate": 182000,
24        "output_tokens_estimate": 5400
25      }
26    }
27  }
28}

Harness Contract

Harnesses are orchestration programs supervised by the plugin runner. They act through canonical VAI APIs using delegated credentials, with no hidden privileged mutation paths.

Request:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "request",
4  "id": "req_123",
5  "op": "harness.run",
6  "body": {
7    "export_id": "triage",
8    "context": { "..." : "..." },
9    "mode": "start",
10    "target": {
11      "session_id": "sess_123",
12      "chain_id": "ch_123",
13      "execution_id": "exec_123"
14    },
15    "trigger": {
16      "type": "user_request",
17      "input": [
18        {"type": "text", "text": "Investigate the bug and fix it"}
19      ]
20    },
21    "policy": {
22      "max_depth": 4,
23      "max_child_runs": 12,
24      "allow_compaction": true
25    }
26  }
27}

Response:

json
1{
2  "protocol": "vai.rpc.v1",
3  "type": "response",
4  "id": "req_123",
5  "ok": true,
6  "body": {
7    "status": "succeeded",
8    "result": {
9      "checkpoint": {
10        "kind": "checkpoint",
11        "data": {"cursor": 12}
12      },
13      "summary": {
14        "text": "Gathered context, planned, and executed fix."
15      },
16      "artifacts": []
17    }
18  }
19}

Harness fidelity classes: lossless, checkpoint, prompt_replay, summary_reseed

Binding Record

Bindings apply org- and project-specific configuration without modifying the immutable package artifact. They own config values, secret slot mappings, rollout state, and enable/disable state.

json
1{
2  "binding_id": "bind_123",
3  "package_id": "acme/devkit",
4  "package_version": "0.1.0",
5  "export_id": "jira_search",
6  "scope": {
7    "org_id": "org_123",
8    "project_id": "proj_123"
9  },
10  "config": {
11    "base_url": "https://acme.atlassian.net",
12    "max_results": 10
13  },
14  "secrets": {
15    "jira_token": "secret://org_123/jira_token"
16  },
17  "status": "active",
18  "rollout": {
19    "traffic_percent": 100
20  }
21}

Control Plane APIs

Marketplace

The platform plugin page separates marketplace discovery from installedinventory. Marketplace entries are curated first-party records that describeinstallable packages before a project binds them. Installed packages, bindings,and runtime inventory continue to come from the canonical plugin control plane.

Initial marketplace entries:

  • OpenAI Codex Hosted Harness — harness export for Codex App Server / SDK orchestration with delegated Rhone gateway credentials. Contract-ready until the native adapter binary is packaged.

  • OpenCode Hosted Harness — harness export for OpenCode server workflows through @opencode-ai/sdk . Contract-ready until the native adapter binary is packaged.

  • Thread Weave Compactor — worker compactor export that keeps recent coding context directly and emits lineage-linked seed blocks for older task history. This entry can be installed into a project as an active thread_weave compactor binding.

Marketplace install actions are intentionally separate from runtime invocation:installing creates or updates package and binding state, while execution stillflows through ordinary harness, tool, hook, and compactor contracts.

Marketplace cards also expose publisher, review, signature, source, OCI, andinstall-policy posture. A reviewed available package can expose an installaction. A contract-ready harness shows its adapter contract and sourcereferences, but remains non-installable until Rhone publishes a pinned signedruntime artifact and dependency lock.

Signed package releases use marketplace.signature.json. Release automation cangenerate it with vai-plugin-sign, and production imports should require atrusted Ed25519 signature before marking a package available.

Package Management

  • POST /v1/plugin-packages — Upload a plugin package

  • GET /v1/plugin-packages — List installed packages

  • GET /v1/plugin-packages/{id} — Get package details

Bindings

  • POST /v1/plugin-bindings — Create a binding

  • GET /v1/plugin-bindings — List bindings

  • PATCH /v1/plugin-bindings/{id} — Update a binding

  • POST /v1/plugin-bindings/{id}:validate — Validate a binding

Export Discovery

  • GET /v1/plugin-harnesses — List available harness exports

  • GET /v1/plugin-tools — List available tool exports

  • GET /v1/plugin-hooks — List available hook exports

  • GET /v1/plugin-compactors — List available compactor exports

Security Model

  • Secret Slots — Plugins declare named secret slots in the manifest. The binding record maps slots to stored secrets. Raw secrets are never in package artifacts, chain records, or run records.

  • Host-Granted Permissions — The host computes effective granted permissions per invocation. The granted set may be narrower than what the package requests.

  • Network Policy — Egress policy enforced per invocation. Inline and isolated execution support domain allowlists.

  • Filesystem Policy — scratch means write access inside the invocation scratch dir only. assets_ro means read-only mounted assets. No plugin receives arbitrary host filesystem access.

Terminal Statuses

All completed invocations use one of these terminal statuses in the response body.

  • succeeded

  • failed

  • cancelled

  • retryable_failure

Observability

Plugin invocations are first-class operational events. Every invocation records:

  • Invocation ID

  • Export kind and ID

  • Binding ID

  • Package ID and version

  • Execution class

  • Start and end time

  • Terminal status

  • Retry count

  • Org and project scope

  • Related session, chain, and run

  • Trace ID

  • Execution ID