Skip to the content.

REQUIRE_WITNESS as a durable tool-call continuation

Status: design note; current ABI has the verdict, but the durable held-call queue described here is not yet implemented end to end.

Short answer

Yes: one useful meaning of REQUIRE_WITNESS is that the agent’s proposed tool call becomes a durable suspended continuation. The kernel records the exact call and the condition that must be witnessed, puts a witness request on a queue, and does not execute the tool. Another process, agent, human, or service reads the request and returns a bound attestation. The kernel verifies that attestation, rechecks policy and freshness, executes the call at most once, admits or quarantines the result, and resumes the waiting agent from its checkpoint.

What pauses is the causal tool-call continuation, not necessarily an OS process or an in-memory model context. A synchronous adapter may block a request for a short approval, but the scalable form parks the turn, releases the model/GPU/HTTP worker, and later rehydrates it. This is closer to await durable_future than to keeping one context window resident in a queue.

The existing pieces make this direction natural but do not yet constitute that service:

That last bullet is the implementation gap. Until it lands, REQUIRE_WITNESS means “stop and route outward,” not “the kernel already owns a complete wait queue.”

The object that waits

Do not queue a mutable chat transcript or a live context window. Queue a small, immutable held-call record:

HeldCall {
  held_id                 // stable public handle
  session_id, turn_id, call_id
  tool, canonical_args_digest
  policy_version, plugin_set_digest
  principal, capability_snapshot
  witness_contract       // what fact/approval/read-back is required
  continuation_ref       // durable checkpoint, not raw provider state
  created_at, expires_at
  state, attempt, idempotency_key
}

The witness request can reveal a bounded presentation of that record, but its response must bind at least held_id, the call digest, the witness contract, the witness identity/class, a decision or evidence digest, and an expiry. A statement like “approved” without those bindings is replayable and must not release a call.

A witness is independent relative to the claim. It may be:

  1. Human approval — a person accepts a concrete, bounded action.
  2. Peer adjudication — another agent or model checks a proposal under a separately identified principal and policy.
  3. Read-back — an independently sourced observation proves a precondition or an earlier effect. A post-effect read-back cannot authorize the same held call, because that call has not run yet; it can witness a prerequisite or release a later dependent call.
  4. External attestation — CI, git ancestry, a database read, a policy engine, or hardware-backed signer proves the requested fact.

“A second agent said yes” is not by itself independence. The witness contract must specify an acceptable source and the verifier must establish that source did not merely echo the proposing agent.

Process model: a reconciled graph, not a pipeline

The arrows in a simple state diagram are useful for orientation, but they are not the process model. A held call is a durable resource reconciled from facts and events. Evidence can arrive twice, in either order, or after cancellation; policy can change while approval is pending; a witness can revoke an attestation; an execution worker can die after the effect but before recording completion; result delivery can fail while the effect remains complete. Treating those cases as one linear WAITING -> VERIFYING -> EXECUTING -> RESUME program creates races and duplicate effects.

Do not make one enum carry all of this. Keep orthogonal observed facts on the held-call resource:

HeldCallStatus {
  intent_generation       // increments if tool/args/contract changes
  lifecycle               // open | cancelled | expired
  evidence[]              // pending | valid | rejected | revoked, each source-bound
  witness_rule            // all | any | quorum(n) | expression over witness classes
  policy_epoch_checked
  readiness               // unsatisfied | satisfied | stale
  execution               // none | leased | effect-recorded | outcome-unknown
  result_admission        // pending | admitted | quarantined
  continuation_delivery   // pending | delivered | acknowledged
}

Each durable event is appended idempotently, then a reducer updates observed facts and a reconciler asks what work is enabled now? It may schedule zero, one, or several actions. Workers claim actions with leases and report new facts; they do not advance a global program counter.

on event(call_id, event_id):
    append_once(event_id)
    facts = reduce(all_events_for(call_id))

    repeat until no local fact changes:
        desired = derive_desired_state(spec, facts, current_policy)
        actions = diff(desired, facts)
        enqueue_idempotently(actions)
        facts = reduce(new_local_events)

The repeat is a bounded fixed-point calculation, not an unbounded busy loop. External work returns through new events and starts another reconciliation tick. A per-call generation and event IDs make old work harmless after edits or cancellation.

The loops that cooperate

