Skip to Content
Numinance
Guides

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.

View as .mdUpdated
Under development

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.

The harness owns intelligence. Numinance owns authority. An adapter translates between the two: it drives one native coding CLI and reports what happened as a canonical stream, while the host decides what the run may do.

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?

Illustrative · simulated
Executor
Tool call
Numinance frame · identical for every executorauthority
  • Policy decision
    evaluated outside the model before every privileged action
  • Grant
    capabilities, allowed and denied paths, budget
  • Credential broker
    hands out handles, never key material
  • Egress proxy
    only the allowed destinations
  • Protected paths
    policy, CI definitions, IAM, workflow files
  • Review and release identity
    no self-approval; only the release pipeline deploys
Harness layer · swapsAdapter: designed
Adapter supervising a pinned Claude Code CLI

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.

Capability manifest
EXECUTOR_CAPABILITY_FILESYSTEM_READEXECUTOR_CAPABILITY_FILESYSTEM_WRITEEXECUTOR_CAPABILITY_SHELLEXECUTOR_CAPABILITY_SUBAGENTSEXECUTOR_CAPABILITY_MCPEXECUTOR_CAPABILITY_STRUCTURED_PLANEXECUTOR_CAPABILITY_APPROVAL_CALLBACKS

Example manifest: public specFrom the published example ExecutorManifest claude-cli-bedrock.

model: untrusted proposer. Gains nothing beyond the grant.

Frame unchanged: 6 of 6. Swapped: harness layer, capability manifest, model route.

Conceptual telemetry · bound to the canonical event stream
tool call: write .github/workflows/release.yml (a protected path)
  1. TOOL_INTENTharness claim
  2. POLICY_DECISIONhost-recorded
  3. TOOL_DENIEDhost-recorded
  4. TOOL_RESULTharness claim
Step 1 of 4 · TOOL_INTENT

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.

The harness owns intelligence. Numinance owns authority. Even a compromised model gains nothing beyond its grant: each attempt is decided outside it and recorded.
■ fixed Numinance frame◇ swappable harness layer● acting at this stepTelemetry is conceptual, bound to the canonical event stream
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.

proto/numinance/ports/authorexecutor/v1/authorexecutor.proto (excerpt)
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 */ }
}
  • Capabilities returns the ExecutorCapability values the adapter claims. The host calls it just before Execute and keeps the answer for that run only. The router reads the ExecutorManifest pinned in the lockfile, never this answer, and an answer that names a capability the pinned manifest does not declare is a provider fault.
  • Execute runs the harness for one run of one work unit and streams ExecutionEvent messages. It is MEDIUM because the harness writes only inside the sandbox; the decision for a dispatch takes the higher of MEDIUM and the risk of every granted credential intent and egress destination.
  • Plan runs the harness read-only, in state Classified, before any plan exists. It streams the same events in PlanResponse. An adapter that serves it claims STRUCTURED_PLAN.

The request: what the host gives the adapter

ChangeExecutionRequest is everything a run is given. Nothing in it is advisory.

Field groupFieldsNotes
Identitychange_id, work_unit_id, run_id, grant_idPrefix plus 32 lowercase hex. One Execute per run_id
Referencestask_ref, plan_ref, context_bundle_ref, test_requirements_refThe provider may resolve only these four through HostService.ResolveReference
Host-only referencesworktree_ref, network_policy_refMarked host_only; never resolvable by the provider
Routingexecutor_id, model_idThe ExecutorManifest name and one of its models, as the router decided
Authorityallowed_paths, denied_paths, allowed_capabilities, credential_capabilities, protected_resourcesOnly the paths the run may write; the protected set is the harness's copy and the sandbox masks it whatever the request says
Boundsbudget (tokens, tool_calls, files_changed, lines_changed, network_destinations), deadlineThe deadline is the hard bound
Digestsrepository_digest, environment_digest, policy_digest, profile_digestMust 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.

EventWhat the adapter reports
EXECUTION_STARTEDFirst event of every stream
PLAN_ACCEPTEDThe harness took the plan of plan_ref as its own
TOOL_INTENTA tool call the harness is about to make
TOOL_ALLOWED, TOOL_DENIEDAn echo of the broker's decision; it authorises nothing
TOOL_RESULTThe result of a tool call
FILE_CHANGEDA worktree file created, changed or removed
COMMAND_EXECUTED, NETWORK_REQUESTA command, or a request made through the egress proxy
CREDENTIAL_REQUESTA handle the harness asked the broker for
TEST_RESULT, CHECKPOINT, BUDGET_EVENTTest outcome, resumable point, budget use
EXECUTION_COMPLETED, EXECUTION_FAILEDTerminal

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:

  1. Translate the grant. Turn allowed_capabilities, allowed_paths and 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.
  2. Start the CLI inside the sandbox with the pinned model, using the CLI's streaming protocol.
  3. Map its events. Each tool call becomes TOOL_INTENT, then the broker's answer arrives as TOOL_ALLOWED or TOOL_DENIED, then TOOL_RESULT. Commands, file changes and test runs map to their own types. Anything the adapter cannot place is dropped, not guessed.
  4. 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.
  5. 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_CHANGED is a claim to cross-check.
  • Hold or request credential material. credential_capabilities are 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.Log or 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_capabilities and network_policy_ref.
  • Resolve worktree_ref or network_policy_ref, widen allowed_paths, or retry past max_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:

schemas/examples/ExecutorManifest/ (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_MEDIUM

This 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:

DimensionQuestion
ComputeWhere does the harness process run?
InferenceWhere does the model run, and in which regions may it run?
EgressWhat prompt and response data leaves the account, and to whom?
RetentionHow 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

FailureResult
Stream has a gap, a repeated sequence, or an event after the terminal oneHost rejects the stream and records the run failed
Event subject outside the grammar or the grantRejected; a canary or handle in a subject is an abnormal action and starts the incident workflow
Capabilities names a capability the manifest does not declareExecute is not dispatched; the host records FAILED_PRECONDITION
Run exceeds files_changed, lines_changed or destination countsHost stops the run at its own count
Reviewer-agent worktree differs from its startNo candidate is built; the worktree is discarded
Residency class below the floorThe Change is refused at start

Next steps

On this page