Extend a coding harness
How an adapter maps a native coding CLI onto the authorexecutor port, what it must report, what it must never do, and why residency is derived from live configuration rather than a cloud's name.
The authorexecutor port, the ExecutorManifest kind and the stream rules are in the public spec. No harness adapter ships in the open tree: the fake, claude-cli and codex-cli adapters are designed, and the adapter conformance kit is a written case list plus offline plan-stream checks, not a runnable harness.
When to use this
Read this if you want to put another coding agent, such as a vendor CLI or an open-weight loop, behind Numinance, or if you want to know precisely what an adapter is allowed to claim. The adapter is the least trusted piece in the architecture: it runs a model-driven process and reports on it. Everything it says is a claim. See Harnesses vs runtime for the product-level split.
Prerequisites
- The plugin lifecycle and the custom plugin walk-through. An adapter is a plugin whose port is
authorexecutor. - Familiarity with Agent harnesses.
- You cannot run an adapter end to end without the proprietary engine, which supplies the sandbox, the egress proxy and the broker. What you can do in the open tree is read the contract, validate the shapes, and run the offline stream rules.
The mechanism
Harness adapter explorer
When the executor changes, what stays fixed?
- Policy decisionevaluated outside the model before every privileged action
- Grantcapabilities, allowed and denied paths, budget
- Credential brokerhands out handles, never key material
- Egress proxyonly the allowed destinations
- Protected pathspolicy, CI definitions, IAM, workflow files
- Review and release identityno self-approval; only the release pipeline deploys
A Go adapter drives the harness over its own JSON-lines protocol inside the work-unit sandbox; the harness keeps its own planning and tools.
Example manifest: public specFrom the published example ExecutorManifest claude-cli-bedrock.
Frame unchanged: 6 of 6. Swapped: harness layer, capability manifest, model route.
- TOOL_INTENTharness claim
- POLICY_DECISIONhost-recorded
- TOOL_DENIEDhost-recorded
- TOOL_RESULTharness claim
The harness reports it is about to write .github/workflows/release.yml (a protected path). This is a claim by the provider; it authorizes nothing.
Text description
The authorexecutor port drives one coding harness for one run of one work unit inside a sandbox. Four executors are shown: fake, claude-cli, codex-cli and a read-only reviewer on a different model vendor. All adapters are designed and under development; the spec publishes the port, the event stream, the conformance case list and one example ExecutorManifest (claude-cli-bedrock), not the adapters.
Fixed whichever executor runs: the policy decision, the grant, the credential broker, the egress proxy, the protected paths, and the review and release identities. What swaps is the harness layer and the capability manifest the router pins. Nothing a request says is advisory, and nothing a provider reports is authority.
The sequence is conceptual. The harness reports TOOL_INTENT; the host takes a policy decision (a host record, not a provider event); TOOL_ALLOWED or TOOL_DENIED echoes the broker; TOOL_RESULT reports the outcome. In the denied example a write to a protected workflow file is refused for every write-capable executor. For the reviewer, any write is refused because its run has no write capability and no allowed paths.
The harness owns intelligence. Numinance owns authority. A compromised model can only attempt what its grant allows, and every attempt is decided and recorded outside it. Illustrative and simulated; nothing is connected to a running engine.
The execution context is the only caller. It dispatches one Execute per run of one work unit, inside a disposable work-unit sandbox, as an author-agent run. A reviewer-agent run goes through the same port but is read-only, and the host refuses every other actor kind.
The port, exactly
The service has three RPCs.
service AuthorExecutorService {
rpc Capabilities(CapabilitiesRequest) returns (CapabilitiesResponse) { /* RISK_CLASS_READ */ }
rpc Execute(ExecuteRequest) returns (stream ExecuteResponse) { /* RISK_CLASS_MEDIUM, EXEC_MODE_OPERATION */ }
rpc Plan(PlanRequest) returns (stream PlanResponse) { /* RISK_CLASS_READ, EXEC_MODE_OPERATION */ }
}Capabilitiesreturns theExecutorCapabilityvalues the adapter claims. The host calls it just beforeExecuteand keeps the answer for that run only. The router reads theExecutorManifestpinned in the lockfile, never this answer, and an answer that names a capability the pinned manifest does not declare is a provider fault.Executeruns the harness for one run of one work unit and streamsExecutionEventmessages. It isMEDIUMbecause the harness writes only inside the sandbox; the decision for a dispatch takes the higher ofMEDIUMand the risk of every granted credential intent and egress destination.Planruns the harness read-only, in stateClassified, before any plan exists. It streams the same events inPlanResponse. An adapter that serves it claimsSTRUCTURED_PLAN.
The request: what the host gives the adapter
ChangeExecutionRequest is everything a run is given. Nothing in it is advisory.
| Field group | Fields | Notes |
|---|---|---|
| Identity | change_id, work_unit_id, run_id, grant_id | Prefix plus 32 lowercase hex. One Execute per run_id |
| References | task_ref, plan_ref, context_bundle_ref, test_requirements_ref | The provider may resolve only these four through HostService.ResolveReference |
| Host-only references | worktree_ref, network_policy_ref | Marked host_only; never resolvable by the provider |
| Routing | executor_id, model_id | The ExecutorManifest name and one of its models, as the router decided |
| Authority | allowed_paths, denied_paths, allowed_capabilities, credential_capabilities, protected_resources | Only the paths the run may write; the protected set is the harness's copy and the sandbox masks it whatever the request says |
| Bounds | budget (tokens, tool_calls, files_changed, lines_changed, network_destinations), deadline | The deadline is the hard bound |
| Digests | repository_digest, environment_digest, policy_digest, profile_digest | Must agree with the ActionContext or the host refuses to dispatch |
Payloads carry references and digests, never file contents, prompt text or credential material. The adapter fetches the task or the plan by reference, and the host checks plan_ref.sha256 against ActionContext.plan_digest.
The response: the canonical event stream
An adapter maps whatever its CLI emits onto these events. A stream holds 1 to 1023 events; the first is EXECUTION_STARTED at sequence 1; sequences are consecutive; the last, and only the last, is EXECUTION_COMPLETED or EXECUTION_FAILED; each message is at most 32 KiB.
| Event | What the adapter reports |
|---|---|
EXECUTION_STARTED | First event of every stream |
PLAN_ACCEPTED | The harness took the plan of plan_ref as its own |
TOOL_INTENT | A tool call the harness is about to make |
TOOL_ALLOWED, TOOL_DENIED | An echo of the broker's decision; it authorises nothing |
TOOL_RESULT | The result of a tool call |
FILE_CHANGED | A worktree file created, changed or removed |
COMMAND_EXECUTED, NETWORK_REQUEST | A command, or a request made through the egress proxy |
CREDENTIAL_REQUEST | A handle the harness asked the broker for |
TEST_RESULT, CHECKPOINT, BUDGET_EVENT | Test outcome, resumable point, budget use |
EXECUTION_COMPLETED, EXECUTION_FAILED | Terminal |
Events carry a subject only for specific types (a tool name, a path, a credential intent) and references that point at content such as a diff or a test report, never the content itself. The subject is untrusted: the host rejects a FILE_CHANGED path outside allowed_paths or absent from its own diff of the worktree.
On the plugin protocol each event rides one RUNNING Invoke message, and the terminal Invoke message is SUCCEEDED after EXECUTION_COMPLETED and FAILED after EXECUTION_FAILED. sdk/go/ports/authorexecutor provides NewExecuteRule and NewPlanRule, which check exactly those stream rules offline, and ValidatePlanRequest for the planning request bounds.
The mapping, in prose
An adapter for a native CLI typically does five things, in order:
- Translate the grant. Turn
allowed_capabilities,allowed_pathsand the tool inventory into the CLI's own allowlist, configuration and instruction files. This is telemetry and convenience. The deny that counts is enforced by the broker, the egress proxy and the read-only filesystem, not by the CLI's settings. - Start the CLI inside the sandbox with the pinned model, using the CLI's streaming protocol.
- Map its events. Each tool call becomes
TOOL_INTENT, then the broker's answer arrives asTOOL_ALLOWEDorTOOL_DENIED, thenTOOL_RESULT. Commands, file changes and test runs map to their own types. Anything the adapter cannot place is dropped, not guessed. - Route permission prompts to the broker. The CLI asks to run a tool; the adapter forwards that to the host's permission path and returns the decision.
- Emit one terminal event and stop. Never emit events after it.
The designed adapters follow this shape: fake is an in-process deterministic executor that applies scripted diffs for lifecycle tests, claude-cli supervises the Claude Code CLI, and codex-cli supervises the Codex CLI. All three are designed. Their ids appear in specification examples (the ExecutorManifest example binds dev.numinance.official/claude-cli), but there is no adapter source in the open tree and nothing resolves those ids.
What an adapter must not do
- Treat anything in its own settings as the enforcement point. The sandbox, proxy and broker are configured from the host's resolved grant, never from the request, which is only the harness's copy.
- Derive the candidate from its own events. The candidate is the host's own diff of the worktree.
FILE_CHANGEDis a claim to cross-check. - Hold or request credential material.
credential_capabilitiesare intent names; the broker returns handles, never secrets. A canary secret is planted on the host in the live kit, and it must never appear in the sandbox, any event,HostService.Logor a diagnostic. - Reach the model endpoint on its own authority. Inference egress for the pinned model is configured by the host outside
allowed_capabilities,credential_capabilitiesandnetwork_policy_ref. - Resolve
worktree_refornetwork_policy_ref, widenallowed_paths, or retry pastmax_repair_attempts. Repair is a host-level, bounded loop. - Emit anything for a path under
protected_resources. - Approve, merge or promote anything. An author-agent never approves its own change.
The manifest the router reads
The adapter is selected through an ExecutorManifest pinned in the lockfile. The shipped example:
apiVersion: numinance.dev/v1
kind: ExecutorManifest
metadata: {name: claude-cli-bedrock, scope: SCOPE_ORG}
spec:
provider: dev.numinance.official/claude-cli
capabilities: [EXECUTOR_CAPABILITY_FILESYSTEM_READ, EXECUTOR_CAPABILITY_FILESYSTEM_WRITE, EXECUTOR_CAPABILITY_SHELL, EXECUTOR_CAPABILITY_SUBAGENTS,
EXECUTOR_CAPABILITY_MCP, EXECUTOR_CAPABILITY_STRUCTURED_PLAN, EXECUTOR_CAPABILITY_APPROVAL_CALLBACKS]
models:
- {id: eu.anthropic.claude-sonnet-v1:0, max_context: 200000}
cost_class: EXECUTOR_COST_CLASS_MEDIUMThis validates with numi validate --files as an example document. It declares capabilities and models; it declares no residency, because the residency field is deliberately held back for a later minor. The provider id here is an example shape, not an installable adapter.
Residency is derived, not declared
Where data goes has more than one dimension, and a cloud's name settles none of them:
| Dimension | Question |
|---|---|
| Compute | Where does the harness process run? |
| Inference | Where does the model run, and in which regions may it run? |
| Egress | What prompt and response data leaves the account, and to whom? |
| Retention | How long may the vendor keep prompts and responses, and may it train on them? |
A ResidencyPolicy sets floors (IN_ACCOUNT, PRIVATE_ENDPOINT, VENDOR_ZDR, ...) plus regions, cross_region, retention_max and training_use. The class that is compared with the floor is derived from the live runtime configuration at Change start, not copied from a static manifest. Until the manifest carries residency fields, a manifest that cannot be checked against a floor fails it.
"On AWS" and "through Bedrock" are therefore not IN_ACCOUNT. A model reached through a cloud provider can still run in another region, retain prompts, or be reached over a public path. The class is whatever the dimensions add up to, and a mismatch with the floor refuses the Change before work starts. An adapter must never write a residency claim into its own events as if it settled the question; ActionContext.residency is informational, host-derived, and never a grant.
Conformance
The authorexecutor kit is a written list of cases, not a runnable harness. CORE-001 to CORE-018 cover Capabilities, Execute and bounds; the live cases (egress to link-local and metadata addresses, credential intents outside the grant, deadline enforcement, a planted canary secret, inference endpoint overrides) need a real sandbox, proxy and broker. HOST-001 to HOST-014 describe what every host does with a request and with a provider's answer: a stream whose first event is not EXECUTION_STARTED is rejected, a TOOL_ALLOWED for a denied intent grants nothing, a second Execute with a used run_id is not dispatched, and a reviewer-agent run with write capability is refused.
What runs today, offline, is the stream half of PLAN-001/PLAN-002 and PLAN-003 through NewPlanRule and ValidatePlanRequest. Until the plugin host work lands, treat full adapter conformance as designed.
Failure modes and safeguards
| Failure | Result |
|---|---|
| Stream has a gap, a repeated sequence, or an event after the terminal one | Host rejects the stream and records the run failed |
Event subject outside the grammar or the grant | Rejected; a canary or handle in a subject is an abnormal action and starts the incident workflow |
Capabilities names a capability the manifest does not declare | Execute is not dispatched; the host records FAILED_PRECONDITION |
Run exceeds files_changed, lines_changed or destination counts | Host stops the run at its own count |
| Reviewer-agent worktree differs from its start | No candidate is built; the worktree is discarded |
| Residency class below the floor | The Change is refused at start |
Next steps
Verify a run
Check an evidence export offline with numi evidence verify, read the four ways it fails, gate CI on the right JSON fields, and pin your keys independently of the export.
Qualify autonomy
Set per-playbook qualification thresholds, build a replay EvalSet, and understand how a promotion is proposed from evidence, accepted by a human, and never applied automatically.