There is no requirement that one process own a held call for its lifetime. At least four independent loops can cooperate through the durable record:

  1. Call controller loop — folds proposal, policy, user edits, cancellation, time, and evidence into desired readiness. It can move unsatisfied -> satisfied -> stale -> unsatisfied rather than only forward.
  2. Witness loops — one adapter per eligible witness route publishes requests and ingests attestations or revocations. Routes can run concurrently; all, any, quorum, veto, and fallback-after-timeout are data in the witness rule.
  3. Execution recovery loop — acquires the effect lease only for the current generation, revalidates immediately before dispatch, and resolves crashes using the tool’s idempotency key or an independent effect read-back. outcome-unknown is a real state; it must not be converted into a blind retry.
  4. Continuation delivery loop — admits/quarantines the result and retries delivery to an offline or moved session. Delivery can loop independently after execution is complete and can be acknowledged separately.

Notification, expiry, garbage collection, and audit projection can be additional observer loops. They do not authorize execution.

Common non-linear paths

                              policy/config changes
                           +-------------------------+
                           v                         |
PROPOSED -> HELD <-> EVIDENCE_UNSATISFIED -> SATISFIED
                ^       ^          |              |
                |       |          | revoke/veto  | pre-exec revalidation
 edit/transform |       +----------+              v
(new generation)                            READY_FOR_LEASE
                ^                              |       |
                |                         lease lost   | effect dispatched
                |                              |       v
                +------------------------------+  EFFECT_RECORDED
                                                   |          |
                                          quarantine      admit result
                                                   |          v
                                                   +----> DELIVERY_PENDING
                                                            ^       |
                                                     retry  |       v
                                                            +-- DELIVERED

Any open pre-effect node --cancel/expiry--> TERMINAL_NO_EFFECT
Any post-dispatch ambiguity -----------> OUTCOME_UNKNOWN --read-back--> effect/no-effect

This is deliberately a graph with cycles:

Joins, forks, and nested holds

A witness obligation is often a dependency graph rather than one request. For example, transfer_money may require (human approval AND fraud check) OR break-glass operator, with a compliance veto. The controller forks witness requests, folds responses under that expression, and joins only when the rule is satisfied. A witness provider may itself propose a governed tool call; that nested call receives its own held_id and dependency edge rather than blocking the parent worker in memory. Cycle detection and a maximum dependency depth must fail closed, because A waits for B waits for A is otherwise a durable deadlock.

The same graph permits speculative work without violating ordering. The scheduler may run calls proven independent of the held result, but any call with a dependency edge remains unready. “Pause the agent” is therefore a policy/UI projection of the dependency graph, not a kernel-wide process suspension.

Invariants across every loop

Does the whole agent pause?

That is a dependency and user-preference question, not part of the verdict itself.

The provider’s KV cache may survive separately, but correctness must not depend on it. The durable continuation is the checkpoint plus references needed to reconstruct the next turn.

Tool-call verbs: dispositions, not plugin names

The core vocabulary is easiest to understand as kernel dispositions over two checkpoints:

Checkpoint Verdict Meaning
proposal ALLOW Execute the canonical proposal now.
proposal DENY Never execute this proposal.
proposal TRANSFORM Replace tool and/or arguments with a kernel-approved proposal, then adjudicate/execute that exact replacement.
proposal REQUIRE_WITNESS Persist and await an independently verifiable condition.
proposal DEFER / INDETERMINATE This adjudicator cannot decide; climb to another layer or fail closed.
result QUARANTINE Keep returned bytes out of model-visible context.

These are not mutually interchangeable extension APIs. In particular:

Where plugins fit

A plugin system should expose narrow, typed stages around the stable verdict ABI rather than let extensions own execution:

proposal
  -> canonicalizers / proposal transforms
  -> adjudicator chain
  -> witness-contract providers (only when held)
  -> kernel executor
  -> result admission filters
  -> audit/notification sinks

1. Proposal transform plugin

A transform plugin can normalize paths, redact forbidden fields, substitute a sanctioned tool, add a preview flag, or map a product-specific tool call into a canonical kernel call. It returns a proposed TransformPayload; the kernel then validates the payload, detects transform loops, applies capability checks to the replacement tool, and re-adjudicates the transformed call.

This makes TRANSFORM a good meeting point between plugins and the kernel, not a trust boundary. A plugin cannot transform delete_everything into an unreviewed shell escape and inherit the original call’s authority.

Recommended contract:

2. Adjudicator plugin

An organization may add DLP, cost, compliance, ownership, or product-specific policy. It returns a core verdict or a registered restrictive verdict. Folding is monotone: an extension may narrow authority, request a witness, or defer, but cannot override the kernel’s default-deny capability floor.

