Evidence envelopes and the action gate
An envelope binds a claim to the actor, the authority it held at admission, the evidence it relied on, and the context it was actually given, so a consequential action is admitted or refused on the record rather than on an agent's say-so.
Every claim on this page checked against the product source at a pinned revision: 2026-08-19 @ 0c2a08a7.
What this is
An evidence envelope is an immutable record that binds five things together before a consequential action is allowed to happen: the claim being made, the actor making it, the authority that actor held at the moment of admission, the evidence the claim relies on, and the context the agent was actually given to work from. The action gate evaluates that envelope and produces exactly one of two durable outcomes: admitted, in which case the action dispatches through an atomic outbox and its result is verified independently of the agent’s own report, or refused, in which case a structured receipt records why and what a safe next step would be. Later contradiction or supersession updates the record; it never erases it.
One thing to know before anything else: this mechanism is source complete,
not yet live. The full contract, gate, outbox, and verification pipeline
exist on the main branch of the codebase (proxy/src/evidenceEnvelope/) and
pass their tests, including a hostile fixture, but the mechanism has not yet
been through live production acceptance in the running product. The public
system map carries the same label. This page describes what the source
guarantees, and says so plainly where the guarantee is a tested design rather
than accumulated production hours.
Why it exists
Retrieval hands an agent text. That is what most memory systems are: the agent asks, gets passages back, and then acts on whatever it concluded, with nothing binding the conclusion to what it actually read, who it actually was, or what it was actually allowed to do. The gap shows up exactly when it is most expensive: at the moment an agent does something consequential and reports “done.”
Three failure modes drive the design, and all three are pinned as named cases
in the hostile fixture
(proxy/src/evidenceEnvelope/__fixtures__/evidence-envelope-v1-hostile.json),
each with its exact expected refusal code and envelope digest. A handoff pins
one revision but the checkout has since moved: source.handoff_stale. A lease
scoped to repository A requests a write against repository B:
authority.action_out_of_scope. A work receipt proves a tool returned, but
nothing independently verifies the completion the agent is claiming:
evidence.completion_unsupported. In each case the interesting property is
that the refusal is computable from the envelope itself, because the envelope
carried the pinned revision, the assignment scope, and the evidence references
as first-class fields instead of leaving them implicit in a transcript.
How it works
Every fact in the envelope is either known with provenance or explicitly
not. The payload schema (proxy/src/evidenceEnvelope/validation.ts) forces
each binding, actor identity, lease, assignment, expected and observed source
revision, delivered context receipt, into an explicit-value shape: known
with an observed_at timestamp and a source_ref naming where it was read,
or unknown / not_applicable with a reason code. Nothing is ever rounded up
from missing to assumed. The whole payload is canonicalized under RFC 8785
with a domain separator and hashed, and the digest names the envelope forever;
a corrected envelope is a new version that must carry its predecessor’s digest
(proxy/src/evidenceEnvelope/contract.ts).
Preparation is server-attested; the agent supplies intent, never
authority. In the current Phase 1 flow,
sophia.prepare_evidence_envelope_set_activity accepts only a work ID, the
desired activity, an optional reason, and an idempotency key
(proxy/src/evidenceEnvelope/preparationService.ts). Sophia derives the
actor’s identity, lease, entity scope, the current coordination head, and the
codebase snapshot from the authenticated connection, appends an immutable
context-delivery receipt to the coordination ledger, and returns a strict
draft. At submission, the draft must resolve back to that ledger receipt: the
receipt’s digest must match the delivered context recorded in the draft, and
the requested action must be bound through one canonical specification item
(proxy/src/evidenceEnvelope/contextDeliveryReceipt.ts). A draft that claims
context it was never delivered does not evaluate; it fails the binding check
before anything persists. The submission tool’s own schema states the rule:
“Runtime authority is never accepted here”
(proxy/src/mcp/tools/evidenceEnvelopeTools.ts).
Admission is nine named checks, and refusal is enumerated. The gate
evaluates approval, authority, context, evidence, identity, policy, result,
snapshot, and source, in that fixed order, against a coherent snapshot of
current state, not against what the envelope asserts about itself. Any
non-passing check refuses the envelope. Every refusal is one of 38 enumerated
codes ranked in the contract, and a refusal produces a durable receipt
carrying the primary code, all codes, the candidate digest, a
safe_next_action sentence, and an effects block that states outright:
admitted_envelope_created: false, requested_action_committed: false
(proxy/src/evidenceEnvelope/contract.ts). A refused envelope leaves no
half-committed action to clean up.
Approval, when required, binds the exact action and only releases once.
An approval is bound to the precise action digest, parameter hash, connection,
lease, and principal; parameters that drift after approval refuse as
approval.params_drifted. An admitted action that still needs approval sits
in the outbox with its authority-ready marker null, and a one-way handoff
releases it only after the user’s approval is consumed, single-use, with crash
retries recognized as idempotent replays rather than second consumptions
(proxy/src/evidenceEnvelope/approvalHandoff.ts).
The outbox is atomic with the decision, and dispatch is fenced. When an
envelope with a requested action is admitted, the envelope version, its
evaluation event, the outbox row, and the decision event commit in one
database transaction (proxy/src/evidenceEnvelope/service.ts); there is no
moment where an action is admitted but unrecorded, or recorded but unadmitted.
Dispatch then claims the row under a fenced lease (a fresh token plus a row
version and attempt count), allows at most three attempts with bounded
backoff, and terminalizes a third expired lease as expired rather than
retrying forever. Every attempt at the same action carries the same
idempotency key, derived from the action’s digest, so an executor that crashed
mid-flight sees a recognizable replay instead of a new command. Terminal
settlement is exactly-once: finalizing the outbox row and appending the
terminal lifecycle event commit as one transaction, and only the current lease
holder’s fence is accepted (proxy/src/evidenceEnvelope/actionOutboxDispatcher.ts,
proxy/src/evidenceEnvelope/lifecycleService.ts).
A result is verified against independent artifacts, never taken from the
actor’s report. A terminal event counts as verified only when two things
cross-check (proxy/src/evidenceEnvelope/terminalVerification.ts): a
server-minted work receipt for the action result whose actor identity matches
the envelope’s actor and whose disposition is complete, and a separately
persisted verified evidence row (a test assertion or runtime observation, in
verified state, at corroborated or structural trust tier, with zero hard
failures and no open material contradiction) whose receipt URI and hash point
at that exact work receipt. The schema deliberately allows a reference kind of
agent_assertion so an agent’s own report can be recorded, and deliberately
never counts it toward verification. Saying “done” is admissible testimony; it
is not proof.
The record survives being wrong. Lifecycle events are append-only and
hash-chained. A decided envelope can later be marked corrected or superseded
by a successor, and those currency events attach only to envelopes that
actually reached a decision (proxy/src/evidenceEnvelope/contract.ts).
Reading an envelope back through sophia.read_evidence_envelope always
returns the original admission or refusal, and reports currency as unknown
unless a bounded contradiction snapshot is actually available, so an
unverified empty contradiction list is never presented as “still current.”
This is the same posture Truth and provenance takes with
mined claims: the honest answer when nothing has been checked is unknown, not
clean.
What your agent does with it
// Phase 1 shapes, drawn from the source contract and its integration
// tests rather than a live capture: the mechanism is source complete
// and awaiting live acceptance.
const prepared = await sophia.prepare_evidence_envelope_set_activity({
work_id: 'work:4f2a91c0',
activity: 'ready',
reason: 'Implementation and tests complete on the pinned revision.',
idempotency_key: 'prep-refactor-auth-01',
});
// → { draft, draft_digest, receipt_ref, head_event_hash, ... }
// Sophia derived your identity, lease, entity scope, coordination head,
// and codebase snapshot itself, and receipted the delivered context in
// the immutable coordination ledger. You cannot supply any of that.
const result = await sophia.submit_evidence_envelope({ draft: prepared.draft });
// Admitted → the action row committed with the decision, then dispatched:
// { status: 'decided', decision: 'admitted', outbox_id: 'outbox:...',
// dispatch: { state: 'succeeded', attempts: 1, ... } }
// Refused → a durable structured decision, not an exception:
// { status: 'decided', decision: 'refused', outbox_id: null,
// primary_code: 'source.handoff_stale',
// refusal_codes: ['source.handoff_stale'],
// side_effects_permitted: false,
// refusal_receipt: {
// safe_next_action: 'Refresh and re-attest the handoff against the
// current exact source revision.',
// effects: { admitted_envelope_created: false,
// requested_action_committed: false } } }
await sophia.read_evidence_envelope({
envelope_id: prepared.draft.payload.envelope_id,
envelope_version: 1,
});
// → the immutable payload plus its verified, append-only lifecycle. The refusal is the point. It names exactly which of the nine checks failed, it certifies that nothing was created or committed, and it tells the agent the safe way forward (here: re-observe the source and re-attest, rather than retrying the same stale claim harder). A weak agent with this receipt in hand knows more than a strong agent with a stack trace.
Boundaries
The gate’s action scope today is deliberately narrow. The action contract
admits any canonical sophia.* tool name in principle
(proxy/src/evidenceEnvelope/actionContract.ts), but submission accepts
exactly one reviewed executor in Phase 1, the coordination set_activity
action, and refuses anything else as unsupported. Wider action serving is the
roadmap, not the present tense. Registration is also runtime-gated: the
prepare and submit tools can be withheld from every connection by a single
environment switch (proxy/src/mcp/runtimeToolAvailability.ts).
“Independently verified” means the record cross-checks two artifacts that the agent could not mint alone; it does not mean the gate re-runs your tests at settlement time. The verification reads persisted verified evidence rows and server-minted work receipts and proves they agree with each other and with the envelope. If the underlying evidence row is wrong, the gate faithfully binds a wrong row. Digests bind content everywhere in this pipeline, but nothing is cryptographically signed by a per-machine key; that boundary is the same one the skills pipeline documents.
This also inherits The Security Model’s local-trust boundary. Whoever controls your OS user controls the daemon, its database, and therefore the envelopes themselves. The gate defends the integrity of the record against confused, stale, or overreaching agents; it does not defend against a person who already has your account.