3. Witness-provider plugin

A witness provider translates a typed witness contract into an external request and verifies the response: GitHub review, Slack approval, CI status, git ancestry, a database read-back, another model, or an enterprise policy engine. The plugin never marks the held call READY directly; it submits an attestation to the kernel verifier.

4. Result-admission plugin

A result plugin can identify secrets, prompt injection, unsupported provenance, or schema violations. It may redact through a separately adjudicated transform or return QUARANTINE; it must not silently inject hidden instructions into model context.

5. Notification and audit plugin

These plugins project state changes to user surfaces. They are observers: failure to notify cannot change the authorization outcome, and replaying a notification cannot replay an effect.

User-centric policy and preferences

Users should be able to choose how stricter gates are experienced, while policy owners retain control over the minimum floor.

Useful preferences include:

Preference Examples
Witness route me, team reviewers, CI, named policy service, independent model class
Wait mode block briefly, always background, notify-only, cancel on disconnect
Timeout fail closed after 10 minutes; re-request after policy change
Scope always witness money movement; witness writes outside workspace; never send source to third-party reviewers
Transform posture auto-apply formatting/path canonicalization; preview semantic rewrites; forbid tool substitution
Disclosure exact args to local approver; redacted summary to remote service
Resume UX return to foreground, inbox item, webhook, continue only on explicit user click
Cost/latency local verifier first, frontier model only if local defers

The merge rule should be explicit:

effective behavior = mandatory kernel floor
                   + organization policy
                   + project policy
                   + user preferences that only narrow or route
                   + per-call choice that only narrows or cancels

A user can say “ask me before every write” or “use CI rather than a reviewer for this claim.” A user preference cannot say “ignore the organization’s secret-exfiltration denial.” Administrators may delegate selected widening choices through a witnessed policy-change syscall, but that is itself governed work, not a preferences toggle.

Plugin selection is also policy. A profile should pin plugin identity, version/digest, permissions, data egress, allowed stages, timeout, fallback, and precedence. Marketplace popularity is not authority.

Minimal external protocol

A practical first spine needs only four durable command/query operations. These mutate or read the held-call resource; they do not encode a linear workflow:

POST /tool-calls
  -> 200 result | 403 denial | 202 { held_id, witness_request, expires_at }

GET /held/{held_id}
  -> current state and bounded status

POST /held/{held_id}/attestations
  -> accepted-for-verification, duplicate, stale, or invalid

POST /held/{held_id}/cancel
  -> idempotent cancellation

A queue consumer and a polling client are then two adapters over the same reconciliation graph. A later event stream (held.created, attestation.accepted, held.ready, call.completed, result.quarantined) improves latency but should not be the source of truth.

The smallest end-to-end witness is: a fake money_transfer call returns 202; a second principal submits a call-digest-bound approval; the kernel revalidates and executes it once; duplicate and out-of-order approval delivery does not duplicate the effect; a policy-epoch change sends a ready call back to evidence reconciliation; and result-delivery retry gives the parked session exactly one logical admitted result without rerunning the tool. A crash-after-effect/read-back case closes the most important non-linear window. Those tests are the proof that this concept has become execution rather than vocabulary.

Design choices to keep separate

  1. Approval versus evidence. Both can satisfy REQUIRE_WITNESS, but their contracts and verifier logic differ.
  2. Transform versus witness. A transform may remove the need for a witness (for example, turn a destructive call into a preview), but the transformed call must be adjudicated from the start; a plugin cannot mutate a held call under an existing approval.
  3. Session resume versus effect execution. The call can complete while the UI is offline. Resuming presentation is retriable; executing the effect is not.
  4. Protocol extension versus implementation plugin. Registered verdict kinds preserve wire compatibility. Plugin loading, isolation, signing, capabilities, and lifecycle require a separate host contract.
  5. Context residency versus semantic continuity. Keeping a provider cache warm is a performance optimization. The held-call ledger is the correctness mechanism.

Recommendation

Treat REQUIRE_WITNESS as the kernel’s durable await primitive, implemented by convergent reconciliation loops rather than a blocking pipeline, and TRANSFORM as one typed output available to proposal-stage plugins. Build the held-call event ledger, reducer/reconciler, and exact-once effect plus idempotent-delivery spine before adding a broad plugin marketplace. Then expose plugins at typed stages with pinned identities and monotone authority, and expose user preferences primarily as routing, latency, disclosure, and stricter-policy choices.