From 1c20cfd22d4775d8a4fb0bf96bfd250c181b52a0 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Tue, 6 Oct 2026 21:57:21 +0200 Subject: [PATCH 1/3] Add agent-cast foundation and implementation ledger --- .gitattributes | 2 + .github/workflows/agent-cast.yml | 52 ++ docs/agent-cast-architecture.html | 504 +++++++++++++ docs/agent-cast/events.jsonl | 6 + docs/agent-cast/evidence/checkpoint.json | 27 + docs/agent-cast/evidence/compiler-repair.json | 47 ++ docs/agent-cast/evidence/contract-review.json | 30 + .../foundation-2026-10-06/build.stderr | 0 .../foundation-2026-10-06/build.stdout | 0 .../foundation-2026-10-06/compare-0.stderr | 0 .../foundation-2026-10-06/compare-0.stdout | 267 +++++++ .../foundation-2026-10-06/compare-1.stderr | 0 .../foundation-2026-10-06/compare-1.stdout | 267 +++++++ .../foundation-2026-10-06/disabled.stderr | 0 .../foundation-2026-10-06/disabled.stdout | 139 ++++ .../foundation-2026-10-06/enabled.stderr | 0 .../foundation-2026-10-06/enabled.stdout | 139 ++++ .../foundation-2026-10-06/fixture-0.bin | 1 + .../foundation-2026-10-06/fixture-1.bin | 1 + .../foundation-2026-10-06/format.stderr | 0 .../foundation-2026-10-06/format.stdout | 0 .../foundation-2026-10-06/help.stderr | 0 .../foundation-2026-10-06/help.stdout | 10 + .../foundation-2026-10-06/qualification.json | 171 +++++ .../foundation-2026-10-06/unit.stderr | 5 + .../foundation-2026-10-06/unit.stdout | 0 .../foundation-2026-10-06/zig-version.stderr | 0 .../foundation-2026-10-06/zig-version.stdout | 1 + docs/agent-cast/evidence/foundation.json | 21 + .../evidence/initial-format-failure.json | 16 + docs/agent-cast/evidence/ledger-tests.stderr | 15 + docs/agent-cast/evidence/ledger-tests.stdout | 0 docs/agent-cast/evidence/ledger.json | 19 + docs/agent-cast/evidence/toolchain.json | 12 + docs/agent-cast/milestones.md | 132 ++++ docs/agent-cast/model-routing.md | 59 ++ docs/agent-cast/tasks.json | 673 ++++++++++++++++++ experiments/agent_cast/README.md | 82 +++ experiments/agent_cast/build.zig | 34 + experiments/agent_cast/src/contracts.zig | 129 ++++ experiments/agent_cast/src/demo.zig | 256 +++++++ experiments/agent_cast/src/executor.zig | 292 ++++++++ experiments/agent_cast/src/optimizer.zig | 291 ++++++++ experiments/agent_cast/src/tests.zig | 6 + scripts/agent_cast_ledger.py | 200 ++++++ scripts/qualify_agent_cast.py | 164 +++++ scripts/tests/test_agent_cast_ledger.py | 148 ++++ 47 files changed, 4218 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/agent-cast.yml create mode 100644 docs/agent-cast-architecture.html create mode 100644 docs/agent-cast/events.jsonl create mode 100644 docs/agent-cast/evidence/checkpoint.json create mode 100644 docs/agent-cast/evidence/compiler-repair.json create mode 100644 docs/agent-cast/evidence/contract-review.json create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/build.stderr create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/build.stdout create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/compare-0.stderr create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/compare-0.stdout create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/compare-1.stderr create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/compare-1.stdout create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/disabled.stderr create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/disabled.stdout create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/enabled.stderr create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/enabled.stdout create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/fixture-0.bin create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/fixture-1.bin create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/format.stderr create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/format.stdout create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/help.stderr create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/help.stdout create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/qualification.json create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/unit.stderr create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/unit.stdout create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/zig-version.stderr create mode 100644 docs/agent-cast/evidence/foundation-2026-10-06/zig-version.stdout create mode 100644 docs/agent-cast/evidence/foundation.json create mode 100644 docs/agent-cast/evidence/initial-format-failure.json create mode 100644 docs/agent-cast/evidence/ledger-tests.stderr create mode 100644 docs/agent-cast/evidence/ledger-tests.stdout create mode 100644 docs/agent-cast/evidence/ledger.json create mode 100644 docs/agent-cast/evidence/toolchain.json create mode 100644 docs/agent-cast/milestones.md create mode 100644 docs/agent-cast/model-routing.md create mode 100644 docs/agent-cast/tasks.json create mode 100644 experiments/agent_cast/README.md create mode 100644 experiments/agent_cast/build.zig create mode 100644 experiments/agent_cast/src/contracts.zig create mode 100644 experiments/agent_cast/src/demo.zig create mode 100644 experiments/agent_cast/src/executor.zig create mode 100644 experiments/agent_cast/src/optimizer.zig create mode 100644 experiments/agent_cast/src/tests.zig create mode 100644 scripts/agent_cast_ledger.py create mode 100644 scripts/qualify_agent_cast.py create mode 100644 scripts/tests/test_agent_cast_ledger.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..c6a7ef62cf --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Keep captured stderr verbatim, including Zig's final blank summary line. +docs/agent-cast/evidence/**/*.stderr whitespace=-blank-at-eof diff --git a/.github/workflows/agent-cast.yml b/.github/workflows/agent-cast.yml new file mode 100644 index 0000000000..99f9d599b1 --- /dev/null +++ b/.github/workflows/agent-cast.yml @@ -0,0 +1,52 @@ +name: Agent-cast Foundation + +on: + pull_request: + paths: + - 'experiments/agent_cast/**' + - 'docs/agent-cast/**' + - 'scripts/agent_cast_ledger.py' + - 'scripts/qualify_agent_cast.py' + - 'scripts/tests/test_agent_cast_ledger.py' + - '.github/workflows/agent-cast.yml' + - '.github/actions/setup-zig/**' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: agent-cast-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + qualify: + name: Agent-cast (${{ matrix.runner }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + runner: [ubuntu-24.04, macos-15] + env: + PYTHONDONTWRITEBYTECODE: '1' + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-zig + with: + version: '0.16.0' + cache-key: agent-cast-foundation + - name: Validate ledger and exercise its failure cases + run: | + python3 scripts/agent_cast_ledger.py validate --json + python3 -m unittest scripts.tests.test_agent_cast_ledger -v + - name: Build and drive the prototype + run: python3 scripts/qualify_agent_cast.py --output "$RUNNER_TEMP/agent-cast-receipts" + - name: Retain qualification evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: agent-cast-${{ matrix.runner }}-${{ github.sha }} + path: ${{ runner.temp }}/agent-cast-receipts/ + if-no-files-found: warn + retention-days: 14 diff --git a/docs/agent-cast-architecture.html b/docs/agent-cast-architecture.html new file mode 100644 index 0000000000..f93251bdbb --- /dev/null +++ b/docs/agent-cast-architecture.html @@ -0,0 +1,504 @@ + + + + + + + + agent-cast · An effect-aware execution optimizer + + + + + + + +
+
Execution architecture / research & design
Proposed system
+
+ An effect-aware execution optimizer + actor runtime +

Optimize the plan.
Own the execution.

+

Compile declared tool calls into guarded execution plans. Preserve each call's authority and observable behavior. Let an actor runtime own the workers, workspaces, connections, and recovery.

+
Protocol: agent-castCompiler: typed Tool IRRuntime: native / proposed ZigFirst target: Linux eval workersClient: macOS + existing harnesses
+
+

Logical calls stay intact.
Physical work can change.

An optimizer batches, schedules, and reuses certified observations. Each original call retains its admission, budget, result, and receipt. The actor runtime executes the chosen plan.

+
Embedded fast pathPersistent resource brokerExisting isolation backends

A cost model chooses among permitted execution strategies. Use the embedded path for tiny native calls and a broker for reusable workers or clients. Retain Erlang-style ownership and existing isolation backends.

+
+
+
01 / Contracts

Describe the effects

Trusted registrations supply dependencies, resource access, and observable contracts.

+
02 / Optimization

Remove physical work

Batch requests and reuse eligible immutable observations while retaining logical calls.

+
03 / Runtime

Own reusable resources

Actors retain workers and clients under explicit leases, epochs, and reset rules.

+
04 / Effects

Retain evidence

Track what started, what committed, and what remains unknown.

+
+

This is a design proposal, not an implemented compiler or runtime. The local measurement below characterizes a small native transport experiment. The isolated eval target is a working assumption; native macOS execution remains a distinct trusted mode.

+
+ + +
+
01

The system map

A bounded logical plan describes the requested calls. The host admits each resolved action. The optimizer changes only eligible physical execution; the actor runtime owns resources and recovery.

+
+
Trusted controlTask scopeExisting providers
+
+
+ + Effect-aware optimizer and actor runtime architectureThe harness builds a typed logical plan from trusted operation contracts. Existing host admission authorizes each resolved action and executor binding. A guarded optimizer batches or reuses eligible admitted work. Embedded and brokered execution retain logical results and receipts. Runtime owners manage workspaces, connections, worker lifetimes, and recovery. + + TRUSTED HARNESS / LOGICAL PLANNING / EXACT AUTHORITY + ACTOR RUNTIME / RESOURCE OWNERS + + + + + + Agent harnessCalls, context, presentationPreserve the model interface + Logical Tool IRTrusted effects + dependenciesCandidates, no execution + Host admissionEach exact resolved actionApproved executor contract + Plan optimizerEligible admitted workGuarded physical plan + agent-cast clientAdmitted calls + groupsFramed control / data streams + Embedded adapterPermitted native strategySkip unnecessary IPC + Execution brokerExecute the physical planLeases, credits, epochs + Native executorsExisting fx execution ownersNo inferred shell rewrites + Operation ownerLogical receipts / work IDsTruthful reuse provenance + Workspace ownerImmutable views + revisionsLease + writer exclusion + Service ownersRetain scoped clientsExecute required requests + Worker supervisorsInterpreter + command jobsMonitor / stop / restart + Artifacts + ledgerValues / receipts / plan provenanceContainment backendOCI / OpenSandbox / VMExisting client librariesHTTP clients / drivers / poolersIsolated workersPython / Node / process domains + +
+
Select a component to inspect its ownership boundary.On small screens, scroll the diagram horizontally.
+
+
Proposed / execution planning

Effect-aware execution optimizer

Transforms eligible admitted logical calls into guarded physical work while preserving the declared observable contract.

  • Each original call retains its authority, budget, result, and receipt.
  • Trusted effects constrain reuse, batching, scheduling, and executor selection.
  • Runtime guards recheck resource identity and generations before start.
+

Ownership

Each mutable resource has one owner. The owner serializes state changes and can delegate concurrent I/O.

Isolation

Arbitrary tool code runs in a worker or sandbox. A logical actor in the broker is not a memory isolation boundary.

Portability

Language adapters use the operation contract. Backends report their actual containment and lifecycle capabilities.

+
+
+ + +
+
02

An effect-aware
execution optimizer

Compile declared tool execution plans into guarded physical work. Optimize the operation graph; existing language runtimes continue to execute Python, JavaScript, and shell code.

+
+
Compiler / proposed

Logical plan → physical plan

The intermediate representation (Tool IR) preserves original call IDs, arguments, dependencies, budgets, and result contracts. Trusted registrations supply effect summaries and executor equivalence rules.

The optimizer can share certified immutable computation, group transport, and schedule independent work. It must retain required errors, audit events, effects, and publication order.

Actor runtime / proposed

Resources with owners

The runtime retains workers, clients, and workspaces. It owns admission queues, lifetimes, cancellation, output credits, generation checks, and effect receipts.

Pooling reuses setup. Each required application request still executes. A repeated HTTP GET or SQL query does not become a pure expression because its client is persistent.

+
01Build logical IRParse bounded plans; attach trusted effects
02Prepare and admitResolve each action; bind its allowed executor
03Optimize ready workApply only certified equivalent strategies
04Guard and executeRecheck leases, versions, epochs, and budgets
05Project each resultKeep logical receipts and publication intact
+
Planning candidates may precede admission, but planning cannot perform tool effects or mint authority. Initial fx passes stay inside existing admitted execution contracts. A lowering that changes an executor or effective environment must be prepared and admitted through the host. The planner cannot read protected preimages outside the host's existing preparation and read-authority policy.
+

Explore legal and blocked transformations

+
+
Logical requests8
Fresh physical reads6
Reuse decision1 cache + 1 shared

All eight calls are separately admitted against snapshot S0. A valid completed value satisfies r2; r1 and r3 share one certified read. Six fresh reads can use a bounded group. No logical call disappears.

Logical Tool IR / schematic
r1 = snapshot.read(S0, A)
+r2 = snapshot.read(S0, B)
+r3 = snapshot.read(S0, A)
+r4 = snapshot.read(S0, C)
+r5 = snapshot.read(S0, D)
+r6 = snapshot.read(S0, E)
+r7 = snapshot.read(S0, F)
+r8 = snapshot.read(S0, G)
Physical plan / schematic
cache_value(B) → r2
+read(A)       → r1, r3
+read(C..G)    → r4..r8
+
+6 fresh reads
+8 logical admissions and receipts
+1 bounded group if the cost model selects it

Proof required for immutable sharing

Same immutable input, implementation, result contract, and authority partition. Preserve per-call audit, budgets, error behavior, and cancellation; a required physical audit event may prohibit coalescing.

This is an explanatory simulation, not an implemented optimizer or measured speedup. The fixture assumes compatible contracts, permitted concurrency, and a valid cached value. Grouping and backend choice remain cost-based.

+
Trusted effect declarations and optimization permissions
Effects describe behavior under a trusted implementation contract, not a model's claim about a tool
Effect / observationPermitted strategyBoundary
Pure(inputs)Memoize certified successful values; share equivalent computationComplete immutable dependencies and no required per-execution effect
ImmutableRead(snapshot)Completed-value reuse, then qualified inflight coalescing and batchingSame input identity, visibility, output contract, and compatible caller state
MutableRead(resource)Execute each observation; retain eligible clients and buffersNo general output deduplication; respect ordering and freshness
Mutate(workspace)Keep each action, exact admission, ordering, and effect evidenceNo automatic deletion, merging, or speculative execution of writes
ExternalEffect(service)Reuse a permitted client while performing required requestsGET, SELECT, or idempotence alone does not prove interchangeable results
OpaqueExec(environment)Preserve the admitted executor and conservative effect barriersNo inferred shell lowering or result cache; unknown dependencies stay unknown
+
Tool IR, physical work, and receipt sketches
type LogicalCall = {
+  logical_id: string;
+  tool: string;
+  implementation_id: string;
+  canonical_arguments: unknown;
+  argument_bindings: Binding[];
+  dependencies: string[];
+  effects: TrustedEffectSummary;
+  result_contract: string;
+  deadline: Deadline;
+  output_budget: number;
+};
+
+type AdmittedCall = {
+  call: LogicalCall;
+  exact_authority: string;
+  allowed_executor_contract: string;
+  generations: GenerationSet;
+};
+
+type PhysicalWork = {
+  work_id: string;
+  admitted_consumers: string[];
+  strategy: "native" | "batch" | "memoized" | "worker";
+  guards: RuntimeGuard[];
+};
+
+type LogicalReceipt = {
+  logical_id: string;
+  outcome: Outcome;
+  effect: EffectEvidence;
+  provenance: "executed" | "memoized" | "coalesced";
+  physical_work_id?: string;
+};

Proposed structural sketches. Supporting types are placeholders for bounded typed contracts. An agent-cast operation ID remains a logical identity; physical work IDs expose actual sharing. Fixture replay has separate provenance and cannot claim current execution. Budgets count logical calls even when physical work is reused.

+
Initial optimization passes and their guards
PassLegal transformationGuard / stop condition
Completed-value reuseReturn a certified immutable value through the existing rendererCurrent authority, input identity, contract, and implementation; a failed guard requires rejection or fresh preparation/admission; cache successful values only
Inflight coalescingShare one physical read among compatible admitted consumersIndependent subscriber deadlines, cancellation, credits, audit, failure provenance, and retry eligibility; never cache shared execution failures as semantic results
Transport batchingSend compatible calls together without removing their identitiesPreserve per-call errors, limits, ordering, and failure policy; do not wait indefinitely to fill a batch
Dependency schedulingExecute already independent nodes within resource budgetsKeep control dependencies, conflicts, and deterministic write order
Executor selectionChoose a certified native or worker implementationStay inside the admitted executor contract; a changed binding requires host admission
Cost-based strategySelect direct, cached, grouped, or persistent execution where permittedInclude planning, hashing, guards, queueing, projection, and persistence; decline unprofitable passes
+
The compiler must be able to decline optimization. A live read is not an immutable observation; a shell command is not a native read by resemblance; an unused result does not erase a requested action. Unknown effects remain barriers.
+

Compile fixed plans and currently ready graph segments. Dynamic arguments require preparation and admission after their inputs arrive. Arbitrary Python or JavaScript orchestration runs in its admitted runtime; the pre-admission planner does not execute or statically understand arbitrary code. MCP tool annotations also require a trusted source; annotations alone do not establish purity.

+
+
+ +
+
03

A call has more than
one outcome

Admission, completion, cancellation, and committed effects are separate facts. An operation can be cancelled after it has changed external state.

+
+
queuedrunningsettled
+
+
+
Successful operation sequenceHarness admission, durable intent, worker start grant, external effect, confirmation, worker result, and harness publication.HarnessBrokerWorkerServiceinvoke + exact leasedurable intentstart grantperform effectconfirm commitresult + evidencesettled receipt
01 / 07

Admit the exact operation

The host binds arguments, workspace, environment, and authority to the operation identity before invoking the broker.

+
Illustrative terminal receipt

Success with a committed effect

outcome: succeededeffect: committed
{
+  "operation_id": "op_42",
+  "actor_epoch": 7,
+  "state": "settled",
+  "outcome": "succeeded",
+  "effect": "committed",
+  "durability": "durable",
+  "evidence": "service_confirmation"
+}

Persist the known result before publication. A reconnect retrieves this operation's receipt without repeating its mutation.

The walkthrough is an explanation. It executes no tools. The receipt always shows the scenario's terminal state, independently of the selected event.

+
+

call, cast, monitor

Use tracked calls for effects, notifications for events, and monitors for lifetime changes. An unacknowledged cast is not a durable mutation API.

Same operation, same identity

Bind identity to the principal, tool implementation, exact arguments, and task/workspace lease. Reconnect queries that operation. Reject reused IDs with different bindings; retries do not automatically authorize repeating an effect.

Bounded data movement

Length-framed JSON-RPC control travels over a persistent local Unix socket. Negotiate a maximum frame size and reject oversized frames before allocating. Separate output transport uses byte credits, sequence numbers, quotas, and artifact handles.

Deadline and cancellation

Queue time consumes the execution budget. Reconnect cannot extend the stored deadline. Cancellation acknowledgement means observed; a terminal receipt describes effects.

Durability by operation class

Persist recovery-relevant intent before dispatch. Cheap reads can use ephemeral receipts; syncing every read could dominate its execution time.

Uncertain external effects

A service may commit before a worker loses its reply. Reconcile through an idempotency key or service evidence. Infrastructure restart alone cannot prove exactly-once effects.

+
Proposed invocation envelope and authority checks
type Invocation = {
+  operation_id: string;
+  actor: { runtime_id: string; actor_id: string; epoch: number };
+  task_lease: string;
+  action_lease: string;
+  tool: string;
+  tool_version: string;
+  arguments: unknown;
+  remaining_budget_ms: number;
+  output_limit_bytes: number;
+};

Schema sketch, not an implemented API. Enroll the harness through an authenticated control channel. Keep authority issuance outside tool workers; keep privileged descriptors and credentials outside arbitrary agent code. Recheck policy, credential, workspace, and implementation generations before start. Revocation stops future grants; an already released operation may still perform an effect.

+

Reuse JSON-RPC and MCP conventions at the external boundary. MCP permits custom bidirectional transports; agent-cast would add a documented execution lifecycle profile.

+
+
+ +
+
04

Recover infrastructure.
Account for effects.

Erlang contributes ownership, messages, monitors, and supervision. Restart policy describes resource recovery; retry policy describes whether work may be performed again.

+
+
Proposed supervision tree
  • runtime_supervisor
    • control_ownersRouting, plan templates, caches, ledger; bounded reconstruction
    • service_supervisors
      • http_client_ownerPool scoped by endpoint, principal, and configuration
      • database_session_ownerTransaction ownership; drop uncertain connections
    • task_supervisor
      • workspace_ownerAuthority loss stops dependants and quarantines state
      • interpreter_workerUnexpected exit: report DOWN, replace epoch
      • command_jobTemporary; do not restart an effect automatically
Explore a failure

What should restart?

A control actor can reconstruct state. A failed tool operation can require reconciliation even after its worker has been replaced.

Select a failure boundary

Logical actors share a native process. Their ownership rules do not create BEAM-style memory isolation; a native fault can still terminate the broker.

+
Cooperative stop

Stop admission, request cancellation, and allow bounded cleanup.

Forced stop

Terminate the owning process or containment domain after grace expires.

Fencing

Invalidate old actor epochs and leases before replacement state is published.

Restart limits

Apply a bounded restart budget and backoff. Repeated failure quarantines the resource.

+
Actors are state machines, not one OS thread each. Keep trusted bookkeeping bounded; delegate blocking I/O. Unknown native plugins belong outside the broker process.
+

A Zig implementation would not inherit BEAM's preemption, garbage collection, or process isolation. Choosing Zig supports integration with fx; this research does not establish BEAM as a performance bottleneck. Erlang process efficiency.

+
+
+ +
+
05

Reuse needs
a validity contract

Persistent resources avoid setup work. Cached observations avoid repeated computation. Both require current authority and an explicit consistency boundary.

+
+
Eligible: immutable inputs

Snapshot reads and search

An immutable snapshot gives a stable input identity. Cache file bytes, directory listings, or search results against that identity.

Identity
snapshot digest + tool version + exact arguments + options
Validity
The snapshot and implementation are unchanged; the caller still has authority to read the result.
Boundary
A write produces a different revision. A live checkout cannot silently stand in for an immutable snapshot. Reused values pass through each call's current renderer and receipt.
Benefit
Reuse search/index work and coalesce identical reads without repeating shell or interpreter setup.
+
01 / Resource reuse

Retain setup

Keep a permitted worker or client alive. Its state remains explicitly owned.

02 / Memoization

New call, cached result

Issue a new receipt after current admission and input validation.

03 / Receipt lookup

Same call, old outcome

Recover the existing operation without repeating its effect.

04 / Fixture replay

Recorded observation

Supply saved data without claiming the effect occurred now.

+
Full cache policy matrix
Proposed first-version reuse rules
ResourceAllowed reuseInvalidation / boundary
Images, schemas, binariesImmutable content and implementation metadataDigest, platform, implementation, or configuration change
Snapshot files / searchResults against immutable input identitiesSnapshot, exact arguments, tool version, ignore rules, visibility, or options
Live filesystemExisting kernel caches; reopen through existing semanticsWatchers are hints. Metadata equality cannot prove unchanged bytes.
HTTPPersistent clients; response caching under HTTP semanticsEndpoint, principal, credentials, TLS, proxy, policy; freshness and validators
DNSEstablished resolver and client behaviorLifetime, resolver configuration, and network context
DatabaseConnections under driver/pooler reset contractsTransaction, session, role, tenant, credential, and configuration state
Shell outputNo general result cacheOnly declared pure operations with known immutable dependencies qualify
Interpreter stateWithin the declared task/sessionFresh-call semantics, worker epoch, task ownership, and reset qualification
+
A previous write receipt cannot satisfy a write in a fresh eval workspace. The operation may be recorded as successful while its effect is absent from the new environment.
+

Networking reuse requires a protocol-aware owner. A new shell-launched client cannot transparently borrow another process's HTTP connection. A CONNECT tunnel cannot pool arbitrary application sessions. Reuse existing HTTP clients and database pooling contracts.

+
+
+ +
+
06

Batch the work.
Preserve each action.

The Tool IR keeps data and control dependencies. The optimizer groups eligible ready work; unresolved actions and effect barriers remain explicit.

+
+
Dependency-aware batch executionTwo immutable reads may run concurrently. Their outputs determine the final write arguments. The exact write must then pass host admission before it executes and produces its own receipt. The graph is not an atomic transaction.Read snapshot AIndependent / admittedRead snapshot BIndependent / admittedBind argumentsUse actual read resultsHost admissionReview exact resolved writeWrite + own receiptExclusive workspace leaseA later failure may leave earlier effects committed. Return per-node outcomes and evidence.
Illustrative graph. Future unresolved arguments cannot be authorized by an earlier graph-level approval.
+

Effect-aware scheduling

  • Immutable reads can run concurrently.
  • Workspace mutations hold an exclusive lease.
  • Opaque execution invalidates mutable derivations before it starts and retains its exclusive lease until contained descendants are quiescent.
  • Transactions retain their service-session lease.
  • The trusted registration derives access; model-supplied purity claims do not establish it.

Two optimization tracks

Transport batching preserves individual tool requests and results. Programmatic tool calling lets the model express loops, transformations, and dependencies in code.

Programmatic calling can reduce inference round trips and intermediate context. It changes the model's affordances, so evaluate quality separately from execution latency. Established code-execution pattern.

+
Fresh

Per-call state

Preserve the harness's fresh interpreter promise. Preloading cannot silently retain a previous call's globals, descriptors, or threads.

Session

Task-owned state

Retain imports and state within one task. Worker loss changes the epoch and reports state loss to the caller.

Template

Qualified preparation

Create fresh workers from a tested, uncredentialed template. Fork safety and cloned state require qualification.

+

Whole-workspace exclusion is the initial policy. Reject conflicting batch nodes without explicit dependencies so lock acquisition cannot choose a different write order. Command exit alone does not establish quiescence; cache eligibility returns only against a newly established immutable revision. Fine-grained path locking must account for aliases, symlinks, hard links, mounts, and directory mutations. External editors bypass broker locks, so live host workspaces remain ineligible for immutable-result caching.

+
+
+ +
+
07

A warm environment
must still be a clean trial

Use the task or evaluation attempt as the containment boundary. Share prepared infrastructure while giving mutable task state a fresh owner.

+
+
Separate lifetimes

Host scope

Immutable images, executable metadata, artifact content, and warm capacity.

Task scope

Writable workspace, worker state, shell state, task operation IDs, and scoped credentials.

Service scope

Protocol-aware HTTP or database owners. Cross-task reuse requires permitted identity and a reset contract.

Independent evaluator boundaryGolden state, grader assertions, and evaluator credentials stay outside the agent's execution environment. Receipts describe execution; judges check the task outcome.

Reuse an existing containment backend

Start with one Linux backend. OCI images and OpenSandbox provide packaging and lifecycle interfaces; Docker is a practical comparison baseline.

Qualify portable VM options for macOS clients. A Linux guest cannot reproduce native macOS builds or host facilities with identical semantics.

Native same-UID workers are a trusted execution mode. Socket permissions and descriptor hygiene alone do not establish containment from hostile code.

OpenSandbox API · Microsandbox candidate

+

The reset contract

+
  1. Fence the taskReject new starts and invalidate old leases and epochs.
  2. Resolve outstanding workFinish, cancel, or retain an explicit unknown outcome.
  3. Stop the containment domainRequest graceful shutdown, then force termination after grace.
  4. Verify process drainConfirm descendants cannot keep writing. Process-group termination has weaker guarantees than qualified containment.
  5. Revoke resourcesClose routes, credentials, subscriptions, and task-scoped handles.
  6. Discard owned task stateDispose of runtime-owned overlays and temporary storage. User mounts and external databases have separate lifetimes.
  7. Initialize the replacementUse pinned images, fixtures, versions, environment, and fresh worker identities.
  8. Probe the boundaryCheck prior files, jobs, state, handles, and credentials are absent. Quarantine failed resets.
+
Memory snapshots can duplicate randomness, identifiers, and credentials. Network connections may not survive restoration. Prepare templates before credential injection and retain external client pools outside the snapshotted guest. Firecracker snapshot constraints.
+

Declare the evaluation mode

Reconstruct declared task state and run actual tools. Warm images and permitted pooled clients may remain, but previous trial mutations and cached stateful observations cannot carry over. Measure performance and independently grade terminal effects.

run manifest = task + harness commit + model/provider configuration + image + workspace snapshot + environment + tool/schema versions + clock/randomness policy + budgets + network fixtures + warm-state policy
+

ThinkingBox provides a relevant evaluation pattern: fresh backend state per attempt, repeated trials, terminal-state grading, and checks for extra effects. A deterministic environment does not make model outputs deterministic.

+
+
+ +
+
08

Plug into fx
at the execution boundary

The logical plan builder can describe calls before admission. Native optimization and portable execution stay after existing exact-action admission and before result publication. Preserve fx's ownership-bearing contracts.

+
+
ExistingDecode tool
ExistingPermission admission
ProposedGuarded optimizer + adapter
ExistingEffect handoff
ExistingResult publication
ExistingSession persistence
+
Existing SDK seam

Begin with a libfx host tool

The Node SDK invokes a registered tool through execute(input, { signal }). A client can retain a broker connection and translate the signal into cancellation.

Use immutable snapshot reads first. A deterministic provider fixture should generate an actual tool call through libfx, receive the broker result, and complete the turn.

Compare optimizer disabled versus enabled with identical descriptors, calls, and results. Keep per-logical receipts outside model-visible content initially; use the existing renderer for reused values.

Illustrative adapter / proposed client API
const snapshotTool = {
+  name: "agent_cast_read_many",
+  description: "Read the task's immutable snapshot.",
+  inputSchema: readManySchema,
+  execute(input, { signal }) {
+    return runtime.readMany(snapshotLease, input.paths, {
+      signal,
+    });
+  },
+};

This sketch assumes a proposed client, schema, and snapshot lease. It is not a shipped API or a runnable example.

+
Contracts that determine migration scope
Current contractIntegration consequenceSource
Ordinary host-tool authorityThe JavaScript host owns effects. This seam does not automatically inherit native CLI permission admission.sdk/README.md
SDK cancellationThe SDK stops waiting and ignores late results. The adapter must drain underlying work, retain receipts, and close resources.sdk/fx-sdk.js
Callback identityThe callback currently exposes a signal, not stable operation/session identity for durable checkpoint recovery.executeHostTool
Prepared file mutationAn opaque process-local commit token cannot be serialized through generic JSON RPC.PreparedFileMutation
Native terminalsTerminals belong to the current fx process. A surviving daemon changes ownership and cleanup behavior.terminal/client.zig
Managed command completionPreserve stop versus waiter-detach decisions, delivery cursors, and process-exit plus output-drain barriers.managed_execution_contract.zig
Admitted command environmentThe executing environment must match the admitted command, cwd, shell profile, and captured/TTY mode.command_admission.zig
+
Keep fx's existing HTTP pool and cheap native paths. The first passes preserve admitted executor selections. Do not rewrite a shell command into a builtin by inference. Create broker connections lazily; terminal and file-mutation migration need separate reviews.
+

Source review: fx commit c1db919112c5c83fdcc0be508f8d34d189d149af. Relative source links work while the explainer remains in this checkout's docs/ directory.

+
+
+ +
+
09

Measure the work
that disappears

Attribute latency before optimizing it. Include plan construction, canonicalization, cache validation, guards, and result fan-out alongside launches, imports, IPC, service setup, useful work, and durable publication.

+
+
Measured / native transport probe

Verified 4 KiB warm file read

Median cost in microseconds per read. Every operation opens, reads, checks EOF, and closes the fixture. Batch cost is total batch time divided by eight.

Logarithmic axis starts at 1 µs. Numeric values are the measured medians.

256 interleaved samples per path. Batch quantiles are full-batch times divided by eight, not individual-call latency quantiles. Full batch: p50 86 µs; p99 102 µs.
Pathp50 µsp95 µsp99 µs
Direct native91213
Warm native RPC162022
RPC batch, amortized10.7512.12512.75
Fresh native process1,1281,3151,436
Illustrative / Amdahl's law

Execution gain versus total gain

Change the assumed fraction of addressable tool work and its speedup. Other work remains unchanged.

1.18×

20% of time accelerated by 4× leaves 85% of the original runtime.

Baseline · 100%
Candidate · 85%
Other workAddressable tools

speedup = 1 / ((1 − f) + f / k)

A scenario calculation, not an agent-cast performance prediction. Throughput also depends on resource budgets and contention.

+
The native probe excludes JSON encoding, permissions, durable receipts, sandbox boot, and fx. It uses a pre-established Unix stream socketpair and warm file data. It supports retaining direct native paths and targeting launch/batch costs; it does not establish an end-to-end harness speedup.
+
Measurement provenance and portable evidence

Recorded on 6 October 2026 using macOS 26.5.2 arm64, Apple Clang 17, and -O2. All returned bytes were checked. Fixed buffers, process-count bounds, CPU limits, and a wall timeout bounded the run. This host rejected memory resource limits; that failed attempt was retained separately and is not a performance result.

Measurement receipt is embedded in this HTML file.

The receipt and probe source are embedded in the file. They remain available when this explainer is copied elsewhere. No external request is made by the downloads.

+

Strong comparison baselines

Stock fx native operations, warmed terminals, persistent MCP servers, Hermes's existing kernel, pooled HTTP clients, and existing warm sandbox providers.

Account for total resources

Report broker and complete process-tree memory, CPU, guest memory, worker density, pools, cache state, and marginal cost per active task.

Two evaluation tracks

Fixed tool traces isolate execution changes. Full agent trials measure outcome rate, repeated reliability, tokens, and cost with equivalent fresh state.

Charge for optimization overhead

Measure planning, hashing, guard checks, and result fan-out. Keep authorization-to-start dispatch separate from total completion, which includes output and required durable synchronization.

+
+
+ +
+
10

Build on what
already exists

Persistent workers, sandbox pools, programmatic tools, and evaluation adapters are established. The proposal must demonstrate useful savings across these existing boundaries.

+
+
Execution providers

DeepSeek Harness

Replaceable providers, canonical tool values, permission boundaries, and declared concurrency provide an integration seam. Its fresh Node program execution must keep fresh semantics when adapted.

Pinned architecture · Node execution
+
Persistent kernels

Hermes

Persistent Python kernels and environment plugins already amortize startup. Bounded concurrent tool RPC is a candidate improvement to compare against the existing persistent path.

Pinned execution implementation
+
Warm provisioning

OpenSandbox

Reuse lifecycle APIs, execd, warm capacity, and backend adapters. Its experimental client pool hands out fresh prepared sandboxes; acquired sandboxes belong to the caller until destruction.

Pinned pool contract
+
Public execution API

Codex app server

Public command execution, filesystem operations, output streaming, cancellation, and environment APIs provide another adapter surface. They do not establish this session's internal service architecture.

Official API overview
+
Recent eval release

Microsoft / Hugging Face ThinkingBox

The joint post on 3 October 2026 is a plausible match to the recent release discussed. Fresh backend state, repeated attempts, terminal-state grading, and extra-effect checks directly inform reset validation.

Primary announcement
+
Recent memory artifact

Cognition Agent Memory Repo

Repository activity began 4–5 October. The artifact is a Git-backed memory specification and skill/plugin. Cognition's devin-connect is a separate transport gateway; these inspected artifacts do not expose the full Devin runtime.

Memory specification · Gateway
+
Evaluation orchestration

Harbor / Inspect / OpenEnv

Reuse existing task orchestration and grader boundaries. Environment and sandbox interfaces are natural integration points for a new execution backend.

Harbor · Inspect interfaces · OpenEnv
+
Model–harness research

Finding the Right Fit

The independent study submitted 1 October examines model–harness interactions. Tool affordances and recovery behavior can change agent results, so evaluate code mode separately from transport acceleration. This is not identified as a Hugging Face organizational harness release.

Primary paper
+

The two recent release identities remain plausible matches to the original references, rather than user-confirmed identities. Research date: 6 October 2026.

+
+ +
+
11

Start with a slice
that can disprove the design

Establish equivalent useful work and real interoperability before expanding into a general execution platform.

+
Foundation locally exercised
Implementation checkpoint, 6 October 2026. The standalone Zig 0.16.0 experiment captures actual file bytes and preserves ten logical receipts while grouping eight approved reads into four backing reads. Eighteen focused tests and two independent fixture comparisons passed locally. This proves the bounded embedded workload; shared completed caching, broker clients, supervision, and harness integration are later milestones. CI is pending. Follow the milestones, task ledger, model assignments, and prototype instructions.
1

Attribute current costs

Trace current optimized baselines. Separate normalization, admission, useful work, service setup, rendering, and persistence; record logical and physical operation counts.

2

Define Tool IR and trusted operation contracts

Preserve original IDs, arguments, dependencies, result contracts, and budgets. Whitelist immutable fixture reads; default unknown registrations and commands to opaque.

3

Add completed-value reuse inside admitted execution

Implement one immutable read executor and current-authority checks. Reuse certified successful values through the existing renderer, with a new logical receipt and explicit provenance.

4

Qualify inflight sharing and bounded batches

Keep independent subscriber cancellation, deadlines, output credits, and audit. Add deterministic scheduling and a cost model that can choose the original direct path.

5

Prove broker and libfx interoperability

Use Python/Node clients and a fixture-driven host tool. Keep descriptors identical. Add one scoped pooled HTTP client while executing every required request.

6

Qualify isolated tasks and failure recovery

Integrate one existing Linux backend. Test clean reset, stale plans/epochs, worker loss, output pressure, and effect uncertainty without automatic replay.

7

Integrate harnesses and profile further passes

Add Hermes/DeepSeek and Harbor/Inspect adapters. Expand certified lowering, service actors, or plan-template reuse only where measurements justify it. Review terminals separately.

+
Engineering target

Dispatch p99 < 1 ms

Define payload, concurrency, load, queue policy, and authorization-to-start timestamps. Report durable completion separately.

Engineering target

Idle broker ≤ 16 MiB

Count arenas, queues, stacks, and metadata. Also report workers, pools, guests, and total cost per task; moving allocations elsewhere does not establish efficiency.

Correctness gate

Equal logical calls, outputs, errors, audit, limits, authority, and terminal effects. Test disabled/enabled passes and reject stale reads, unauthorized fan-out, duplicate effects, and state leaks.

Performance gate

Compare warm, persistent, pooled baselines under equal resources. Preserve fx's Linux startup budget through lazy service connection.

+
Disable a pass if effect declarations are unavailable or contradicted, or if planning, hashing, guards, and fan-out erase its gains. Reject improvements based on skipped logical actions, weaker isolation, changed outputs, or stale observations. Use the unchanged fallback only while its exact authority and applicable guards remain valid. Stale authority or generations require rejection or fresh host preparation/admission.
+

The prototype uses fixture-owned admissions and two logical agent identities in one process. Backing read counts refer to captured-content reader invocations, not OS reads or elapsed time. The native transport probe above is a separate experiment. No production fx integration or sandbox qualification has been performed.

+
+
+ +
+
12

The decisions
that constrain the build

Keep the first implementation small enough to compare against existing paths and explicit enough to recover from failure.

+
Retain

Effect-aware plan optimization

Optimize certified physical work while preserving logical identities, current authority, error behavior, audit, budgets, and receipts.

Reject

Universal tool-code compilation

Use bounded declared plans. Do not infer purity, replace shell semantics, or execute arbitrary model-authored code during planning.

Retain

Embedded native operations

The local probe shows IPC overhead on tiny reads. Existing fx operations can keep their direct execution path.

Retain

Optional persistent broker

Own cross-language workers and clients where batching or retained setup repays dispatch cost.

Reuse

Existing packaging and containment

Use OCI, backend SDKs, and qualified container or VM services. A new VMM is outside the initial scope.

Reject as default

MicroVM per tool call

Lifecycle, control-channel, filesystem, and memory costs belong at a task boundary unless measurements justify finer isolation.

Reject

Transparent syscall memoization

Arbitrary commands depend on mutable files, clocks, randomness, credentials, services, and process state.

Defer

Shared-memory transport

Ownership, crash reclamation, and tenant separation add cost. Profile socket copying and framing before adding it.

Reject

Automatic uncertain-effect retries

Restart infrastructure and expose uncertainty. Repeat an effect only under an explicit eligible retry or reconciliation contract.

Defer

Native fx terminal migration

Review ownership, input authority, recovery identity, cancellation, output drainage, and publication before moving terminals.

+
+ +
+
13

Sources & evidence

Primary references support the existing capabilities and constraints. The proposed architecture and engineering budgets are design judgments.

+
  1. DeepSeek Harness architecturePinned source review; provider and tool ownership.
  2. DeepSeek Node program executionFresh execution semantics and host bindings.
  3. Hermes code executionPersistent kernels and RPC ownership.
  4. Hermes kernelInterpreter state and lifecycle.
  5. OpenSandbox pool contractPrepared capacity, fresh ownership, leases, and fencing.
  6. OpenSandbox execution APIExisting execution and lifecycle integration surface.
  7. Codex app-server APIPublic commands, output, cancellation, and filesystem methods.
  8. MCP transportsJSON-RPC and custom bidirectional transport requirements.
  9. Erlang process efficiencyBEAM process and message behavior.
  10. Firecracker snapshot supportNetwork restoration and cloned-state constraints.
  11. Microsoft / Hugging Face ThinkingBox3 October 2026; repeated clean trials and terminal effects.
  12. Finding the Right FitModel–harness interaction research submitted 1 October 2026.
  13. Cognition Agent Memory RepoMemory specification; distinct from execution acceleration.
  14. Cognition devin-connectCustomer gateway and transport implementation.
  15. HarborExisting agent evaluation orchestration.
  16. Inspect sandbox extension interfacesExecution/file operations and sample lifecycle.
  17. Anthropic code execution with MCPProgrammatic calling and intermediate context handling.
  18. libcurl connection reuseConnection lifetime belongs to a retained client.
  19. PgBouncer pooling modesProtocol and session-state compatibility.
  20. MicrosandboxPortable VM candidate requiring qualification.
  21. Linux cgroup v2Containment cleanup and descendant memory accounting.
  22. fx source revisionReviewed ownership contracts and existing execution paths.
  23. MCP tool metadata and annotationsAnnotations require a trusted source; they do not prove deterministic behavior.
+
+
agent-cast · Effect-aware optimizer + actor runtime · Revision 02 · 6 October 2026Back to overview ↑
+
+ + + + + diff --git a/docs/agent-cast/events.jsonl b/docs/agent-cast/events.jsonl new file mode 100644 index 0000000000..5a7a587236 --- /dev/null +++ b/docs/agent-cast/events.jsonl @@ -0,0 +1,6 @@ +{"sequence": 1, "observed_at_utc": "2026-10-06T19:38:41.491825+00:00", "event": "program_created", "source": "user request to set milestones, ledger, model assignments and begin", "details": {"branch": "agent-cast-foundation", "scope": "M0/M1 initial foundation; later milestones remain planned"}} +{"sequence": 2, "observed_at_utc": "2026-10-06T19:38:41.491825+00:00", "event": "tasks_assigned", "details": {"implementation": {"task": "AC-0101", "model": "gpt-6.1-sol", "agent": "/root/agent_cast_core"}, "routine": {"task": "AC-0002", "model": "gpt-6-luna", "agent": "/root/agent_cast_ledger_tools"}, "review": {"task": "AC-0103", "model": "gpt-6-astra", "agent": "/root/agent_cast_contract_review"}, "input_tokens": null, "output_tokens": null, "usage_source": null}} +{"sequence": 3, "observed_at_utc": "2026-10-06T19:55:56.443491+00:00", "event": "compiler_repair_verified", "details": {"failure": "docs/agent-cast/evidence/initial-format-failure.json", "repair": "docs/agent-cast/evidence/compiler-repair.json", "reverification": "docs/agent-cast/evidence/foundation.json"}} +{"sequence": 4, "observed_at_utc": "2026-10-06T19:55:56.443491+00:00", "event": "review_finding_resolved", "details": {"task": "AC-0103", "evidence": "docs/agent-cast/evidence/contract-review.json", "finding": "Borrowed admission data must remain unchanged; snapshot owns captured bytes/path."}} +{"sequence": 5, "observed_at_utc": "2026-10-06T19:55:56.443491+00:00", "event": "local_verification_recorded", "details": {"tasks": ["AC-0001", "AC-0002", "AC-0003", "AC-0101", "AC-0102", "AC-0103", "AC-0104"], "zig_unit_tests": 18, "ledger_cli_tests": 10, "independent_actual_fixtures": 2, "logical_receipts_per_mode": 10, "backing_reader_calls": {"disabled": 8, "enabled": 4}, "performance_claim": null}} +{"sequence": 6, "observed_at_utc": "2026-10-06T19:55:56.443491+00:00", "event": "publication_gate_pending", "details": {"task": "AC-0105", "evidence": "docs/agent-cast/evidence/checkpoint.json", "reason": "Available GitHub account has READ access; no exact-commit CI result exists."}} diff --git a/docs/agent-cast/evidence/checkpoint.json b/docs/agent-cast/evidence/checkpoint.json new file mode 100644 index 0000000000..5c58762699 --- /dev/null +++ b/docs/agent-cast/evidence/checkpoint.json @@ -0,0 +1,27 @@ +{ + "observed_at_utc": "2026-10-06T19:55:56.443491+00:00", + "repository": "vercel-labs/fx", + "branch": "agent-cast-foundation", + "authenticated_account": "alvgeppetto-debug", + "viewer_permission": "READ", + "source": "gh repo view vercel-labs/fx --json nameWithOwner,isPrivate,viewerPermission", + "push": "not_attempted_without_repository_write_access", + "draft_pr": null, + "exact_commit_ci": "not_run", + "prototype_workflow": ".github/workflows/agent-cast.yml", + "required_product_checks": [ + "Build & Test", + "E2E (deterministic)", + "Shellcheck", + "Startup Latency", + "macOS arm64" + ], + "required_prototype_checks": [ + "Agent-cast (ubuntu-24.04)", + "Agent-cast (macos-15)" + ], + "ship_gate": "not_run", + "remaining_action": "Publish the local checkpoint using a contributor account with appropriate access, open a draft PR, and qualify its exact current commit.", + "checkpoint_ref": "agent-cast-foundation", + "checkpoint_policy": "Resolve the local branch HEAD after its checkpoint commit; verify the recorded source hashes and require CI on the exact current commit before promotion." +} diff --git a/docs/agent-cast/evidence/compiler-repair.json b/docs/agent-cast/evidence/compiler-repair.json new file mode 100644 index 0000000000..5bbcd926e9 --- /dev/null +++ b/docs/agent-cast/evidence/compiler-repair.json @@ -0,0 +1,47 @@ +[ + { + "phase": "format", + "argv": [ + "", + "fmt", + "src/", + "build.zig" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.03945279214531183, + "stdout": "", + "stderr": "" + }, + { + "phase": "build", + "argv": [ + "", + "build", + "-Doptimize=ReleaseSafe", + "-j1" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 9.568622666876763, + "stdout": "", + "stderr": "" + }, + { + "phase": "unit", + "argv": [ + "", + "build", + "test", + "-Doptimize=ReleaseSafe", + "-j1", + "--summary", + "all" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 4.928705625003204, + "stdout": "", + "stderr": "Build Summary: 3/3 steps succeeded; 18/18 tests passed\ntest success\n+- run test 18 pass (18 total) 4ms MaxRSS:2M\n +- compile test ReleaseSafe native success 4s MaxRSS:387M\n\n" + } +] diff --git a/docs/agent-cast/evidence/contract-review.json b/docs/agent-cast/evidence/contract-review.json new file mode 100644 index 0000000000..6d0cd7499a --- /dev/null +++ b/docs/agent-cast/evidence/contract-review.json @@ -0,0 +1,30 @@ +{ + "recorded_at_utc": "2026-10-06T19:55:06.387230+00:00", + "source": "Delegated review response in this session; coordinator checked the cited boundary and owned-capture implementation.", + "agent": "/root/agent_cast_contract_review", + "requested_model": "gpt-6-astra", + "reasoning_effort": "high", + "scope": "Exact binding, immutable grouping and borrowed-memory contracts in the bounded synchronous fixture.", + "finding": "Borrowed binding data must remain unchanged as well as alive. A const slice alone does not freeze mutable backing.", + "resolution": [ + "CallBinding and Plan document frozen borrowed backing through receipt consumption.", + "Snapshot duplicates bytes and path; identity points to stable owned hash storage.", + "Focused tests mutate original source/path buffers and verify captured bytes; mutation of captured bytes is detected before admission." + ], + "remaining_gaps": [ + "Host admissions are fixture assertions, not production permission tokens or current registry queries.", + "No expiry/revocation race, cancellation, subscriber lifetime, completed cache, broker or worker recovery is qualified.", + "No latency or harness efficiency claim." + ], + "source_sha256": { + "experiments/agent_cast/build.zig": "6de2342b938220052abb5f2aa6484a2d0b4989318f929f8cfc6cd5b71260d5b5", + "experiments/agent_cast/src/contracts.zig": "d11df3241d4d9c5441da4b85250d192fb0e890ff7bdd613368c917a71ca2470e", + "experiments/agent_cast/src/demo.zig": "1b085e57e0663722c0a15a7af8b65f14a2a3d5b377ccaf50e11589e6276fb032", + "experiments/agent_cast/src/executor.zig": "d7a24d288eaa054762c58462e10877c14b736b26ab9dab23afca56b0f24cf990", + "experiments/agent_cast/src/optimizer.zig": "e33266af767f303d2c3918a27d514afd1e62913aad495fdfacf6b42dcfa55a63", + "experiments/agent_cast/src/tests.zig": "4d80e3ea2d4d149f0fb9c09bd3f0c327f20995aac083ea51a5e4e89bd4cbd8ce" + }, + "input_tokens": null, + "output_tokens": null, + "usage_source": null +} diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/build.stderr b/docs/agent-cast/evidence/foundation-2026-10-06/build.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/build.stdout b/docs/agent-cast/evidence/foundation-2026-10-06/build.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/compare-0.stderr b/docs/agent-cast/evidence/foundation-2026-10-06/compare-0.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/compare-0.stdout b/docs/agent-cast/evidence/foundation-2026-10-06/compare-0.stdout new file mode 100644 index 0000000000..1c374cb305 --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/compare-0.stdout @@ -0,0 +1,267 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "compare", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": true, + "runs": [ + { + "mode": "disabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 8, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 4, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 5, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 6, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 7, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + }, + { + "mode": "enabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/compare-1.stderr b/docs/agent-cast/evidence/foundation-2026-10-06/compare-1.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/compare-1.stdout b/docs/agent-cast/evidence/foundation-2026-10-06/compare-1.stdout new file mode 100644 index 0000000000..91330d8055 --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/compare-1.stdout @@ -0,0 +1,267 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "compare", + "immutable_input_sha256": "3d55b51a98d198e5b5bf86d066c92f53c1349d4636253f2da34c573976850cf8", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": true, + "runs": [ + { + "mode": "disabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 8, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 4, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 5, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 6, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 7, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + }, + { + "mode": "enabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/disabled.stderr b/docs/agent-cast/evidence/foundation-2026-10-06/disabled.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/disabled.stdout b/docs/agent-cast/evidence/foundation-2026-10-06/disabled.stdout new file mode 100644 index 0000000000..deee42bd2d --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/disabled.stdout @@ -0,0 +1,139 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "disabled", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": null, + "runs": [ + { + "mode": "disabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 8, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 4, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 5, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 6, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 7, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/enabled.stderr b/docs/agent-cast/evidence/foundation-2026-10-06/enabled.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/enabled.stdout b/docs/agent-cast/evidence/foundation-2026-10-06/enabled.stdout new file mode 100644 index 0000000000..541d8a14fa --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/enabled.stdout @@ -0,0 +1,139 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "enabled", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": null, + "runs": [ + { + "mode": "enabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/fixture-0.bin b/docs/agent-cast/evidence/foundation-2026-10-06/fixture-0.bin new file mode 100644 index 0000000000..092edfcdaa --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/fixture-0.bin @@ -0,0 +1 @@ +fixture-0-window-00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfixture-0-window-01BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBfixture-0-window-02CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCfixture-0-window-03DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDfixture-0-window-04EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEfixture-0-window-05FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfixture-0-window-06GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGfixture-0-window-07HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHfixture-0-window-08IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIfixture-0-window-09JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJfixture-0-window-10KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKfixture-0-window-11LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLfixture-0-window-12MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMfixture-0-window-13NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNfixture-0-window-14OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOfixture-0-window-15PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP \ No newline at end of file diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/fixture-1.bin b/docs/agent-cast/evidence/foundation-2026-10-06/fixture-1.bin new file mode 100644 index 0000000000..7023653028 --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/fixture-1.bin @@ -0,0 +1 @@ +fixture-1-window-00BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBfixture-1-window-01CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCfixture-1-window-02DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDfixture-1-window-03EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEfixture-1-window-04FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfixture-1-window-05GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGfixture-1-window-06HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHfixture-1-window-07IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIfixture-1-window-08JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJfixture-1-window-09KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKfixture-1-window-10LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLfixture-1-window-11MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMfixture-1-window-12NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNfixture-1-window-13OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOfixture-1-window-14PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPfixture-1-window-15QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ \ No newline at end of file diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/format.stderr b/docs/agent-cast/evidence/foundation-2026-10-06/format.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/format.stdout b/docs/agent-cast/evidence/foundation-2026-10-06/format.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/help.stderr b/docs/agent-cast/evidence/foundation-2026-10-06/help.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/help.stdout b/docs/agent-cast/evidence/foundation-2026-10-06/help.stdout new file mode 100644 index 0000000000..c4d8831f20 --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/help.stdout @@ -0,0 +1,10 @@ +Usage: agent-cast-demo --fixture PATH [--mode compare|enabled|disabled] + +Capture one file of 256 bytes to 1 MiB and exercise an experimental embedded +immutable content reader. Compare mode checks every logical receipt and prints +JSON with actual physical read counts. This is a correctness demonstration. +Host admissions are fixture contracts, with no production permission checks. + + --fixture PATH File to capture once into owned immutable storage + --mode MODE Select execution mode (default: compare) + --help Show this help diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/qualification.json b/docs/agent-cast/evidence/foundation-2026-10-06/qualification.json new file mode 100644 index 0000000000..2cf61e5683 --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/qualification.json @@ -0,0 +1,171 @@ +{ + "schema_version": 1, + "recorded_at_utc": "2026-10-06T19:46:12.090279+00:00", + "scope": "local foundation correctness; logical agent identities in one process", + "platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O", + "machine": "arm64", + "source_sha256": { + "experiments/agent_cast/build.zig": "6de2342b938220052abb5f2aa6484a2d0b4989318f929f8cfc6cd5b71260d5b5", + "experiments/agent_cast/src/contracts.zig": "d11df3241d4d9c5441da4b85250d192fb0e890ff7bdd613368c917a71ca2470e", + "experiments/agent_cast/src/demo.zig": "1b085e57e0663722c0a15a7af8b65f14a2a3d5b377ccaf50e11589e6276fb032", + "experiments/agent_cast/src/executor.zig": "d7a24d288eaa054762c58462e10877c14b736b26ab9dab23afca56b0f24cf990", + "experiments/agent_cast/src/optimizer.zig": "e33266af767f303d2c3918a27d514afd1e62913aad495fdfacf6b42dcfa55a63", + "experiments/agent_cast/src/tests.zig": "4d80e3ea2d4d149f0fb9c09bd3f0c327f20995aac083ea51a5e4e89bd4cbd8ce" + }, + "checks": [ + { + "name": "zig-version", + "argv": [ + "", + "version" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.03602166613563895, + "stdout": "zig-version.stdout", + "stderr": "zig-version.stderr" + }, + { + "name": "format", + "argv": [ + "", + "fmt", + "--check", + "src/", + "build.zig" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.035616999957710505, + "stdout": "format.stdout", + "stderr": "format.stderr" + }, + { + "name": "build", + "argv": [ + "", + "build", + "-Doptimize=ReleaseSafe", + "-j1" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 5.185604041907936, + "stdout": "build.stdout", + "stderr": "build.stderr" + }, + { + "name": "unit", + "argv": [ + "", + "build", + "test", + "-Doptimize=ReleaseSafe", + "-j1", + "--summary", + "all" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 4.926578124985099, + "stdout": "unit.stdout", + "stderr": "unit.stderr" + }, + { + "name": "help", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--help" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.003019292140379548, + "stdout": "help.stdout", + "stderr": "help.stderr" + }, + { + "name": "compare-0", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-0.bin", + "--mode", + "compare" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.002790084108710289, + "stdout": "compare-0.stdout", + "stderr": "compare-0.stderr" + }, + { + "name": "compare-1", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-1.bin", + "--mode", + "compare" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.002785374876111746, + "stdout": "compare-1.stdout", + "stderr": "compare-1.stderr" + }, + { + "name": "enabled", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-0.bin", + "--mode", + "enabled" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0026113330386579037, + "stdout": "enabled.stdout", + "stderr": "enabled.stderr" + }, + { + "name": "disabled", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-0.bin", + "--mode", + "disabled" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.002653209026902914, + "stdout": "disabled.stdout", + "stderr": "disabled.stderr" + } + ], + "ci_status": "not_run", + "performance_claim": null, + "ok": true, + "zig_version": "0.16.0", + "binary_sha256": "7a0f8aaf270097d069878f2f9cce0493a0cfb06ddbbbf6aca73fdc08e3a82291", + "fixtures": [ + { + "sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "bytes": 1024, + "comparison": "compare-0.stdout", + "logical_receipts_per_mode": 10, + "disabled_backing_reads": 8, + "enabled_backing_reads": 4 + }, + { + "sha256": "3d55b51a98d198e5b5bf86d066c92f53c1349d4636253f2da34c573976850cf8", + "bytes": 1024, + "comparison": "compare-1.stdout", + "logical_receipts_per_mode": 10, + "disabled_backing_reads": 8, + "enabled_backing_reads": 4 + } + ], + "path_normalization": "Machine-specific checkout, Zig executable, and receipt-directory prefixes replaced with declared placeholders; exits, logs, hashes, timings and outputs unchanged." +} diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/unit.stderr b/docs/agent-cast/evidence/foundation-2026-10-06/unit.stderr new file mode 100644 index 0000000000..55b9ec692e --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/unit.stderr @@ -0,0 +1,5 @@ +Build Summary: 3/3 steps succeeded; 18/18 tests passed +test success ++- run test 18 pass (18 total) 4ms MaxRSS:2M + +- compile test ReleaseSafe native success 4s MaxRSS:384M + diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/unit.stdout b/docs/agent-cast/evidence/foundation-2026-10-06/unit.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/zig-version.stderr b/docs/agent-cast/evidence/foundation-2026-10-06/zig-version.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/foundation-2026-10-06/zig-version.stdout b/docs/agent-cast/evidence/foundation-2026-10-06/zig-version.stdout new file mode 100644 index 0000000000..04a373efe6 --- /dev/null +++ b/docs/agent-cast/evidence/foundation-2026-10-06/zig-version.stdout @@ -0,0 +1 @@ +0.16.0 diff --git a/docs/agent-cast/evidence/foundation.json b/docs/agent-cast/evidence/foundation.json new file mode 100644 index 0000000000..4e5652ff36 --- /dev/null +++ b/docs/agent-cast/evidence/foundation.json @@ -0,0 +1,21 @@ +{ + "recorded_at_utc": "2026-10-06T19:55:06.387230+00:00", + "status": "verified_local", + "qualification_receipt": "foundation-2026-10-06/qualification.json", + "zig_version": "0.16.0", + "platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O", + "unit_tests_passed": 18, + "fixture_comparisons_passed": 2, + "logical_receipts_per_mode": 10, + "disabled_backing_reader_calls": 8, + "enabled_backing_reader_calls": 4, + "actual_multi_process_agents": false, + "built_cli_stderr_clean": true, + "initial_failure_retained": "initial-format-failure.json", + "repair_receipt": "compiler-repair.json", + "qualifier_sha256": "cf2a1ecc20af226f0c57127ccd10a967b9b94cea34cdf40d88d4870e054c08df", + "binary_sha256": "7a0f8aaf270097d069878f2f9cce0493a0cfb06ddbbbf6aca73fdc08e3a82291", + "scope": "Owned captured bytes and fixture-certified snapshot calls inside one process; reader call counts are not OS calls or measured speedups.", + "ci_status": "not_run", + "performance_claim": null +} diff --git a/docs/agent-cast/evidence/initial-format-failure.json b/docs/agent-cast/evidence/initial-format-failure.json new file mode 100644 index 0000000000..f538d95944 --- /dev/null +++ b/docs/agent-cast/evidence/initial-format-failure.json @@ -0,0 +1,16 @@ +[ + { + "phase": "format", + "argv": [ + "", + "fmt", + "src/", + "build.zig" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 1, + "seconds": 0.040125666884705424, + "stdout": "", + "stderr": "src/contracts.zig:8:11: error: expected '{', found ','\n opaque,\n ^\nsrc/executor.zig:216:35: error: expected expression, found '.'\n 3 => request.effect = .opaque,\n ^\nsrc/optimizer.zig:265:122: error: expected pointer dereference, optional unwrap, or field access, found 'opaque'\n inline for (.{ contracts.Effect.mutable_read, contracts.Effect.mutation, contracts.Effect.external, contracts.Effect.opaque }) |effect| {\n ^~~~~~\n" + } +] diff --git a/docs/agent-cast/evidence/ledger-tests.stderr b/docs/agent-cast/evidence/ledger-tests.stderr new file mode 100644 index 0000000000..2f980b4b6f --- /dev/null +++ b/docs/agent-cast/evidence/ledger-tests.stderr @@ -0,0 +1,15 @@ +test_bool_is_not_a_schema_version_or_token_count (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_bool_is_not_a_schema_version_or_token_count) ... ok +test_canonical_ledger_validates_without_mutation (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_canonical_ledger_validates_without_mutation) ... ok +test_corrupt_field_types_return_diagnostics (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_corrupt_field_types_return_diagnostics) ... ok +test_cycles_and_missing_dependencies_are_rejected (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_cycles_and_missing_dependencies_are_rejected) ... ok +test_duplicate_ids_are_rejected (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_duplicate_ids_are_rejected) ... ok +test_invalid_json_returns_structured_failure (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_invalid_json_returns_structured_failure) ... ok +test_next_preserves_ownership_and_dependency_gate (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_next_preserves_ownership_and_dependency_gate) ... ok +test_show_retains_full_task_records (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_show_retains_full_task_records) ... ok +test_usage_requires_a_receipt_reference (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_usage_requires_a_receipt_reference) ... ok +test_verified_status_requires_evidence_and_done_dependencies (scripts.tests.test_agent_cast_ledger.LedgerCliTests.test_verified_status_requires_evidence_and_done_dependencies) ... ok + +---------------------------------------------------------------------- +Ran 10 tests in 0.809s + +OK diff --git a/docs/agent-cast/evidence/ledger-tests.stdout b/docs/agent-cast/evidence/ledger-tests.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/ledger.json b/docs/agent-cast/evidence/ledger.json new file mode 100644 index 0000000000..d9e86e0868 --- /dev/null +++ b/docs/agent-cast/evidence/ledger.json @@ -0,0 +1,19 @@ +{ + "recorded_at_utc": "2026-10-06T19:55:06.387230+00:00", + "argv": [ + "python3", + "-m", + "unittest", + "scripts.tests.test_agent_cast_ledger", + "-v" + ], + "exit_code": 0, + "test_count": 10, + "stdout": "ledger-tests.stdout", + "stderr": "ledger-tests.stderr", + "source_sha256": { + "scripts/agent_cast_ledger.py": "6caebf0bed9b64d1e7bbb902338c5682cad06ceb662b65cd9b71cc426d1a1689", + "scripts/tests/test_agent_cast_ledger.py": "9fb9c0bf78a730f1bf229e2bc0c9f1f38e31c6bdbe8d2c85abccfa1e6e5a4fd2" + }, + "scope": "CLI consistency, malformed input and dependency/status gates; no live CI or provider billing verification" +} diff --git a/docs/agent-cast/evidence/toolchain.json b/docs/agent-cast/evidence/toolchain.json new file mode 100644 index 0000000000..7afa56da78 --- /dev/null +++ b/docs/agent-cast/evidence/toolchain.json @@ -0,0 +1,12 @@ +{ + "version": "0.16.0", + "target": "aarch64-macos", + "source": "https://ziglang.org/download/0.16.0/zig-aarch64-macos-0.16.0.tar.xz", + "archive_bytes": 52238004, + "sha256": "b23d70deaa879b5c2d486ed3316f7eaa53e84acf6fc9cc747de152450d401489", + "published_sha256": "b23d70deaa879b5c2d486ed3316f7eaa53e84acf6fc9cc747de152450d401489", + "extracted_bytes": 370614961, + "executable": "", + "version_stdout": "0.16.0", + "exit_code": 0 +} diff --git a/docs/agent-cast/milestones.md b/docs/agent-cast/milestones.md new file mode 100644 index 0000000000..4252482c95 --- /dev/null +++ b/docs/agent-cast/milestones.md @@ -0,0 +1,132 @@ +# agent-cast implementation program + +Build an effect-aware execution optimizer backed by actor-owned resources. Keep +logical tool calls, current authority, results, and effect evidence intact while +reducing measured physical work. + +The initial implementation lives in `experiments/agent_cast/`. The design is in +[the HTML explainer](../agent-cast-architecture.html). The first slice is a +standalone correctness experiment; production permission integration, a broker, +worker supervision, shared completed-value caching, and sandbox isolation are +later milestones. + +## Canonical ledger + +[tasks.json](tasks.json) is the source of task status. Each task has a stable ID, +milestone, dependencies, assigned role/model, owned file scope, acceptance +criteria, evidence references, and usage fields. + +[events.jsonl](events.jsonl) records observed assignments and verification events. +The coordinator owns ledger changes. Workers return artifacts and findings; +they do not mark their own work ready. Unknown token counts remain `null` until +a provider receipt supplies them. + +Run these commands from the fx root: + +```sh +PYTHONDONTWRITEBYTECODE=1 python3 scripts/agent_cast_ledger.py validate +PYTHONDONTWRITEBYTECODE=1 python3 scripts/agent_cast_ledger.py next --json +PYTHONDONTWRITEBYTECODE=1 python3 scripts/agent_cast_ledger.py show +``` + +The checker validates structure, dependencies, status requirements, and usage +provenance. It does not verify live CI or establish that evidence is true. The +coordinator reads the referenced receipts and confirms the gate. + +## Milestones and exit gates + +| ID | Deliverable | Exit gate | +| --- | --- | --- | +| M0 | Program setup | Ledger and dependency graph validate; pinned Zig 0.16.0 runs; scoped model assignments and verification rules are recorded | +| M1 | Typed planner and embedded reader | Known immutable capture; exact fixture bindings; enabled/disabled byte parity; denied/stale exclusion; focused tests and actual built-binary receipts | +| M2 | Shared observation cache | Owned bounded cache; current admission on every hit; two-agent reuse in a compatible domain; isolation, eviction, and result lifetime tests | +| M3 | Broker and language clients | Bounded framed transport; actual Python/Node clients; independent logical identities, deadlines, cancellation, and credits | +| M4 | Supervision and recovery | Epoch replacement, stale-handle rejection, bounded cleanup, fault injection, and explicit uncertain effects without blind retries | +| M5 | libfx integration | A fixture provider drives a real host-tool interaction; existing authority, cancellation, output, and publication rules survive | +| M6 | Isolated eval tasks | One existing Linux backend; independent clean task state; verified process drain and reset; grader outside the agent environment | +| M7 | Service owners and cost model | Scoped pooled HTTP clients execute required requests; credential/configuration drain; complete cost attribution; valid direct fallback | +| M8 | Qualification and release decision | Equivalent work and outcomes against optimized baselines; full resource accounting; repeated trials; exact-commit CI and ship gates | + +M5 and M7 can progress from the same verified broker foundation. Isolation +qualification depends on recovery and integration. Performance claims wait for +M8's meaningful comparisons. + +## Status rules + +| Status | Meaning | +| --- | --- | +| `planned` | Scope and acceptance criteria exist; work has not started | +| `in_progress` | An assigned worker owns the task's file scope | +| `implemented` | Artifacts exist; required verification is pending | +| `verified_local` | Referenced local checks and the applicable real interaction succeeded | +| `ci_pending` | Local proof exists; exact-commit CI or the relevant qualification remains pending | +| `done` | Every applicable local, review, CI, and ship gate is supported by evidence | +| `blocked` | A concrete dependency or external condition prevents the task | + +`next` treats `verified_local` dependencies as available for development. That +does not make their artifacts ship-ready. A `done` task requires completed +dependencies, and a shipping task requires CI for the exact current commit. + +## First implementation slice + +The M1 planner preserves ten logical calls in its demonstration: eight permitted +reads with four unique windows, one denied request, and one stale-authority +request. Two logical agent identities request the same certified captured +content. Enabled grouping can share backing reader invocations while keeping +individual receipts. + +This is one embedded process, not two network clients. Host admissions are +fixture-owned assertions, not production capabilities. Immutable content is +captured into owned storage; arbitrary tools and commands are unsupported. + +The qualification runner uses two different actual fixtures and checks result +bytes independently. It also runs the built binary's help, enabled, disabled, +and compare paths, and records source/binary hashes, exit results, and stderr. +Planning and read-count differences do not establish a latency speedup. + +## Work allocation and handoffs + +Use [model-routing.md](model-routing.md) for task selection. A task brief names +its ID, allowed files, dependency artifacts, required invariants, verification +command, and repair limit. Pass a compact brief and relevant files instead of +the full conversation. + +Only one worker mutates a file scope. The coordinator owns integration, +resource staging, receipts, task transitions, and checkpoints. Run one heavy +build or benchmark at a time; keep fixture and output bounds explicit. + +At a checkpoint, update the ledger and event log with observed results. Retain +failed receipts. After a repair, rerun proof that depended on the broken path. +Leave later milestones planned until their own acceptance criteria are met. + +## Shipping gate + +Local verification is distinct from publication and readiness. Follow the fx +repository's checkpoint, draft PR, exact-commit CI, macOS arm64, and ship-gate +requirements when promoting changes. A standalone prototype gate must also run +the prototype itself; an unchanged fx smoke test cannot qualify its behavior. + +Read the evidence references in the ledger for the current gate state. Do not +infer readiness from a successful model response, nonempty receipt path, or old +CI result. + +## Current checkpoint + +M0 and the bounded M1 slice have local evidence in +[evidence/foundation.json](evidence/foundation.json) and +[evidence/ledger.json](evidence/ledger.json). The qualification observed 18 Zig +tests, 10 ledger CLI tests, and two different actual fixtures. Each comparison +retained ten logical receipts while using eight or four captured-content reader +invocations. This is correctness evidence, with no measured speedup claim. + +The available GitHub account has read access to `vercel-labs/fx`. +[evidence/checkpoint.json](evidence/checkpoint.json) records the publication and +CI gate. The added `Agent-cast Foundation` workflow builds and exercises this +prototype on Linux and macOS when a permitted contributor publishes it. Its +checks supplement the existing fx product checks. + +The next task is `AC-0201`: owned bounded completed-value caching. Verify a new +logical admission before every hit, keep task/domain/snapshot/contract boundaries +in the key, and prove eviction plus output lifetime. Inflight cancellation and +actual multi-client sharing follow as separate tasks. Refine later milestone +scopes into concrete file ownership before dispatching them. diff --git a/docs/agent-cast/model-routing.md b/docs/agent-cast/model-routing.md new file mode 100644 index 0000000000..0195ec0782 --- /dev/null +++ b/docs/agent-cast/model-routing.md @@ -0,0 +1,59 @@ +# Model routing for agent-cast work + +Route bounded implementation tasks to a workhorse model, routine tasks to a +smaller model, and consequential design review to a stronger model. Evaluate +the result through task acceptance gates; the model's confidence is not a gate. + +The following models are exposed by this session's collaboration tools. +Official OpenAI documentation checked on 6 October 2026 describes their roles. +These assignments are workload choices, not measured savings or a guarantee of +account-specific billing. + +| Work | Assigned model | Initial effort | Escalation | +| --- | --- | --- | --- | +| Ledger checks, bounded schema changes, source inventories, fixture plumbing | `gpt-6-luna` | `low` or `medium` | Sol if the bounded task fails its contract after a repair | +| Typed contracts, cache implementation, adapters, runtime integration | `gpt-6.1-sol` | `medium`; `high` for ownership and concurrency | Astra for unresolved correctness or architectural tradeoffs | +| Exact authority, effect equivalence, recovery, uncertain mutations, milestone review | `gpt-6-astra` | `high` | Coordinator resolves findings with concrete receipts | +| Integration and operator handoff in this thread | Current session | Inherited | Delegate independent bounded work rather than replaying the thread | + +[GPT-6 Luna](https://developers.openai.com/api/docs/models/gpt-6-luna) is described +as an efficient model for focused, high-volume work. +[GPT-6.1 Sol](https://developers.openai.com/api/docs/models/gpt-6.1-sol) is positioned +for complex work at lower cost than Astra. Use +[GPT-6 Astra](https://developers.openai.com/api/docs/models/gpt-6-astra) for demanding +reasoning and coding review. Compare quality and cost on these tasks rather +than assuming a universal ranking. + +## Token and cost controls + +1. Start delegated workers with a short task brief and no full-history fork. +2. Give each worker an explicit source allowlist and one owned file scope. +3. Reuse verified findings; do not repeat the landscape research for each task. +4. Limit a scope to two repair rounds before coordinator review and reassignment. +5. Return changed files, exact check results, unresolved issues, and ownership + requirements in the handoff. +6. Run deterministic tooling for formatting, validation, and fixtures. Use model + review for the decisions those checks cannot establish. +7. Keep actual usage fields `null` when collaboration tools expose no token + receipts. Never estimate them from file size, elapsed time, or prose length. + +Smaller briefs reduce token volume. Model selection changes the expected cost +of that work; it does not itself reduce the number of tokens. Track retries and +accepted task outcomes when comparing routing policies. + +The session currently has no user-specified spending ceiling. The policy uses +bounded task batches. A hard budget needs provider usage receipts and an +enforceable accounting boundary; this ledger alone does not supply either. + +## Assignment receipt + +The initial delegation used: + +| Worker | Task | Requested model | +| --- | --- | --- | +| `agent_cast_ledger_tools` | Read-only ledger checker and bounded validation repair | `gpt-6-luna`, `medium` | +| `agent_cast_core` | Planner contracts, immutable executor, CLI, and compiler repair | `gpt-6.1-sol`, `high` | +| `agent_cast_contract_review` | Exact binding, ownership, and equivalence review | `gpt-6-astra`, `high` | + +These are observed assignment settings. Provider token counts and actual billed +cost were not returned by the collaboration interface and remain unknown. diff --git a/docs/agent-cast/tasks.json b/docs/agent-cast/tasks.json new file mode 100644 index 0000000000..f4f3f505a2 --- /dev/null +++ b/docs/agent-cast/tasks.json @@ -0,0 +1,673 @@ +{ + "schema_version": 1, + "project": { + "name": "agent-cast", + "objective": "Build an effect-aware execution optimizer backed by actor-owned resources; preserve logical authority and effects while reducing measured physical work.", + "location": "experiments/agent_cast/", + "design": "docs/agent-cast-architecture.html", + "scope": "Foundation first; no production permissions, broker, isolation or performance claim yet." + }, + "last_updated_utc": "2026-10-06T19:55:56.443491+00:00", + "routing_policy": { + "routine_model": "gpt-6-luna", + "implementation_model": "gpt-6.1-sol", + "critical_review_model": "gpt-6-astra", + "context": "Short task briefs and explicit source allowlists; no full-history forks", + "repair_policy": "At most two repair rounds per delegated scope before parent review", + "spending_limit": null, + "usage_policy": "Null means unavailable; actual token counts require a provider receipt. No inferred totals.", + "current_batch": "M0/M1 locally verified; M2 queued; publication/CI pending" + }, + "milestones": [ + { + "id": "M0", + "title": "Program setup and reproducible toolchain", + "depends_on": [], + "acceptance": [ + "Canonical task ledger validates; milestone/task IDs and dependencies are stable", + "Task-scoped Zig 0.16.0 matches repository CI and published archive SHA256", + "Compact model assignments, file ownership, and verification gates recorded" + ] + }, + { + "id": "M1", + "title": "Typed planner and embedded immutable-reader proof", + "depends_on": [ + "M0" + ], + "acceptance": [ + "Per-logical identity and exact fixture admissions survive optimized grouping", + "Real bytes are captured once; enabled and disabled modes return equal full outputs", + "Denied/stale calls receive no data; unsupported execution is rejected", + "Build, focused tests, and built prototype interaction have successful receipts; CI remains separately gated" + ] + }, + { + "id": "M2", + "title": "Bounded shared observation cache", + "depends_on": [ + "M1" + ], + "acceptance": [ + "Successful certified immutable values are reusable by separately admitted agents in one compatible task/domain", + "Different task/domain/view/contract cannot reuse a result; current authority checked on hits", + "Owned cache memory, entry/byte limits, eviction, and truthful provenance verified" + ] + }, + { + "id": "M3", + "title": "Broker, Python and Node clients", + "depends_on": [ + "M2" + ], + "acceptance": [ + "Framed control/data, negotiated limits, logical IDs and backing work IDs interoperable", + "Two actual clients share eligible values; deadlines, cancellation and byte credits are independent", + "Exact host authority and resource generations rechecked before dispatch" + ] + }, + { + "id": "M4", + "title": "Supervised workers and recovery", + "depends_on": [ + "M3" + ], + "acceptance": [ + "Worker loss replaces epoch, invalidates old handles, and retains settled/unknown receipts", + "Queued versus running versus committed cancellation classifications survive faults", + "Unknown effects are reconciled rather than automatically repeated; restart and memory budgets enforced" + ] + }, + { + "id": "M5", + "title": "libfx and harness integration", + "depends_on": [ + "M3", + "M4" + ], + "acceptance": [ + "A deterministic provider fixture drives an actual libfx tool interaction and result publication", + "Schemas/outputs, host authority, output-drain and SDK cancellation semantics preserved", + "Native terminal/file-mutation migration remains separately reviewed; exact-commit product CI gates pass" + ] + }, + { + "id": "M6", + "title": "Clean isolated eval tasks", + "depends_on": [ + "M4", + "M5" + ], + "acceptance": [ + "One existing Linux backend creates independent state from pinned images/fixtures", + "Full containment cleanup, routes/credentials revocation, reset probes and quarantine verified", + "Independent terminal-state grading detects required, missing and extra effects" + ] + }, + { + "id": "M7", + "title": "Scoped service clients and measured strategy", + "depends_on": [ + "M3", + "M4" + ], + "acceptance": [ + "Pooled HTTP requests still execute every required application request", + "Pool partitions and credential/config generation changes are tested; database work requires its own reset contract", + "Cost model includes planning, guards, hashing, fan-out, useful work, output and persistence; valid native fallback tested" + ] + }, + { + "id": "M8", + "title": "Comparative qualification and release decision", + "depends_on": [ + "M5", + "M6", + "M7" + ], + "acceptance": [ + "Compare exact fixed traces and full repeated trials against optimized native/persistent/warm baselines", + "Equal results/errors/audit/authority/effects under equal complete resource budgets", + "Publish only measured claims and require exact-commit CI plus local happy path and ship gate" + ] + } + ], + "tasks": [ + { + "id": "AC-0001", + "milestone": "M0", + "title": "Freeze scope, milestones and model-routing policy", + "status": "verified_local", + "depends_on": [], + "owner": { + "role": "coordinator", + "model": "current-session", + "reasoning_effort": "inherited" + }, + "scope": [ + "docs/agent-cast/tasks.json", + "docs/agent-cast/milestones.md", + "docs/agent-cast/model-routing.md" + ], + "acceptance": [ + "Milestones have gates and dependencies; each task has a bounded owner and scope", + "Model assignments agree with available tools; unknown usage remains null" + ], + "evidence": [ + "docs/agent-cast/milestones.md", + "docs/agent-cast/model-routing.md", + "docs/agent-cast/evidence/ledger.json" + ], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + }, + { + "id": "AC-0002", + "milestone": "M0", + "title": "Implement and validate the read-only task-ledger CLI", + "status": "verified_local", + "depends_on": [], + "owner": { + "role": "routine tooling", + "model": "gpt-6-luna", + "reasoning_effort": "medium", + "agent": "/root/agent_cast_ledger_tools" + }, + "scope": [ + "scripts/agent_cast_ledger.py", + "scripts/tests/test_agent_cast_ledger.py" + ], + "acceptance": [ + "Actual ledger validates; next/show preserve status and ownership", + "Malformed fields, cycles, missing dependencies, and unproven completion fail with useful errors", + "Checker does not present ledger status as live CI or ship verification" + ], + "evidence": [ + "docs/agent-cast/evidence/ledger.json" + ], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:55:56.443491+00:00", + "integration_note": "Luna authored the checker and one bounded repair; coordinator added CLI tests and repaired malformed milestone/schema-version handling." + }, + { + "id": "AC-0003", + "milestone": "M0", + "title": "Provision pinned toolchain and record receipt", + "status": "verified_local", + "depends_on": [], + "owner": { + "role": "build owner", + "model": "current-session", + "reasoning_effort": "inherited" + }, + "scope": [ + "task-scoped temporary toolchain", + "docs/agent-cast/evidence/toolchain.json" + ], + "acceptance": [ + "Zig 0.16.0 binary runs; archive hash equals published SHA256", + "No global toolchain/environment changes; one build job at a time" + ], + "evidence": [ + "docs/agent-cast/evidence/toolchain.json" + ], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + }, + { + "id": "AC-0101", + "milestone": "M1", + "title": "Implement typed exact-binding and immutable grouping contracts", + "status": "verified_local", + "depends_on": [ + "AC-0003" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "high", + "agent": "/root/agent_cast_core" + }, + "scope": [ + "experiments/agent_cast/src/contracts.zig", + "experiments/agent_cast/src/optimizer.zig" + ], + "acceptance": [ + "Preserve every logical call and exclude denied/stale/mismatched consumers", + "Share only compatible certified immutable reads; different domains/tasks/options/contracts remain separate", + "Borrowed data remains alive and immutable; bounded allocation and duplicate-ID rejection tested" + ], + "evidence": [ + "docs/agent-cast/evidence/contract-review.json", + "docs/agent-cast/evidence/foundation.json" + ], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + }, + { + "id": "AC-0102", + "milestone": "M1", + "title": "Implement frozen-content executor and actual demo binary", + "status": "verified_local", + "depends_on": [ + "AC-0101" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "high", + "agent": "/root/agent_cast_core" + }, + "scope": [ + "experiments/agent_cast/src/executor.zig", + "experiments/agent_cast/src/demo.zig", + "experiments/agent_cast/build.zig", + "experiments/agent_cast/src/tests.zig" + ], + "acceptance": [ + "Capture real file bytes once, execute known reader only, and preserve full logical outputs", + "Enabled/disabled parity includes denied/stale consumers, window bounds, contract and output-budget checks", + "Built demo uses input identity and actual executor counts; labels two logical agents without claiming multi-process execution" + ], + "evidence": [ + "docs/agent-cast/evidence/foundation.json" + ], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + }, + { + "id": "AC-0103", + "milestone": "M1", + "title": "Review authority, ownership and equivalence boundaries", + "status": "verified_local", + "depends_on": [ + "AC-0101" + ], + "owner": { + "role": "critical review", + "model": "gpt-6-astra", + "reasoning_effort": "high", + "agent": "/root/agent_cast_contract_review" + }, + "scope": [ + "experiments/agent_cast/src/contracts.zig", + "experiments/agent_cast/src/optimizer.zig", + "experiments/agent_cast/src/executor.zig" + ], + "acceptance": [ + "Review exact-binding fields, denied consumers, immutable proof and mutable borrowed backing", + "Fix material findings before local proof; record remaining production gaps" + ], + "evidence": [ + "docs/agent-cast/evidence/contract-review.json", + "docs/agent-cast/evidence/foundation.json" + ], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + }, + { + "id": "AC-0104", + "milestone": "M1", + "title": "Run focused tests and real enabled/disabled interactions", + "status": "verified_local", + "depends_on": [ + "AC-0102", + "AC-0103" + ], + "owner": { + "role": "verification", + "model": "current-session", + "reasoning_effort": "inherited" + }, + "scope": [ + "experiments/agent_cast/zig-out/bin/agent-cast-demo", + "docs/agent-cast/evidence/foundation.json", + "scripts/qualify_agent_cast.py", + "experiments/agent_cast/README.md" + ], + "acceptance": [ + "Format/build/focused tests pass under pinned toolchain", + "Personally run built binary against actual fixture and compare full result hashes and counts", + "Stderr clean, no abort; source/binary hashes and exit receipts retained; no speed claim" + ], + "evidence": [ + "docs/agent-cast/evidence/foundation.json", + "docs/agent-cast/evidence/foundation-2026-10-06/qualification.json" + ], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + }, + { + "id": "AC-0105", + "milestone": "M1", + "title": "Prepare checkpoint and exact-commit CI qualification", + "status": "ci_pending", + "depends_on": [ + "AC-0104" + ], + "owner": { + "role": "coordinator integration", + "model": "current-session", + "reasoning_effort": "inherited" + }, + "scope": [ + "feature branch checkpoint", + "draft pull request", + "CI qualification configuration" + ], + "acceptance": [ + "Focused proof and built happy path precede any external publication", + "Require exact current commit CI including macOS and prototype qualification before any ready claim", + "Record absent publication authorization or unavailable checks; do not invent a successful gate" + ], + "evidence": [ + "docs/agent-cast/evidence/checkpoint.json" + ], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:55:56.443491+00:00", + "pending_reason": "Local CI configuration exists; available GitHub account has READ permission. Exact-commit publication and checks are pending." + }, + { + "id": "AC-0201", + "milestone": "M2", + "title": "Add owned bounded completed-value cache", + "status": "planned", + "depends_on": [ + "AC-0104" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "high" + }, + "scope": [ + "experiments/agent_cast/src/cache.zig", + "cache-focused tests" + ], + "acceptance": [ + "Cache successful immutable canonical values after each current logical admission", + "Prove two-agent sharing, domain/task isolation, invalidation, eviction and output lifetime" + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + }, + { + "id": "AC-0202", + "milestone": "M2", + "title": "Qualify inflight sharing and independent consumers", + "status": "planned", + "depends_on": [ + "AC-0201" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "high" + }, + "scope": [ + "cache/subscriber contracts", + "cancellation and deadline tests" + ], + "acceptance": [ + "Cancel one subscriber without stopping work required by another", + "Keep independent deadlines/credits/audit/retry provenance; reject stale result delivery" + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + }, + { + "id": "AC-0301", + "milestone": "M3", + "title": "Implement framed local broker and scoped enrollment", + "status": "planned", + "depends_on": [ + "AC-0202" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "high" + }, + "scope": [ + "experiments/agent_cast/src/broker.zig", + "wire/enrollment contracts" + ], + "acceptance": [ + "Bounds, framing, epochs, roles, authority and reconnect semantics tested", + "Two actual clients exercise shared state; same-UID process is not advertised as containment" + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + }, + { + "id": "AC-0302", + "milestone": "M3", + "title": "Add Python and Node adapters", + "status": "planned", + "depends_on": [ + "AC-0301" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "medium" + }, + "scope": [ + "experimental Python/Node clients", + "adapter integration fixtures" + ], + "acceptance": [ + "Identical operation contract and ordinary results across both languages", + "Credits, interrupt, reconnect/status and cleanup work against real broker" + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + }, + { + "id": "AC-0401", + "milestone": "M4", + "title": "Supervise isolated worker lifetimes and uncertainty", + "status": "planned", + "depends_on": [ + "AC-0301" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "high" + }, + "scope": [ + "worker supervisor", + "receipt/reconciliation contracts", + "fault fixtures" + ], + "acceptance": [ + "Inject pre-start, inflight, after-effect and before-publication failures", + "No duplicated uncertain mutation; bounded restart, drain and teardown" + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + }, + { + "id": "AC-0501", + "milestone": "M5", + "title": "Connect a fixture-driven libfx host tool", + "status": "planned", + "depends_on": [ + "AC-0302", + "AC-0401" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "high" + }, + "scope": [ + "SDK experimental adapter", + "fixture provider", + "existing host callback contract" + ], + "acceptance": [ + "Actual tool call travels libfx to broker and returns its result", + "Preserve host authority/cancellation and document absent stable SDK recovery identity" + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + }, + { + "id": "AC-0601", + "milestone": "M6", + "title": "Qualify one existing Linux isolation backend", + "status": "planned", + "depends_on": [ + "AC-0401", + "AC-0501" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "high" + }, + "scope": [ + "existing backend adapter", + "reset and independent grader fixtures" + ], + "acceptance": [ + "Fresh workspace/worker/backend state per trial; cleanup prevents surviving writers", + "Grader stays outside execution service; failed resets quarantined" + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + }, + { + "id": "AC-0701", + "milestone": "M7", + "title": "Add scoped pooled HTTP owner and cost attribution", + "status": "planned", + "depends_on": [ + "AC-0302", + "AC-0401" + ], + "owner": { + "role": "implementation", + "model": "gpt-6.1-sol", + "reasoning_effort": "high" + }, + "scope": [ + "HTTP resource owner", + "controlled network fixtures", + "cost spans" + ], + "acceptance": [ + "Retain client setup but perform all required requests", + "Credential/config partitions, drain, request accounting and full costs verified" + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + }, + { + "id": "AC-0801", + "milestone": "M8", + "title": "Qualify fixed traces and repeated agent outcomes", + "status": "planned", + "depends_on": [ + "AC-0501", + "AC-0601", + "AC-0701" + ], + "owner": { + "role": "critical review", + "model": "gpt-6-astra", + "reasoning_effort": "high" + }, + "scope": [ + "qualification receipts", + "performance report", + "release decision" + ], + "acceptance": [ + "Meaningful optimized baselines, equal work/resources, and independent outcomes", + "Exact-commit gates and local happy path verified; report failed/uncertain results" + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + } + ] +} diff --git a/experiments/agent_cast/README.md b/experiments/agent_cast/README.md new file mode 100644 index 0000000000..7a52356d2a --- /dev/null +++ b/experiments/agent_cast/README.md @@ -0,0 +1,82 @@ +# agent-cast foundation experiment + +This standalone Zig 0.16.0 experiment implements typed logical calls, exact +fixture admission, certified immutable read grouping, and per-call receipts. +It is the M1 foundation of the [implementation program](../../docs/agent-cast/milestones.md). + +The executor captures real file bytes into owned immutable storage and dispatches +only its registered content reader. Enabled and disabled modes preserve logical +results while using different backing read groups. Two agent identities appear +in one embedded process; multi-process clients belong to the broker milestone. + +Host admission and current generation values are trusted fixture contracts. +Production permissions, expiry, cancellation, a shared completed-value cache, +worker supervision, service pools, and sandbox containment remain planned. + +## Build and qualify + +Use the repository's pinned Zig 0.16.0. Set `AGENT_CAST_ZIG` to the verified +executable, then run from the fx repository root: + +```sh +PYTHONDONTWRITEBYTECODE=1 python3 scripts/qualify_agent_cast.py --zig "$AGENT_CAST_ZIG" +``` + +The runner creates a new temporary receipt directory and retains its fixtures, +logs, and JSON evidence. `--output PATH` selects a new directory and rejects an +existing path. It checks formatting, builds with one job, runs focused unit +tests, and drives the built binary against two distinct 1 KiB fixtures. Full +result bytes and rejected consumers are checked independently of the binary's +own parity flag. + +To run individual checks, change to this directory: + +```sh +"$AGENT_CAST_ZIG" fmt --check src/ build.zig +"$AGENT_CAST_ZIG" build -Doptimize=ReleaseSafe -j1 +"$AGENT_CAST_ZIG" build test -Doptimize=ReleaseSafe -j1 +./zig-out/bin/agent-cast-demo --help +``` + +Run the built demo with a file containing 256 bytes to 1 MiB: + +```sh +./zig-out/bin/agent-cast-demo --fixture /path/to/fixture --mode compare +``` + +Replace `/path/to/fixture` with the actual input file. `--mode enabled` and +`--mode disabled` run one mode; `compare` runs both against the same capture. + +## Interpreting results + +The demo requests four distinct 64-byte windows twice, plus a denied and a stale +request. Each mode retains ten logical receipts. Successful allowed requests +must match actual captured bytes; rejected requests have no output or backing +group. + +`physical_reads` counts successful calls to the captured-content reader. It does +not count OS file opens, network requests, elapsed time, or simultaneous agents. +The source file is captured once before these reads. This experiment supplies +correctness evidence and no performance claim. + +Snapshot bytes, path, and identity remain at a stable address and unchanged +through execution. Receipt output slices borrow the snapshot; consume receipts +and destroy executions before destroying the snapshot. Plan-owned arrays have +explicit `deinit` methods. + +## Work ledger + +From the fx root: + +```sh +PYTHONDONTWRITEBYTECODE=1 python3 scripts/agent_cast_ledger.py next --json +``` + +Local proof is recorded separately from CI and readiness in +[tasks.json](../../docs/agent-cast/tasks.json). Read the referenced receipts before +advancing a gate. + +The `Agent-cast Foundation` workflow qualifies this experiment on Linux and +macOS using the same runner. It supplements the repository's product checks. +The local checkpoint has no CI result until a permitted contributor publishes +the branch and those checks run on its exact commit. diff --git a/experiments/agent_cast/build.zig b/experiments/agent_cast/build.zig new file mode 100644 index 0000000000..356dbcedf6 --- /dev/null +++ b/experiments/agent_cast/build.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const demo = b.addExecutable(.{ + .name = "agent-cast-demo", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/demo.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); + b.installArtifact(demo); + + const tests = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path("src/tests.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); + const run_tests = b.addRunArtifact(tests); + const test_step = b.step("test", "Run the bounded foundation tests"); + test_step.dependOn(&run_tests.step); + + const run_demo = b.addRunArtifact(demo); + run_demo.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_demo.addArgs(args); + const run_step = b.step("run", "Run the immutable-reader demonstration"); + run_step.dependOn(&run_demo.step); +} diff --git a/experiments/agent_cast/src/contracts.zig b/experiments/agent_cast/src/contracts.zig new file mode 100644 index 0000000000..4dec6de4eb --- /dev/null +++ b/experiments/agent_cast/src/contracts.zig @@ -0,0 +1,129 @@ +const std = @import("std"); + +pub const Effect = enum { + snapshot_read, + mutable_read, + mutation, + external, + opaque_exec, +}; + +pub const Mode = enum { disabled, enabled }; + +pub const SnapshotIdentity = struct { + id: []const u8, + version: []const u8, +}; + +pub const Authority = struct { + partition: []const u8, + epoch: u64, + generation: u64, +}; + +pub const Window = struct { + offset: usize, + length: usize, +}; + +pub const Arguments = struct { + path: []const u8, + window: Window, + // Exact option bytes supplied by the caller. The planner does not normalize them. + options: []const u8, +}; + +pub const ResultContract = struct { + schema_id: []const u8, + version: u32, + encoding: enum { bytes, utf8, json }, +}; + +// All strings are borrowed and must remain alive and unchanged through execution +// and receipt consumption. Use literals or frozen owned buffers for host bindings. +pub const CallBinding = struct { + id: u64, + agent_id: []const u8, + principal_domain: []const u8, + task_id: []const u8, + tool_id: []const u8, + effect: Effect, + snapshot: ?SnapshotIdentity, + args: Arguments, + result: ResultContract, + output_budget: usize, + authority: Authority, +}; + +// The trusted fixture host supplies this exact binding and current authority. +// This experiment does not implement production permission checks or expiry clocks. +pub const HostAdmission = struct { + binding: CallBinding, + current_authority: Authority, + // Presence certifies this exact snapshot as immutable in the fixture host. + immutable_snapshot: ?SnapshotIdentity, +}; + +pub const Admission = union(enum) { + denied, + admitted: HostAdmission, +}; + +pub const LogicalCall = struct { + request: CallBinding, + admission: Admission, +}; + +pub const AdmissionStatus = enum { + admitted, + denied, + stale_authority, + binding_mismatch, + uncertified_snapshot, +}; + +pub const PlannedCall = struct { + call: LogicalCall, + status: AdmissionStatus, + physical_group: ?usize, +}; + +pub const PhysicalGroup = struct { + id: usize, + // Index in Plan.logical_calls; only admitted calls can be representatives. + representative: usize, + consumer_count: usize, +}; + +// The caller owns both arrays. Logical calls are copied, but their strings are +// borrowed and must stay alive and unchanged until receipt consumption ends. +pub const Plan = struct { + logical_calls: []PlannedCall, + groups: []PhysicalGroup, + + pub fn deinit(self: *Plan, alloc: std.mem.Allocator) void { + alloc.free(self.logical_calls); + alloc.free(self.groups); + self.* = undefined; + } +}; + +pub const ReceiptStatus = enum { + success, + admission_rejected, + unsupported_effect, + execution_failed, + output_budget_exceeded, + invalid_window, + snapshot_mismatch, +}; + +// Identity and exact arguments survive sharing. Request strings remain frozen. +// Embedded executor outputs borrow its immutable Snapshot until receipt consumption ends. +pub const Receipt = struct { + request: CallBinding, + admission_status: AdmissionStatus, + physical_group: ?usize, + status: ReceiptStatus, + output: []const u8, +}; diff --git a/experiments/agent_cast/src/demo.zig b/experiments/agent_cast/src/demo.zig new file mode 100644 index 0000000000..8843227c7c --- /dev/null +++ b/experiments/agent_cast/src/demo.zig @@ -0,0 +1,256 @@ +const std = @import("std"); +const contracts = @import("contracts.zig"); +const executor = @import("executor.zig"); + +const DemoMode = enum { compare, enabled, disabled }; +const max_fixture_bytes = 1024 * 1024; +const help = + \\Usage: agent-cast-demo --fixture PATH [--mode compare|enabled|disabled] + \\ + \\Capture one file of 256 bytes to 1 MiB and exercise an experimental embedded + \\immutable content reader. Compare mode checks every logical receipt and prints + \\JSON with actual physical read counts. This is a correctness demonstration. + \\Host admissions are fixture contracts, with no production permission checks. + \\ + \\ --fixture PATH File to capture once into owned immutable storage + \\ --mode MODE Select execution mode (default: compare) + \\ --help Show this help + \\ +; + +pub fn main(init: std.process.Init) !void { + run(init) catch |err| { + var buffer: [256]u8 = undefined; + const message = try std.fmt.bufPrint(&buffer, "agent-cast-demo: {s}; see --help\n", .{@errorName(err)}); + try std.Io.File.stderr().writeStreamingAll(init.io, message); + std.process.exit(1); + }; +} + +fn run(init: std.process.Init) !void { + var arena = std.heap.ArenaAllocator.init(init.gpa); + defer arena.deinit(); + const alloc = arena.allocator(); + const args = try init.minimal.args.toSlice(alloc); + var fixture_path: ?[]const u8 = null; + var mode: DemoMode = .compare; + var index: usize = 1; + while (index < args.len) : (index += 1) { + if (std.mem.eql(u8, args[index], "--help")) { + try std.Io.File.stdout().writeStreamingAll(init.io, help); + return; + } else if (std.mem.eql(u8, args[index], "--fixture")) { + index += 1; + if (index >= args.len) return error.MissingFixturePath; + if (fixture_path != null) return error.DuplicateFixtureOption; + fixture_path = args[index]; + } else if (std.mem.eql(u8, args[index], "--mode")) { + index += 1; + if (index >= args.len) return error.MissingMode; + mode = std.meta.stringToEnum(DemoMode, args[index]) orelse return error.InvalidMode; + } else { + return error.UnknownArgument; + } + } + const path = fixture_path orelse return error.MissingFixturePath; + const captured_file = try std.Io.Dir.cwd().readFileAlloc(init.io, path, alloc, .limited(max_fixture_bytes + 1)); + if (captured_file.len < 256) return error.FixtureTooSmall; + var snapshot = try executor.Snapshot.init(alloc, path, captured_file); + defer snapshot.deinit(alloc); + alloc.free(captured_file); + + const calls = fixture_calls(&snapshot); + var executions: [2]executor.Execution = undefined; + var run_count: usize = 0; + defer for (executions[0..run_count]) |*execution| execution.deinit(alloc); + switch (mode) { + .compare => { + executions[0] = try executor.execute(alloc, &snapshot, &calls, .disabled); + run_count = 1; + executions[1] = try executor.execute(alloc, &snapshot, &calls, .enabled); + run_count = 2; + }, + .disabled, .enabled => { + executions[0] = try executor.execute(alloc, &snapshot, &calls, if (mode == .enabled) .enabled else .disabled); + run_count = 1; + }, + } + const parity: ?bool = if (mode == .compare) receipts_equal(executions[0].receipts, executions[1].receipts) else null; + const reports = try alloc.alloc(RunReport, run_count); + for (executions[0..run_count], 0..) |execution, run_index| { + const execution_mode: contracts.Mode = if (mode == .compare and run_index == 0 or mode == .disabled) .disabled else .enabled; + try validate_fixture(&snapshot, &calls, execution, execution_mode); + reports[run_index] = try run_report(alloc, execution, execution_mode); + } + const report = .{ + .experimental_scope = "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + .mode = @tagName(mode), + .immutable_input_sha256 = @as([]const u8, &snapshot.input_hash_hex), + .immutable_input_bytes = snapshot.bytes.len, + .logical_call_count = calls.len, + .fixture_contract_verified = true, + .parity = parity, + .runs = reports, + }; + var writer: std.Io.Writer.Allocating = .init(alloc); + defer writer.deinit(); + try std.json.Stringify.value(report, .{ .whitespace = .indent_2 }, &writer.writer); + try writer.writer.writeByte('\n'); + try std.Io.File.stdout().writeStreamingAll(init.io, writer.written()); + if (parity != null and !parity.?) return error.ReceiptParityFailed; +} + +fn fixture_calls(snapshot: *const executor.Snapshot) [10]contracts.LogicalCall { + var calls: [10]contracts.LogicalCall = undefined; + for (0..4) |window_index| { + for (0..2) |agent_index| { + const index = window_index * 2 + agent_index; + calls[index] = fixture_call(snapshot, @intCast(index + 1), if (agent_index == 0) "agent-a" else "agent-b", window_index * 64); + } + } + calls[8] = fixture_call(snapshot, 9, "denied-agent", 0); + calls[8].admission = .denied; + calls[9] = fixture_call(snapshot, 10, "stale-agent", 0); + calls[9].admission.admitted.current_authority.generation += 1; + return calls; +} + +fn fixture_call(snapshot: *const executor.Snapshot, id: u64, agent_id: []const u8, offset: usize) contracts.LogicalCall { + const request: contracts.CallBinding = .{ + .id = id, + .agent_id = agent_id, + .principal_domain = "fixture-operator", + .task_id = "fixture-task", + .tool_id = "embedded-content-reader", + .effect = .snapshot_read, + .snapshot = snapshot.identity(), + .args = .{ .path = snapshot.path, .window = .{ .offset = offset, .length = 64 }, .options = "raw" }, + .result = .{ .schema_id = "content", .version = 1, .encoding = .bytes }, + .output_budget = 64, + .authority = .{ .partition = "fixture-read", .epoch = 1, .generation = 1 }, + }; + return .{ + .request = request, + .admission = .{ .admitted = .{ + .binding = request, + .current_authority = request.authority, + .immutable_snapshot = request.snapshot, + } }, + }; +} + +const CallReport = struct { + logical_id: u64, + agent_id: []const u8, + principal_domain: []const u8, + task_id: []const u8, + admission_status: []const u8, + status: []const u8, + physical_group: ?usize, + output_bytes: usize, + output_sha256: []const u8, + output_hex: []const u8, +}; + +const RunReport = struct { + mode: []const u8, + logical_call_count: usize, + receipt_count: usize, + physical_reads: usize, + receipts: []CallReport, +}; + +fn run_report(alloc: std.mem.Allocator, execution: executor.Execution, mode: contracts.Mode) !RunReport { + const receipts = try alloc.alloc(CallReport, execution.receipts.len); + for (execution.receipts, 0..) |receipt, index| { + var hash: [32]u8 = undefined; + std.crypto.hash.sha2.Sha256.hash(receipt.output, &hash, .{}); + receipts[index] = .{ + .logical_id = receipt.request.id, + .agent_id = receipt.request.agent_id, + .principal_domain = receipt.request.principal_domain, + .task_id = receipt.request.task_id, + .admission_status = @tagName(receipt.admission_status), + .status = @tagName(receipt.status), + .physical_group = receipt.physical_group, + .output_bytes = receipt.output.len, + .output_sha256 = try hex_owned(alloc, &hash), + .output_hex = try hex_owned(alloc, receipt.output), + }; + } + return .{ + .mode = @tagName(mode), + .logical_call_count = execution.plan.logical_calls.len, + .receipt_count = execution.receipts.len, + .physical_reads = execution.physical_read_count, + .receipts = receipts, + }; +} + +fn hex_owned(alloc: std.mem.Allocator, bytes: []const u8) ![]const u8 { + const hex = try alloc.alloc(u8, bytes.len * 2); + const alphabet = "0123456789abcdef"; + for (bytes, 0..) |byte, index| { + hex[index * 2] = alphabet[byte >> 4]; + hex[index * 2 + 1] = alphabet[byte & 15]; + } + return hex; +} + +fn receipts_equal(a: []const contracts.Receipt, b: []const contracts.Receipt) bool { + if (a.len != b.len) return false; + for (a, b) |receipt_a, receipt_b| { + if (receipt_a.request.id != receipt_b.request.id or + !std.mem.eql(u8, receipt_a.request.agent_id, receipt_b.request.agent_id) or + !std.mem.eql(u8, receipt_a.request.principal_domain, receipt_b.request.principal_domain) or + !std.mem.eql(u8, receipt_a.request.task_id, receipt_b.request.task_id) or + receipt_a.admission_status != receipt_b.admission_status or + receipt_a.status != receipt_b.status or + !std.mem.eql(u8, receipt_a.output, receipt_b.output)) return false; + } + return true; +} + +fn validate_fixture(snapshot: *const executor.Snapshot, calls: []const contracts.LogicalCall, execution: executor.Execution, mode: contracts.Mode) !void { + if (execution.receipts.len != calls.len) return error.FixtureReceiptCountMismatch; + const allowed_calls = calls.len - 2; + const expected_reads = if (mode == .enabled) allowed_calls / 2 else allowed_calls; + if (execution.physical_read_count != expected_reads) return error.FixturePhysicalReadCountMismatch; + for (execution.receipts, calls, 0..) |receipt, call, index| { + if (receipt.request.id != call.request.id or + !std.mem.eql(u8, receipt.request.agent_id, call.request.agent_id) or + !std.mem.eql(u8, receipt.request.principal_domain, call.request.principal_domain) or + !std.mem.eql(u8, receipt.request.task_id, call.request.task_id)) return error.FixtureIdentityMismatch; + if (index < allowed_calls) { + if (receipt.admission_status != .admitted or receipt.status != .success or receipt.physical_group == null) + return error.FixtureAllowedCallFailed; + const offset = call.request.args.window.offset; + const length = call.request.args.window.length; + if (!std.mem.eql(u8, receipt.output, snapshot.bytes[offset..][0..length])) return error.FixtureOutputMismatch; + } else { + const expected_admission: contracts.AdmissionStatus = if (index == allowed_calls) .denied else .stale_authority; + if (receipt.admission_status != expected_admission or receipt.status != .admission_rejected or + receipt.physical_group != null or receipt.output.len != 0) return error.FixtureRejectedCallExecuted; + } + } +} + +test "demo compares full receipt bytes and identities rather than aggregate counts" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "fixture", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + var call = fixture_call(&snapshot, 1, "agent-a", 0); + call.request.args.window.length = 3; + call.admission.admitted.binding = call.request; + var execution = try executor.execute(std.testing.allocator, &snapshot, &.{call}, .enabled); + defer execution.deinit(std.testing.allocator); + var altered = execution.receipts[0]; + try std.testing.expect(receipts_equal(execution.receipts, &.{altered})); + altered.output = "abd"; + try std.testing.expect(!receipts_equal(execution.receipts, &.{altered})); + altered = execution.receipts[0]; + altered.request.agent_id = "other-agent"; + try std.testing.expect(!receipts_equal(execution.receipts, &.{altered})); + altered = execution.receipts[0]; + altered.admission_status = .denied; + try std.testing.expect(!receipts_equal(execution.receipts, &.{altered})); +} diff --git a/experiments/agent_cast/src/executor.zig b/experiments/agent_cast/src/executor.zig new file mode 100644 index 0000000000..fc863f6e02 --- /dev/null +++ b/experiments/agent_cast/src/executor.zig @@ -0,0 +1,292 @@ +const std = @import("std"); +const contracts = @import("contracts.zig"); +const optimizer = @import("optimizer.zig"); + +const max_snapshot_bytes = 1024 * 1024; + +// Owns captured bytes and path. Keep this value at one address and leave all +// fields unchanged after identity() is used. Outputs borrow this frozen storage. +pub const Snapshot = struct { + path: []const u8, + bytes: []const u8, + input_hash: [32]u8, + input_hash_hex: [64]u8, + + pub fn init(alloc: std.mem.Allocator, path: []const u8, bytes: []const u8) (std.mem.Allocator.Error || error{SnapshotTooLarge})!Snapshot { + if (bytes.len > max_snapshot_bytes) return error.SnapshotTooLarge; + const captured_path = try alloc.dupe(u8, path); + errdefer alloc.free(captured_path); + const captured_bytes = try alloc.dupe(u8, bytes); + errdefer alloc.free(captured_bytes); + var hash: [32]u8 = undefined; + std.crypto.hash.sha2.Sha256.hash(captured_bytes, &hash, .{}); + return .{ + .path = captured_path, + .bytes = captured_bytes, + .input_hash = hash, + .input_hash_hex = hash_hex(hash), + }; + } + + pub fn identity(self: *const Snapshot) contracts.SnapshotIdentity { + return .{ .id = "embedded-fixture", .version = &self.input_hash_hex }; + } + + pub fn deinit(self: *Snapshot, alloc: std.mem.Allocator) void { + alloc.free(self.path); + alloc.free(self.bytes); + self.* = undefined; + } +}; + +// Owns plan and receipt arrays; receipt outputs borrow the Snapshot. Destroy this +// before the Snapshot and keep call strings frozen through receipt consumption. +pub const Execution = struct { + plan: contracts.Plan, + receipts: []contracts.Receipt, + physical_read_count: usize, + + pub fn deinit(self: *Execution, alloc: std.mem.Allocator) void { + self.plan.deinit(alloc); + alloc.free(self.receipts); + self.* = undefined; + } +}; + +// Builds its own plan and dispatches only the fixed embedded content reader. +// Host admissions are fixture contracts; production permission checks are absent. +pub fn execute( + alloc: std.mem.Allocator, + snapshot: *const Snapshot, + calls: []const contracts.LogicalCall, + mode: contracts.Mode, +) (std.mem.Allocator.Error || error{ DuplicateLogicalId, TooManyCalls, SnapshotTampered })!Execution { + var current_hash: [32]u8 = undefined; + std.crypto.hash.sha2.Sha256.hash(snapshot.bytes, ¤t_hash, .{}); + const current_hash_hex = hash_hex(current_hash); + if (!std.mem.eql(u8, ¤t_hash, &snapshot.input_hash) or + !std.mem.eql(u8, ¤t_hash_hex, &snapshot.input_hash_hex)) return error.SnapshotTampered; + + var plan = try optimizer.build_plan(alloc, calls, mode); + errdefer plan.deinit(alloc); + const receipts = try alloc.alloc(contracts.Receipt, plan.logical_calls.len); + errdefer alloc.free(receipts); + for (plan.logical_calls, 0..) |planned, index| { + receipts[index] = .{ + .request = planned.call.request, + .admission_status = planned.status, + .physical_group = planned.physical_group, + .status = .admission_rejected, + .output = "", + }; + } + + var physical_read_count: usize = 0; + for (plan.groups) |group| { + const request = plan.logical_calls[group.representative].call.request; + const result = read_snapshot(snapshot, request); + if (result.status == .success) physical_read_count += 1; + for (plan.logical_calls, 0..) |planned, index| { + if (planned.physical_group == group.id) { + receipts[index].status = result.status; + receipts[index].output = result.output; + } + } + } + return .{ .plan = plan, .receipts = receipts, .physical_read_count = physical_read_count }; +} + +const ReadResult = struct { + status: contracts.ReceiptStatus, + output: []const u8 = "", +}; + +fn read_snapshot(snapshot: *const Snapshot, request: contracts.CallBinding) ReadResult { + if (request.effect != .snapshot_read or + !std.mem.eql(u8, request.tool_id, "embedded-content-reader") or + !std.mem.eql(u8, request.args.options, "raw") or + !std.mem.eql(u8, request.result.schema_id, "content") or + request.result.version != 1 or request.result.encoding != .bytes) + return .{ .status = .unsupported_effect }; + + const requested_snapshot = request.snapshot orelse return .{ .status = .snapshot_mismatch }; + const captured_identity = snapshot.identity(); + if (!std.mem.eql(u8, request.args.path, snapshot.path) or + !std.mem.eql(u8, requested_snapshot.id, captured_identity.id) or + !std.mem.eql(u8, requested_snapshot.version, captured_identity.version)) + return .{ .status = .snapshot_mismatch }; + + const window = request.args.window; + if (window.offset > snapshot.bytes.len) return .{ .status = .invalid_window }; + if (window.length > snapshot.bytes.len - window.offset) return .{ .status = .invalid_window }; + if (window.length > request.output_budget) return .{ .status = .output_budget_exceeded }; + return .{ .status = .success, .output = snapshot.bytes[window.offset..][0..window.length] }; +} + +fn hash_hex(hash: [32]u8) [64]u8 { + const alphabet = "0123456789abcdef"; + var hex: [64]u8 = undefined; + for (hash, 0..) |byte, index| { + hex[index * 2] = alphabet[byte >> 4]; + hex[index * 2 + 1] = alphabet[byte & 15]; + } + return hex; +} + +fn fixture(snapshot: *const Snapshot, id: u64, agent_id: []const u8, offset: usize, length: usize) contracts.LogicalCall { + const request: contracts.CallBinding = .{ + .id = id, + .agent_id = agent_id, + .principal_domain = "operator", + .task_id = "fixture-task", + .tool_id = "embedded-content-reader", + .effect = .snapshot_read, + .snapshot = snapshot.identity(), + .args = .{ .path = snapshot.path, .window = .{ .offset = offset, .length = length }, .options = "raw" }, + .result = .{ .schema_id = "content", .version = 1, .encoding = .bytes }, + .output_budget = 64, + .authority = .{ .partition = "fixture-read", .epoch = 1, .generation = 1 }, + }; + return admit(request); +} + +fn admit(request: contracts.CallBinding) contracts.LogicalCall { + return .{ + .request = request, + .admission = .{ .admitted = .{ + .binding = request, + .current_authority = request.authority, + .immutable_snapshot = request.snapshot, + } }, + }; +} + +test "capture owns immutable content and path independent of mutable source buffers" { + var source = [_]u8{ 'a', 'b', 'c', 'd', 'e', 'f' }; + var path = [_]u8{ 'f', 'i', 'l', 'e' }; + var snapshot = try Snapshot.init(std.testing.allocator, &path, &source); + defer snapshot.deinit(std.testing.allocator); + source[1] = 'X'; + path[0] = 'X'; + const call = fixture(&snapshot, 1, "agent-a", 1, 3); + var result = try execute(std.testing.allocator, &snapshot, &.{call}, .enabled); + defer result.deinit(std.testing.allocator); + try std.testing.expectEqualStrings("file", snapshot.path); + try std.testing.expectEqualStrings("bcd", result.receipts[0].output); + try std.testing.expectEqual(@as(usize, 1), result.physical_read_count); + try std.testing.expectEqual(@intFromPtr(snapshot.bytes.ptr + 1), @intFromPtr(result.receipts[0].output.ptr)); +} + +test "execution detects mutation of captured bytes before admitting calls" { + var snapshot = try Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + const call = fixture(&snapshot, 1, "agent-a", 0, 3); + @constCast(snapshot.bytes)[0] = 'X'; + try std.testing.expectError(error.SnapshotTampered, execute(std.testing.allocator, &snapshot, &.{call}, .enabled)); +} + +test "windows reject out of range offsets lengths and addition overflow without reading" { + var snapshot = try Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + const windows = [_]contracts.Window{ + .{ .offset = 7, .length = 0 }, + .{ .offset = 5, .length = 2 }, + .{ .offset = 1, .length = std.math.maxInt(usize) }, + .{ .offset = std.math.maxInt(usize), .length = 2 }, + }; + for (windows) |window| { + const call = fixture(&snapshot, 1, "agent-a", window.offset, window.length); + var result = try execute(std.testing.allocator, &snapshot, &.{call}, .enabled); + defer result.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.ReceiptStatus.invalid_window, result.receipts[0].status); + try std.testing.expectEqual(@as(usize, 0), result.physical_read_count); + try std.testing.expectEqualStrings("", result.receipts[0].output); + } +} + +test "unsupported effects tools options and result contracts reject before dispatch" { + var snapshot = try Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + inline for (0..9) |change| { + var request = fixture(&snapshot, 1, "agent-a", 0, 3).request; + switch (change) { + 0 => request.effect = .mutable_read, + 1 => request.effect = .mutation, + 2 => request.effect = .external, + 3 => request.effect = .opaque_exec, + 4 => request.tool_id = "shell", + 5 => request.args.options = "normalize", + 6 => request.result.schema_id = "other", + 7 => request.result.version = 2, + 8 => request.result.encoding = .utf8, + else => unreachable, + } + var result = try execute(std.testing.allocator, &snapshot, &.{admit(request)}, .enabled); + defer result.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.ReceiptStatus.unsupported_effect, result.receipts[0].status); + try std.testing.expectEqual(@as(usize, 0), result.physical_read_count); + } +} + +test "output budget rejects before content is read" { + var snapshot = try Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + var request = fixture(&snapshot, 1, "agent-a", 0, 3).request; + request.output_budget = 2; + var result = try execute(std.testing.allocator, &snapshot, &.{admit(request)}, .enabled); + defer result.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.ReceiptStatus.output_budget_exceeded, result.receipts[0].status); + try std.testing.expectEqualStrings("", result.receipts[0].output); + try std.testing.expectEqual(@as(usize, 0), result.physical_read_count); +} + +test "snapshot labels and paths must match the captured content identity" { + var snapshot = try Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + inline for (0..3) |change| { + var request = fixture(&snapshot, 1, "agent-a", 0, 3).request; + switch (change) { + 0 => request.snapshot.?.id = "other", + 1 => request.snapshot.?.version = "unverified-label", + 2 => request.args.path = "other-file", + else => unreachable, + } + var result = try execute(std.testing.allocator, &snapshot, &.{admit(request)}, .enabled); + defer result.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.ReceiptStatus.snapshot_mismatch, result.receipts[0].status); + try std.testing.expectEqual(@as(usize, 0), result.physical_read_count); + } +} + +test "enabled and disabled execution preserve every receipt including denied and stale" { + var snapshot = try Snapshot.init(std.testing.allocator, "file", "abcdefgh"); + defer snapshot.deinit(std.testing.allocator); + var calls = [_]contracts.LogicalCall{ + fixture(&snapshot, 1, "agent-a", 0, 4), + fixture(&snapshot, 2, "agent-b", 0, 4), + fixture(&snapshot, 3, "agent-a", 4, 4), + fixture(&snapshot, 4, "agent-b", 4, 4), + fixture(&snapshot, 5, "denied-agent", 0, 4), + fixture(&snapshot, 6, "stale-agent", 0, 4), + }; + calls[4].admission = .denied; + calls[5].admission.admitted.current_authority.generation += 1; + var disabled = try execute(std.testing.allocator, &snapshot, &calls, .disabled); + defer disabled.deinit(std.testing.allocator); + var enabled = try execute(std.testing.allocator, &snapshot, &calls, .enabled); + defer enabled.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 4), disabled.physical_read_count); + try std.testing.expectEqual(@as(usize, 2), enabled.physical_read_count); + try std.testing.expectEqual(@as(usize, 6), enabled.receipts.len); + for (disabled.receipts, enabled.receipts) |a, b| { + try std.testing.expectEqual(a.request.id, b.request.id); + try std.testing.expectEqualStrings(a.request.agent_id, b.request.agent_id); + try std.testing.expectEqual(a.admission_status, b.admission_status); + try std.testing.expectEqual(a.status, b.status); + try std.testing.expectEqualStrings(a.output, b.output); + } + try std.testing.expectEqual(contracts.AdmissionStatus.denied, enabled.receipts[4].admission_status); + try std.testing.expectEqual(contracts.AdmissionStatus.stale_authority, enabled.receipts[5].admission_status); + try std.testing.expectEqualStrings("", enabled.receipts[4].output); + try std.testing.expectEqualStrings("", enabled.receipts[5].output); +} diff --git a/experiments/agent_cast/src/optimizer.zig b/experiments/agent_cast/src/optimizer.zig new file mode 100644 index 0000000000..2f8a5fd66c --- /dev/null +++ b/experiments/agent_cast/src/optimizer.zig @@ -0,0 +1,291 @@ +const std = @import("std"); +const contracts = @import("contracts.zig"); + +const max_calls = 1024; + +// The caller owns the returned arrays and releases them with Plan.deinit. +// Calls retain input order. Borrowed strings must stay alive and unchanged. +pub fn build_plan( + alloc: std.mem.Allocator, + calls: []const contracts.LogicalCall, + mode: contracts.Mode, +) (std.mem.Allocator.Error || error{ DuplicateLogicalId, TooManyCalls })!contracts.Plan { + if (calls.len > max_calls) return error.TooManyCalls; + for (calls, 0..) |call, index| { + for (calls[0..index]) |earlier| { + if (call.request.id == earlier.request.id) return error.DuplicateLogicalId; + } + } + + const logical_calls = try alloc.alloc(contracts.PlannedCall, calls.len); + errdefer alloc.free(logical_calls); + var groups: std.ArrayList(contracts.PhysicalGroup) = .empty; + defer groups.deinit(alloc); + try groups.ensureTotalCapacity(alloc, calls.len); + + for (calls, 0..) |call, index| { + const status = admission_status(call); + logical_calls[index] = .{ + .call = call, + .status = status, + .physical_group = null, + }; + if (status != .admitted) continue; + + var existing_group: ?usize = null; + if (mode == .enabled and call.request.effect == .snapshot_read) { + for (groups.items, 0..) |group, group_index| { + const representative = logical_calls[group.representative].call.request; + if (compatible(call.request, representative)) { + existing_group = group_index; + break; + } + } + } + if (existing_group) |group_index| { + logical_calls[index].physical_group = group_index; + groups.items[group_index].consumer_count += 1; + } else { + const group_index = groups.items.len; + groups.appendAssumeCapacity(.{ + .id = group_index, + .representative = index, + .consumer_count = 1, + }); + logical_calls[index].physical_group = group_index; + } + } + + return .{ + .logical_calls = logical_calls, + .groups = try groups.toOwnedSlice(alloc), + }; +} + +fn admission_status(call: contracts.LogicalCall) contracts.AdmissionStatus { + const admitted = switch (call.admission) { + .denied => return .denied, + .admitted => |host_admission| host_admission, + }; + if (!exact_binding(call.request, admitted.binding)) return .binding_mismatch; + if (!same_authority(call.request.authority, admitted.current_authority)) return .stale_authority; + if (call.request.effect == .snapshot_read) { + const requested_snapshot = call.request.snapshot orelse return .uncertified_snapshot; + const certified_snapshot = admitted.immutable_snapshot orelse return .uncertified_snapshot; + if (!same_snapshot(requested_snapshot, certified_snapshot)) return .uncertified_snapshot; + } + return .admitted; +} + +fn exact_binding(a: contracts.CallBinding, b: contracts.CallBinding) bool { + return a.id == b.id and + same_bytes(a.agent_id, b.agent_id) and + compatible_fields(a, b); +} + +fn compatible(a: contracts.CallBinding, b: contracts.CallBinding) bool { + return a.effect == .snapshot_read and b.effect == .snapshot_read and compatible_fields(a, b); +} + +// Logical identity is deliberately excluded from physical equivalence. +fn compatible_fields(a: contracts.CallBinding, b: contracts.CallBinding) bool { + return same_bytes(a.principal_domain, b.principal_domain) and + same_bytes(a.task_id, b.task_id) and + same_bytes(a.tool_id, b.tool_id) and + a.effect == b.effect and + same_optional_snapshot(a.snapshot, b.snapshot) and + same_bytes(a.args.path, b.args.path) and + a.args.window.offset == b.args.window.offset and + a.args.window.length == b.args.window.length and + same_bytes(a.args.options, b.args.options) and + same_bytes(a.result.schema_id, b.result.schema_id) and + a.result.version == b.result.version and + a.result.encoding == b.result.encoding and + a.output_budget == b.output_budget and + same_authority(a.authority, b.authority); +} + +fn same_authority(a: contracts.Authority, b: contracts.Authority) bool { + return same_bytes(a.partition, b.partition) and a.epoch == b.epoch and a.generation == b.generation; +} + +fn same_optional_snapshot(a: ?contracts.SnapshotIdentity, b: ?contracts.SnapshotIdentity) bool { + if (a) |snapshot_a| { + return if (b) |snapshot_b| same_snapshot(snapshot_a, snapshot_b) else false; + } + return b == null; +} + +fn same_snapshot(a: contracts.SnapshotIdentity, b: contracts.SnapshotIdentity) bool { + return same_bytes(a.id, b.id) and same_bytes(a.version, b.version); +} + +fn same_bytes(a: []const u8, b: []const u8) bool { + return std.mem.eql(u8, a, b); +} + +fn fixture(id: u64, agent_id: []const u8) contracts.LogicalCall { + const request: contracts.CallBinding = .{ + .id = id, + .agent_id = agent_id, + .principal_domain = "operator", + .task_id = "task-1", + .tool_id = "embedded-content-reader", + .effect = .snapshot_read, + .snapshot = .{ .id = "snapshot-1", .version = "content-v1" }, + .args = .{ .path = "fixture.txt", .window = .{ .offset = 0, .length = 8 }, .options = "raw" }, + .result = .{ .schema_id = "content", .version = 1, .encoding = .bytes }, + .output_budget = 64, + .authority = .{ .partition = "operator-read", .epoch = 2, .generation = 3 }, + }; + return admit(request); +} + +fn admit(request: contracts.CallBinding) contracts.LogicalCall { + return .{ + .request = request, + .admission = .{ .admitted = .{ + .binding = request, + .current_authority = request.authority, + .immutable_snapshot = request.snapshot, + } }, + }; +} + +test "two agents in the same domain share certified reads only when enabled" { + const calls = [_]contracts.LogicalCall{ fixture(1, "agent-a"), fixture(2, "agent-b") }; + var enabled = try build_plan(std.testing.allocator, &calls, .enabled); + defer enabled.deinit(std.testing.allocator); + var disabled = try build_plan(std.testing.allocator, &calls, .disabled); + defer disabled.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 1), enabled.groups.len); + try std.testing.expectEqual(@as(usize, 2), disabled.groups.len); + try std.testing.expectEqual(@as(usize, 2), enabled.logical_calls.len); + try std.testing.expectEqual(@as(usize, 2), enabled.groups[0].consumer_count); + try std.testing.expectEqualStrings("agent-a", enabled.logical_calls[0].call.request.agent_id); + try std.testing.expectEqualStrings("agent-b", enabled.logical_calls[1].call.request.agent_id); + try std.testing.expectEqual(@as(?usize, 0), enabled.logical_calls[0].physical_group); + try std.testing.expectEqual(@as(?usize, 0), enabled.logical_calls[1].physical_group); +} + +test "denied consumers retain logical identity and never enter physical groups" { + var denied = fixture(2, "agent-b"); + denied.admission = .denied; + const calls = [_]contracts.LogicalCall{ denied, fixture(1, "agent-a"), fixture(3, "agent-c") }; + inline for (.{ contracts.Mode.disabled, contracts.Mode.enabled }) |mode| { + var plan = try build_plan(std.testing.allocator, &calls, mode); + defer plan.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 3), plan.logical_calls.len); + try std.testing.expectEqual(contracts.AdmissionStatus.denied, plan.logical_calls[0].status); + try std.testing.expectEqual(@as(?usize, null), plan.logical_calls[0].physical_group); + for (plan.groups) |group| try std.testing.expect(group.representative != 0); + try std.testing.expectEqual(@as(u64, 2), plan.logical_calls[0].call.request.id); + } +} + +test "host generation and epoch changes reject stale authority" { + inline for (.{ "epoch", "generation" }) |field| { + var stale = fixture(1, "agent-a"); + @field(stale.admission.admitted.current_authority, field) += 1; + var plan = try build_plan(std.testing.allocator, &.{stale}, .enabled); + defer plan.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.AdmissionStatus.stale_authority, plan.logical_calls[0].status); + try std.testing.expectEqual(@as(usize, 0), plan.groups.len); + } +} + +test "admission binds the exact request including logical agent and arguments" { + inline for (0..4) |change| { + var changed = fixture(1, "agent-a"); + switch (change) { + 0 => changed.request.id = 9, + 1 => changed.request.agent_id = "agent-b", + 2 => changed.request.args.path = "other.txt", + 3 => changed.request.args.window.offset = 1, + else => unreachable, + } + var plan = try build_plan(std.testing.allocator, &.{changed}, .enabled); + defer plan.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.AdmissionStatus.binding_mismatch, plan.logical_calls[0].status); + try std.testing.expectEqual(@as(usize, 0), plan.groups.len); + } +} + +test "snapshot reads need an exact host certificate independent of tool names" { + inline for (0..3) |change| { + var uncertified = fixture(1, "agent-a"); + switch (change) { + 0 => uncertified.admission.admitted.immutable_snapshot = null, + 1 => uncertified.admission.admitted.immutable_snapshot.?.version = "content-v2", + 2 => { + uncertified.request.snapshot = null; + uncertified.admission.admitted.binding = uncertified.request; + }, + else => unreachable, + } + var plan = try build_plan(std.testing.allocator, &.{uncertified}, .enabled); + defer plan.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.AdmissionStatus.uncertified_snapshot, plan.logical_calls[0].status); + try std.testing.expectEqual(@as(usize, 0), plan.groups.len); + } +} + +test "different snapshots contracts authorities domains tasks and options prevent sharing" { + inline for (0..16) |change| { + const first = fixture(1, "agent-a"); + var request = fixture(2, "agent-b").request; + switch (change) { + 0 => request.snapshot.?.id = "snapshot-2", + 1 => request.snapshot.?.version = "content-v2", + 2 => request.result.schema_id = "other-schema", + 3 => request.result.version = 2, + 4 => request.result.encoding = .utf8, + 5 => request.authority.partition = "other-grant", + 6 => request.authority.epoch += 1, + 7 => request.authority.generation += 1, + 8 => request.principal_domain = "other-operator", + 9 => request.task_id = "other-task", + 10 => request.args.options = "with-header", + 11 => request.args.path = "other.txt", + 12 => request.args.window.offset = 1, + 13 => request.args.window.length = 4, + 14 => request.output_budget = 32, + 15 => request.tool_id = "other-reader", + else => unreachable, + } + const calls = [_]contracts.LogicalCall{ first, admit(request) }; + var plan = try build_plan(std.testing.allocator, &calls, .enabled); + defer plan.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 2), plan.groups.len); + try std.testing.expectEqual(contracts.AdmissionStatus.admitted, plan.logical_calls[1].status); + } +} + +test "mutable reads mutations external effects and opaque calls remain separate" { + inline for (.{ contracts.Effect.mutable_read, contracts.Effect.mutation, contracts.Effect.external, contracts.Effect.opaque_exec }) |effect| { + var first = fixture(1, "agent-a").request; + var second = fixture(2, "agent-b").request; + first.effect = effect; + second.effect = effect; + const calls = [_]contracts.LogicalCall{ admit(first), admit(second) }; + var plan = try build_plan(std.testing.allocator, &calls, .enabled); + defer plan.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 2), plan.groups.len); + } +} + +test "duplicate logical IDs are rejected even across agents or admission outcomes" { + var second = fixture(1, "agent-b"); + second.admission = .denied; + const calls = [_]contracts.LogicalCall{ fixture(1, "agent-a"), second }; + try std.testing.expectError(error.DuplicateLogicalId, build_plan(std.testing.allocator, &calls, .enabled)); +} + +test "empty input is valid and the bounded planner rejects excess calls" { + var empty = try build_plan(std.testing.allocator, &.{}, .enabled); + defer empty.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 0), empty.logical_calls.len); + try std.testing.expectEqual(@as(usize, 0), empty.groups.len); + const calls: [max_calls + 1]contracts.LogicalCall = undefined; + try std.testing.expectError(error.TooManyCalls, build_plan(std.testing.allocator, &calls, .enabled)); +} diff --git a/experiments/agent_cast/src/tests.zig b/experiments/agent_cast/src/tests.zig new file mode 100644 index 0000000000..d704f5f289 --- /dev/null +++ b/experiments/agent_cast/src/tests.zig @@ -0,0 +1,6 @@ +test { + _ = @import("contracts.zig"); + _ = @import("optimizer.zig"); + _ = @import("executor.zig"); + _ = @import("demo.zig"); +} diff --git a/scripts/agent_cast_ledger.py b/scripts/agent_cast_ledger.py new file mode 100644 index 0000000000..d498fc66d2 --- /dev/null +++ b/scripts/agent_cast_ledger.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +"""Validate and inspect the agent-cast task ledger without changing it.""" + +import argparse +import json +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +DEFAULT_LEDGER = ROOT / "docs" / "agent-cast" / "tasks.json" +STATUSES = { + "planned", "in_progress", "implemented", "verified_local", + "ci_pending", "done", "blocked", +} +AVAILABLE = {"done", "verified_local"} + + +def strings(value): + return isinstance(value, list) and all(isinstance(item, str) and item.strip() for item in value) + + +def acyclic(items, label, errors): + valid_items = [item for item in items if isinstance(item, dict)] + ids = {item["id"] for item in valid_items if isinstance(item.get("id"), str) and item["id"].strip()} + graph = { + item["id"]: item["depends_on"] + for item in valid_items + if isinstance(item.get("id"), str) and item["id"].strip() + and strings(item.get("depends_on")) + } + visiting, visited = set(), set() + + def visit(node): + if node in visiting: + errors.append(f"{label} dependency cycle includes {node!r}") + return + if node in visited: + return + visiting.add(node) + for dep in graph.get(node, []): + if dep in ids: + visit(dep) + visiting.remove(node) + visited.add(node) + + for node in ids: + visit(node) + + +def validate(data): + errors = [] + if not isinstance(data, dict): + return ["ledger root must be an object"] + if type(data.get("schema_version")) is not int or data["schema_version"] != 1: + errors.append("schema_version must be 1") + project = data.get("project") + if not isinstance(project, dict) or not all(isinstance(project.get(k), str) and project[k].strip() for k in ("name", "objective")): + errors.append("project must contain nonempty name and objective") + milestones, tasks = data.get("milestones"), data.get("tasks") + if not isinstance(milestones, list) or not isinstance(tasks, list): + return errors + ["milestones and tasks must be arrays"] + + def check_ids(items, label): + ids = [] + for i, item in enumerate(items): + if not isinstance(item, dict): + errors.append(f"{label}[{i}] must be an object") + continue + ident = item.get("id") + if not isinstance(ident, str) or not ident.strip(): + errors.append(f"{label}[{i}] needs a nonempty id") + else: + ids.append(ident) + if len(ids) != len(set(ids)): + errors.append(f"{label} IDs must be unique") + return set(ids) + + milestone_ids = check_ids(milestones, "milestones") + task_ids = check_ids(tasks, "tasks") + for i, item in enumerate(milestones): + if not isinstance(item, dict): + continue + if not isinstance(item.get("title"), str) or not item["title"].strip(): + errors.append(f"milestones[{i}] needs a nonempty title") + if not strings(item.get("acceptance")) or not item["acceptance"]: + errors.append(f"milestones[{i}].acceptance must be a nonempty array of nonempty strings") + deps = item.get("depends_on") + if not strings(deps): + errors.append(f"milestones[{i}].depends_on must be an array of nonempty strings") + else: + for dep in deps: + if dep not in milestone_ids: + errors.append(f"milestones[{i}] has unknown dependency {dep!r}") + acyclic(milestones, "milestone", errors) + + by_id = {item.get("id"): item for item in tasks if isinstance(item, dict) and isinstance(item.get("id"), str)} + for i, task in enumerate(tasks): + if not isinstance(task, dict): + continue + prefix = f"tasks[{i}]" + if not isinstance(task.get("milestone"), str) or task["milestone"] not in milestone_ids: + errors.append(f"{prefix} references unknown milestone {task.get('milestone')!r}") + if not isinstance(task.get("title"), str) or not task["title"].strip(): + errors.append(f"{prefix} needs a nonempty title") + if not isinstance(task.get("status"), str) or task["status"] not in STATUSES: + errors.append(f"{prefix} has invalid status {task.get('status')!r}") + for key in ("scope", "acceptance"): + if not strings(task.get(key)) or not task[key]: + errors.append(f"{prefix}.{key} must be a nonempty array of nonempty strings") + deps = task.get("depends_on") + if not strings(deps): + errors.append(f"{prefix}.depends_on must be an array of nonempty strings") + else: + for dep in deps: + if dep not in task_ids: + errors.append(f"{prefix} has unknown dependency {dep!r}") + owner = task.get("owner") + if not isinstance(owner, dict) or not all(isinstance(owner.get(k), str) and owner[k].strip() for k in ("role", "model", "reasoning_effort")): + errors.append(f"{prefix}.owner needs nonempty role, model, and reasoning_effort") + usage = task.get("usage") + if not isinstance(usage, dict) or any(usage.get(k) is not None and (not isinstance(usage[k], int) or isinstance(usage[k], bool) or usage[k] < 0) for k in ("input_tokens", "output_tokens")): + errors.append(f"{prefix}.usage token counts must be nonnegative integers or null") + if isinstance(usage, dict) and usage.get("source") is not None and not isinstance(usage["source"], str): + errors.append(f"{prefix}.usage.source must be a string or null") + if isinstance(usage, dict) and any(usage.get(k) is not None for k in ("input_tokens", "output_tokens")) and not (isinstance(usage.get("source"), str) and usage["source"].strip()): + errors.append(f"{prefix}.usage.source must be nonempty when token counts are provided") + evidence = task.get("evidence") + if not strings(evidence): + errors.append(f"{prefix}.evidence must be an array of nonempty strings") + elif task.get("status") in ("done", "verified_local") and not evidence: + errors.append(f"{prefix} status {task['status']} requires evidence") + acyclic(tasks, "task", errors) + for task in tasks: + if not isinstance(task, dict) or task.get("status") != "done": + continue + deps = task.get("depends_on") + if not strings(deps): + continue + for dep in deps: + dependency = by_id.get(dep) + if dependency and dependency.get("status") != "done": + errors.append(f"done task {task.get('id')!r} requires dependency {dep!r} to be done") + return errors + + +def report(data, command): + tasks = data.get("tasks", []) + if command == "next": + selected = [t for t in tasks if t.get("status") == "planned" and all( + isinstance(next((d for d in tasks if d.get("id") == dep), None), dict) + and next(d for d in tasks if d.get("id") == dep).get("status") in AVAILABLE + for dep in t.get("depends_on", []))] + elif command == "show": + selected = tasks + else: + selected = [] + return {"ok": True, "command": command, "project": data.get("project"), "tasks": selected, + "done_task_ids": [t.get("id") for t in tasks if t.get("status") == "done"], + "ci_pending": [t.get("id") for t in tasks if t.get("status") == "ci_pending"], + "verification_note": "This checker validates ledger consistency, not live CI results or the truth of receipt content."} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("command", nargs="?", choices=("validate", "next", "show"), default="validate") + parser.add_argument("--ledger", type=Path, default=DEFAULT_LEDGER) + parser.add_argument("--json", action="store_true", help="write structured JSON") + args = parser.parse_args() + try: + data = json.loads(args.ledger.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + errors = [f"cannot read ledger {args.ledger}: {exc}"] + result = {"ok": False, "errors": errors} + else: + errors = validate(data) + result = {"ok": not errors, "errors": errors} if errors else report(data, args.command) + if args.json: + print(json.dumps(result, indent=2)) + elif result["ok"]: + if args.command == "validate": + print("Ledger valid") + elif args.command == "next": + for task in result["tasks"]: + print(f"{task['id']} [{task['status']}] {task['title']}") + if not result["tasks"]: + print("No planned tasks are currently unblocked") + if result["ci_pending"]: + print("CI pending: " + ", ".join(result["ci_pending"])) + else: + for task in result["tasks"]: + print(f"{task['id']} [{task['status']}] {task['title']}") + else: + for error in result["errors"]: + print(error, file=sys.stderr) + return 0 if result["ok"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/qualify_agent_cast.py b/scripts/qualify_agent_cast.py new file mode 100644 index 0000000000..635edd0aae --- /dev/null +++ b/scripts/qualify_agent_cast.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Build and exercise the standalone agent-cast foundation with owned fixtures.""" + +import argparse +import datetime +import hashlib +import json +import platform +import subprocess +import tempfile +import time +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +PROJECT = ROOT / "experiments" / "agent_cast" + + +def digest(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def verify_report(report, content, mode): + expected_modes = ["disabled", "enabled"] if mode == "compare" else [mode] + if report["mode"] != mode or report["logical_call_count"] != 10: + raise ValueError("wrong logical workload or execution mode") + if report["fixture_contract_verified"] is not True: + raise ValueError("binary did not verify the useful fixture workload") + if report["immutable_input_sha256"] != hashlib.sha256(content).hexdigest(): + raise ValueError("binary did not capture the actual supplied fixture") + if report["immutable_input_bytes"] != len(content): + raise ValueError("wrong captured input length") + if [run["mode"] for run in report["runs"]] != expected_modes: + raise ValueError("wrong execution reports") + logical_results = [] + for run in report["runs"]: + if run["logical_call_count"] != 10 or run["receipt_count"] != 10: + raise ValueError("logical calls or receipts disappeared") + if run["physical_reads"] != (4 if run["mode"] == "enabled" else 8): + raise ValueError("wrong actual backing read count") + receipts = run["receipts"] + if len(receipts) != 10: + raise ValueError("wrong result array size") + result = [] + for index, receipt in enumerate(receipts): + if receipt["logical_id"] != index + 1: + raise ValueError("logical identity changed") + if index < 8: + expected = content[(index // 2) * 64:((index // 2) + 1) * 64] + agent = "agent-a" if index % 2 == 0 else "agent-b" + if receipt["agent_id"] != agent: + raise ValueError("agent identity changed") + if receipt["admission_status"] != "admitted" or receipt["status"] != "success": + raise ValueError("allowed read failed") + if receipt["physical_group"] is None: + raise ValueError("successful read has no backing work") + else: + expected = b"" + admission = "denied" if index == 8 else "stale_authority" + if receipt["admission_status"] != admission or receipt["status"] != "admission_rejected": + raise ValueError("rejected call executed") + if receipt["physical_group"] is not None: + raise ValueError("rejected call entered a backing group") + if bytes.fromhex(receipt["output_hex"]) != expected: + raise ValueError("result bytes differ from the independent fixture") + if receipt["output_bytes"] != len(expected): + raise ValueError("wrong output length") + if receipt["output_sha256"] != hashlib.sha256(expected).hexdigest(): + raise ValueError("wrong output digest") + result.append({key: value for key, value in receipt.items() if key != "physical_group"}) + logical_results.append(result) + if mode == "compare": + if report["parity"] is not True or logical_results[0] != logical_results[1]: + raise ValueError("enabled and disabled logical results differ") + elif report["parity"] is not None: + raise ValueError("single-mode run claims comparative parity") + + +def qualify(zig, output): + checks = [] + receipt = { + "schema_version": 1, + "recorded_at_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(), + "scope": "local foundation correctness; logical agent identities in one process", + "platform": platform.platform(), + "machine": platform.machine(), + "source_sha256": {str(p.relative_to(ROOT)): digest(p) for p in sorted(PROJECT.rglob("*.zig")) if "zig-out" not in p.parts and ".zig-cache" not in p.parts}, + "checks": checks, + "ci_status": "not_run", + "performance_claim": None, + "ok": False, + } + + def run(name, argv, cwd=PROJECT): + start = time.monotonic() + result = subprocess.run(argv, cwd=cwd, capture_output=True, text=True, timeout=300) + (output / f"{name}.stdout").write_text(result.stdout) + (output / f"{name}.stderr").write_text(result.stderr) + check = {"name": name, "argv": [str(a) for a in argv], "cwd": str(cwd), "exit_code": result.returncode, "seconds": time.monotonic() - start, "stdout": f"{name}.stdout", "stderr": f"{name}.stderr"} + checks.append(check) + if result.returncode: + raise RuntimeError(f"{name} failed: {result.stderr[-2000:]}") + return result + + try: + version = run("zig-version", [zig, "version"]).stdout.strip() + if version != "0.16.0": + raise ValueError(f"expected pinned Zig0.16.0, got {version}") + receipt["zig_version"] = version + run("format", [zig, "fmt", "--check", "src/", "build.zig"]) + run("build", [zig, "build", "-Doptimize=ReleaseSafe", "-j1"]) + run("unit", [zig, "build", "test", "-Doptimize=ReleaseSafe", "-j1", "--summary", "all"]) + binary = PROJECT / "zig-out" / "bin" / "agent-cast-demo" + receipt["binary_sha256"] = digest(binary) + help_result = run("help", [binary, "--help"]) + if help_result.stderr or "--fixture" not in help_result.stdout: + raise ValueError("built CLI help interaction failed") + fixtures = [] + for fixture_index in range(2): + content = b"".join(f"fixture-{fixture_index}-window-{i:02d}".encode().ljust(64, bytes([65 + fixture_index + i])) for i in range(16)) + if len({content[i:i + 64] for i in range(0, 256, 64)}) != 4: + raise ValueError("fixture windows are degenerate") + path = output / f"fixture-{fixture_index}.bin" + path.write_bytes(content) + result = run(f"compare-{fixture_index}", [binary, "--fixture", path, "--mode", "compare"]) + if result.stderr: + raise ValueError("built comparison wrote unexpected stderr") + report = json.loads(result.stdout) + verify_report(report, content, "compare") + fixtures.append({"sha256": digest(path), "bytes": len(content), "comparison": f"compare-{fixture_index}.stdout", "logical_receipts_per_mode": 10, "disabled_backing_reads": report["runs"][0]["physical_reads"], "enabled_backing_reads": report["runs"][1]["physical_reads"]}) + if fixtures[0]["sha256"] == fixtures[1]["sha256"]: + raise ValueError("the independent fixtures have identical content") + receipt["fixtures"] = fixtures + content = (output / "fixture-0.bin").read_bytes() + for mode in ["enabled", "disabled"]: + result = run(mode, [binary, "--fixture", output / "fixture-0.bin", "--mode", mode]) + if result.stderr: + raise ValueError("single-mode interaction wrote unexpected stderr") + verify_report(json.loads(result.stdout), content, mode) + receipt["ok"] = True + except (OSError, subprocess.TimeoutExpired, RuntimeError, ValueError, KeyError) as error: + receipt["error"] = str(error) + finally: + (output / "qualification.json").write_text(json.dumps(receipt, indent=2) + "\n") + return receipt + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--zig", default="zig", help="verified Zig0.16.0 executable") + parser.add_argument("--output", type=Path, help="new output directory; existing paths are rejected") + args = parser.parse_args() + if args.output: + output = args.output.resolve() + output.mkdir(parents=True, exist_ok=False) + else: + output = Path(tempfile.mkdtemp(prefix="agent-cast-qualification-")) + receipt = qualify(args.zig, output) + print(json.dumps({"ok": receipt["ok"], "receipt": str(output / "qualification.json"), "checks": len(receipt["checks"]), "error": receipt.get("error")}, indent=2)) + return 0 if receipt["ok"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/tests/test_agent_cast_ledger.py b/scripts/tests/test_agent_cast_ledger.py new file mode 100644 index 0000000000..382c26c024 --- /dev/null +++ b/scripts/tests/test_agent_cast_ledger.py @@ -0,0 +1,148 @@ +"""Exercise the ledger CLI against corrupt state and dependency gates.""" + +import copy +import hashlib +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +CLI = ROOT / "scripts" / "agent_cast_ledger.py" + + +def sample_ledger(): + task = { + "id": "AC-1", "milestone": "M0", "title": "Establish contracts", + "status": "planned", "depends_on": [], + "owner": {"role": "implementation", "model": "gpt-6.1-sol", "reasoning_effort": "medium"}, + "scope": ["src/contracts.zig"], "acceptance": ["Exact binding is preserved"], + "evidence": [], "usage": {"input_tokens": None, "output_tokens": None, "source": None}, + } + dependent = copy.deepcopy(task) + dependent.update(id="AC-2", title="Implement reader", depends_on=["AC-1"]) + return { + "schema_version": 1, + "project": {"name": "agent-cast", "objective": "Preserve logical calls"}, + "milestones": [{"id": "M0", "title": "Foundation", "depends_on": [], "acceptance": ["Contracts verified"]}], + "tasks": [task, dependent], + } + + +class LedgerCliTests(unittest.TestCase): + def run_cli(self, data, command="validate", expect_ok=True): + with tempfile.TemporaryDirectory(prefix="agent-cast-ledger-test-") as directory: + path = Path(directory) / "tasks.json" + payload = json.dumps(data).encode() + path.write_bytes(payload) + result = subprocess.run( + [sys.executable, str(CLI), command, "--ledger", str(path), "--json"], + capture_output=True, text=True, timeout=10, + env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}, + ) + self.assertEqual(path.read_bytes(), payload, "read-only CLI mutated the ledger") + self.assertEqual(result.returncode, 0 if expect_ok else 1, result.stderr) + self.assertEqual(result.stderr, "", "JSON mode must return structured failures without a traceback") + output = json.loads(result.stdout) + self.assertEqual(output["ok"], expect_ok) + return output + + def test_canonical_ledger_validates_without_mutation(self): + path = ROOT / "docs" / "agent-cast" / "tasks.json" + before = hashlib.sha256(path.read_bytes()).digest() + self.run_cli(json.loads(path.read_text())) + self.assertEqual(hashlib.sha256(path.read_bytes()).digest(), before) + + def test_next_preserves_ownership_and_dependency_gate(self): + data = sample_ledger() + output = self.run_cli(data, "next") + self.assertEqual(output["tasks"], [data["tasks"][0]]) + first = data["tasks"][0] + first.update(status="verified_local", evidence=["local-test-receipt.json"]) + output = self.run_cli(data, "next") + self.assertEqual(output["tasks"], [data["tasks"][1]]) + self.assertEqual(output["done_task_ids"], []) + self.assertIn("not live CI", output["verification_note"]) + first["status"] = "ci_pending" + output = self.run_cli(data, "next") + self.assertEqual(output["tasks"], []) + self.assertEqual(output["ci_pending"], ["AC-1"]) + + def test_show_retains_full_task_records(self): + data = sample_ledger() + self.assertEqual(self.run_cli(data, "show")["tasks"], data["tasks"]) + + def test_corrupt_field_types_return_diagnostics(self): + cases = [ + ("id", [], "nonempty id"), ("milestone", {}, "unknown milestone"), + ("status", [], "invalid status"), ("depends_on", [None], "depends_on"), + ("acceptance", [], "acceptance"), ("scope", "src", "scope"), + ("owner", None, "owner"), ("evidence", [1], "evidence"), + ] + for field, value, diagnostic in cases: + with self.subTest(field=field): + data = sample_ledger() + data["tasks"][0][field] = value + output = self.run_cli(data, expect_ok=False) + self.assertTrue(any(diagnostic in item for item in output["errors"]), output) + + def test_bool_is_not_a_schema_version_or_token_count(self): + data = sample_ledger() + data["schema_version"] = True + self.assertIn("schema_version must be 1", self.run_cli(data, expect_ok=False)["errors"]) + data = sample_ledger() + data["tasks"][0]["usage"]["input_tokens"] = True + self.assertIn("token counts", " ".join(self.run_cli(data, expect_ok=False)["errors"])) + + def test_cycles_and_missing_dependencies_are_rejected(self): + data = sample_ledger() + data["tasks"][0]["depends_on"] = ["AC-2"] + self.assertIn("dependency cycle", " ".join(self.run_cli(data, expect_ok=False)["errors"])) + data["tasks"][0]["depends_on"] = ["AC-missing"] + self.assertIn("unknown dependency", " ".join(self.run_cli(data, expect_ok=False)["errors"])) + data = sample_ledger() + data["milestones"][0]["depends_on"] = ["M0"] + self.assertIn("milestone dependency cycle", " ".join(self.run_cli(data, expect_ok=False)["errors"])) + + def test_duplicate_ids_are_rejected(self): + data = sample_ledger() + data["tasks"][1]["id"] = "AC-1" + self.assertIn("tasks IDs must be unique", self.run_cli(data, expect_ok=False)["errors"]) + + def test_verified_status_requires_evidence_and_done_dependencies(self): + data = sample_ledger() + data["tasks"][0]["status"] = "verified_local" + self.assertIn("requires evidence", " ".join(self.run_cli(data, expect_ok=False)["errors"])) + data["tasks"][0]["evidence"] = ["proof.json"] + data["tasks"][1].update(status="done", evidence=["ci.json"]) + self.assertIn("requires dependency", " ".join(self.run_cli(data, expect_ok=False)["errors"])) + data["tasks"][0]["status"] = "done" + self.assertEqual(self.run_cli(data)["done_task_ids"], ["AC-1", "AC-2"]) + + def test_usage_requires_a_receipt_reference(self): + data = sample_ledger() + data["tasks"][0]["usage"]["input_tokens"] = 100 + self.assertIn("source must be nonempty", " ".join(self.run_cli(data, expect_ok=False)["errors"])) + data["tasks"][0]["usage"]["source"] = "provider-receipt.json" + self.run_cli(data) + + def test_invalid_json_returns_structured_failure(self): + with tempfile.TemporaryDirectory(prefix="agent-cast-ledger-test-") as directory: + path = Path(directory) / "tasks.json" + path.write_text("{") + result = subprocess.run( + [sys.executable, str(CLI), "--ledger", str(path), "--json"], + capture_output=True, text=True, timeout=10, + env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}, + ) + self.assertEqual(result.returncode, 1) + self.assertEqual(result.stderr, "") + self.assertFalse(json.loads(result.stdout)["ok"]) + + +if __name__ == "__main__": + unittest.main() From 03cc8c91924138341dc2789c6581c0aa3a3bfed6 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Tue, 6 Oct 2026 22:23:28 +0200 Subject: [PATCH 2/3] Add bounded completed-value caching and subscriber contracts --- docs/agent-cast-architecture.html | 2 +- docs/agent-cast/events.jsonl | 4 + .../evidence/m2-2026-10-06/build.stderr | 0 .../evidence/m2-2026-10-06/build.stdout | 0 .../evidence/m2-2026-10-06/cache-0.stderr | 0 .../evidence/m2-2026-10-06/cache-0.stdout | 220 ++++++ .../evidence/m2-2026-10-06/cache-1.stderr | 0 .../evidence/m2-2026-10-06/cache-1.stdout | 220 ++++++ .../evidence/m2-2026-10-06/cache-help.stderr | 0 .../evidence/m2-2026-10-06/cache-help.stdout | 10 + .../m2-2026-10-06/cache-too-small.stderr | 1 + .../m2-2026-10-06/cache-too-small.stdout | 0 .../evidence/m2-2026-10-06/compare-0.stderr | 0 .../evidence/m2-2026-10-06/compare-0.stdout | 267 +++++++ .../evidence/m2-2026-10-06/compare-1.stderr | 0 .../evidence/m2-2026-10-06/compare-1.stdout | 267 +++++++ .../evidence/m2-2026-10-06/disabled.stderr | 0 .../evidence/m2-2026-10-06/disabled.stdout | 139 ++++ .../evidence/m2-2026-10-06/enabled.stderr | 0 .../evidence/m2-2026-10-06/enabled.stdout | 139 ++++ .../evidence/m2-2026-10-06/fixture-0.bin | 1 + .../evidence/m2-2026-10-06/fixture-1.bin | 1 + .../evidence/m2-2026-10-06/format.stderr | 0 .../evidence/m2-2026-10-06/format.stdout | 0 .../evidence/m2-2026-10-06/help.stderr | 0 .../evidence/m2-2026-10-06/help.stdout | 10 + .../evidence/m2-2026-10-06/qualification.json | 234 ++++++ .../evidence/m2-2026-10-06/too-small.bin | 1 + .../evidence/m2-2026-10-06/unit.stderr | 5 + .../evidence/m2-2026-10-06/unit.stdout | 0 .../evidence/m2-2026-10-06/zig-version.stderr | 0 .../evidence/m2-2026-10-06/zig-version.stdout | 1 + docs/agent-cast/evidence/m2-review.json | 34 + docs/agent-cast/evidence/m2.json | 18 + .../evidence/publication-route.json | 15 + docs/agent-cast/milestones.md | 40 +- docs/agent-cast/tasks.json | 117 ++- experiments/agent_cast/README.md | 44 +- experiments/agent_cast/build.zig | 18 +- experiments/agent_cast/src/cache.zig | 570 ++++++++++++++ experiments/agent_cast/src/cache_demo.zig | 206 +++++ .../agent_cast/src/cached_executor.zig | 133 ++++ experiments/agent_cast/src/executor.zig | 4 +- experiments/agent_cast/src/subscribers.zig | 731 ++++++++++++++++++ experiments/agent_cast/src/tests.zig | 5 + experiments/agent_cast/src/wire.zig | 245 ++++++ scripts/qualify_agent_cast.py | 80 +- 47 files changed, 3721 insertions(+), 61 deletions(-) create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/build.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/build.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/cache-0.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/cache-0.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/cache-1.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/cache-1.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/cache-help.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/cache-help.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/cache-too-small.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/cache-too-small.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/compare-0.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/compare-0.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/compare-1.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/compare-1.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/disabled.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/disabled.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/enabled.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/enabled.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/fixture-0.bin create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/fixture-1.bin create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/format.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/format.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/help.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/help.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/qualification.json create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/too-small.bin create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/unit.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/unit.stdout create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/zig-version.stderr create mode 100644 docs/agent-cast/evidence/m2-2026-10-06/zig-version.stdout create mode 100644 docs/agent-cast/evidence/m2-review.json create mode 100644 docs/agent-cast/evidence/m2.json create mode 100644 docs/agent-cast/evidence/publication-route.json create mode 100644 experiments/agent_cast/src/cache.zig create mode 100644 experiments/agent_cast/src/cache_demo.zig create mode 100644 experiments/agent_cast/src/cached_executor.zig create mode 100644 experiments/agent_cast/src/subscribers.zig create mode 100644 experiments/agent_cast/src/wire.zig diff --git a/docs/agent-cast-architecture.html b/docs/agent-cast-architecture.html index f93251bdbb..d8b7dd9f2c 100644 --- a/docs/agent-cast-architecture.html +++ b/docs/agent-cast-architecture.html @@ -302,7 +302,7 @@

The reset contract

11

Start with a slice
that can disprove the design

Establish equivalent useful work and real interoperability before expanding into a general execution platform.

-
Foundation locally exercised
Implementation checkpoint, 6 October 2026. The standalone Zig 0.16.0 experiment captures actual file bytes and preserves ten logical receipts while grouping eight approved reads into four backing reads. Eighteen focused tests and two independent fixture comparisons passed locally. This proves the bounded embedded workload; shared completed caching, broker clients, supervision, and harness integration are later milestones. CI is pending. Follow the milestones, task ledger, model assignments, and prototype instructions.
1

Attribute current costs

Trace current optimized baselines. Separate normalization, admission, useful work, service setup, rendering, and persistence; record logical and physical operation counts.

2

Define Tool IR and trusted operation contracts

Preserve original IDs, arguments, dependencies, result contracts, and budgets. Whitelist immutable fixture reads; default unknown registrations and commands to opaque.

3

Add completed-value reuse inside admitted execution

Implement one immutable read executor and current-authority checks. Reuse certified successful values through the existing renderer, with a new logical receipt and explicit provenance.

4

Qualify inflight sharing and bounded batches

Keep independent subscriber cancellation, deadlines, output credits, and audit. Add deterministic scheduling and a cost model that can choose the original direct path.

5

Prove broker and libfx interoperability

Use Python/Node clients and a fixture-driven host tool. Keep descriptors identical. Add one scoped pooled HTTP client while executing every required request.

6

Qualify isolated tasks and failure recovery

Integrate one existing Linux backend. Test clean reset, stale plans/epochs, worker loss, output pressure, and effect uncertainty without automatic replay.

7

Integrate harnesses and profile further passes

Add Hermes/DeepSeek and Harbor/Inspect adapters. Expand certified lowering, service actors, or plan-template reuse only where measurements justify it. Review terminals separately.

+
Foundation locally exercised
Implementation checkpoint, 6 October 2026. The standalone Zig 0.16.0 foundation captures actual bytes and preserves ten logical receipts while grouping eight approved reads into four backing reads. A completed-value cache serves four new admitted logical calls from owned values and rejects three unauthorized or stale consumers without output. The refreshed qualification passed 52 tests and exercised both built binaries against two different actual fixtures. Subscriber and framing contracts have unit evidence; real broker clients, supervision, and harness integration remain pending. CI is pending. Follow the milestones, task ledger, model assignments, and prototype instructions.
1

Attribute current costs

Trace current optimized baselines. Separate normalization, admission, useful work, service setup, rendering, and persistence; record logical and physical operation counts.

2

Define Tool IR and trusted operation contracts

Preserve original IDs, arguments, dependencies, result contracts, and budgets. Whitelist immutable fixture reads; default unknown registrations and commands to opaque.

3

Add completed-value reuse inside admitted execution

Implement one immutable read executor and current-authority checks. Reuse certified successful values through the existing renderer, with a new logical receipt and explicit provenance.

4

Qualify inflight sharing and bounded batches

Keep independent subscriber cancellation, deadlines, output credits, and audit. Add deterministic scheduling and a cost model that can choose the original direct path.

5

Prove broker and libfx interoperability

Use Python/Node clients and a fixture-driven host tool. Keep descriptors identical. Add one scoped pooled HTTP client while executing every required request.

6

Qualify isolated tasks and failure recovery

Integrate one existing Linux backend. Test clean reset, stale plans/epochs, worker loss, output pressure, and effect uncertainty without automatic replay.

7

Integrate harnesses and profile further passes

Add Hermes/DeepSeek and Harbor/Inspect adapters. Expand certified lowering, service actors, or plan-template reuse only where measurements justify it. Review terminals separately.

Engineering target

Dispatch p99 < 1 ms

Define payload, concurrency, load, queue policy, and authorization-to-start timestamps. Report durable completion separately.

Engineering target

Idle broker ≤ 16 MiB

Count arenas, queues, stacks, and metadata. Also report workers, pools, guests, and total cost per task; moving allocations elsewhere does not establish efficiency.

Correctness gate

Equal logical calls, outputs, errors, audit, limits, authority, and terminal effects. Test disabled/enabled passes and reject stale reads, unauthorized fan-out, duplicate effects, and state leaks.

Performance gate

Compare warm, persistent, pooled baselines under equal resources. Preserve fx's Linux startup budget through lazy service connection.

Disable a pass if effect declarations are unavailable or contradicted, or if planning, hashing, guards, and fan-out erase its gains. Reject improvements based on skipped logical actions, weaker isolation, changed outputs, or stale observations. Use the unchanged fallback only while its exact authority and applicable guards remain valid. Stale authority or generations require rejection or fresh host preparation/admission.

The prototype uses fixture-owned admissions and two logical agent identities in one process. Backing read counts refer to captured-content reader invocations, not OS reads or elapsed time. The native transport probe above is a separate experiment. No production fx integration or sandbox qualification has been performed.

diff --git a/docs/agent-cast/events.jsonl b/docs/agent-cast/events.jsonl index 5a7a587236..20306cdb0f 100644 --- a/docs/agent-cast/events.jsonl +++ b/docs/agent-cast/events.jsonl @@ -4,3 +4,7 @@ {"sequence": 4, "observed_at_utc": "2026-10-06T19:55:56.443491+00:00", "event": "review_finding_resolved", "details": {"task": "AC-0103", "evidence": "docs/agent-cast/evidence/contract-review.json", "finding": "Borrowed admission data must remain unchanged; snapshot owns captured bytes/path."}} {"sequence": 5, "observed_at_utc": "2026-10-06T19:55:56.443491+00:00", "event": "local_verification_recorded", "details": {"tasks": ["AC-0001", "AC-0002", "AC-0003", "AC-0101", "AC-0102", "AC-0103", "AC-0104"], "zig_unit_tests": 18, "ledger_cli_tests": 10, "independent_actual_fixtures": 2, "logical_receipts_per_mode": 10, "backing_reader_calls": {"disabled": 8, "enabled": 4}, "performance_claim": null}} {"sequence": 6, "observed_at_utc": "2026-10-06T19:55:56.443491+00:00", "event": "publication_gate_pending", "details": {"task": "AC-0105", "evidence": "docs/agent-cast/evidence/checkpoint.json", "reason": "Available GitHub account has READ access; no exact-commit CI result exists."}} +{"sequence": 7, "observed_at_utc": "2026-10-06T20:02:55.247521+00:00", "event": "overnight_goal_started", "details": {"scope": "M2 through M8 implemented and independent red-team validation", "active_task": "AC-0201", "token_budget": null, "heavy_job_policy": "one per host", "infra_inventory": "No known local Linux container runtime; existing pinned Docker CI lane identified", "red_team_foundation_finding": "Test wording confused snapshot hashing with successful reader invocation; wording repaired, source-dependent proof must be refreshed."}} +{"sequence": 8, "observed_at_utc": "2026-10-06T20:21:58.277937+00:00", "event": "m2_completed_cache_verified", "details": {"evidence": "docs/agent-cast/evidence/m2.json", "unit_tests": 52, "qualified_actual_fixtures": 2, "cache_receipts_per_fixture": 11}} +{"sequence": 9, "observed_at_utc": "2026-10-06T20:21:58.277937+00:00", "event": "red_team_findings_repaired", "details": {"evidence": "docs/agent-cast/evidence/m2-review.json", "actual_broker_validation": "pending"}} +{"sequence": 10, "observed_at_utc": "2026-10-06T20:21:58.277937+00:00", "event": "fork_publication_route_created", "details": {"evidence": "docs/agent-cast/evidence/publication-route.json", "ci_result": "not_run"}} diff --git a/docs/agent-cast/evidence/m2-2026-10-06/build.stderr b/docs/agent-cast/evidence/m2-2026-10-06/build.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/build.stdout b/docs/agent-cast/evidence/m2-2026-10-06/build.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/cache-0.stderr b/docs/agent-cast/evidence/m2-2026-10-06/cache-0.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/cache-0.stdout b/docs/agent-cast/evidence/m2-2026-10-06/cache-0.stdout new file mode 100644 index 0000000000..cdc813f0f7 --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/cache-0.stdout @@ -0,0 +1,220 @@ +{ + "experimental_scope": "M2 synchronous owned completed-value cache; exact fresh fixture admissions; no production registry or performance claim", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "fixture_verified": true, + "logical_call_count": 11, + "limits": { + "max_entries": 8, + "max_bytes": 32768 + }, + "batch_limits": { + "max_result_bytes": 4096 + }, + "cache": { + "entries": 4, + "retained_bytes": 3388, + "metadata_bytes": 2112, + "hits": 4, + "misses": 4, + "backing_reads": 4, + "evictions": 0, + "not_retained": 0, + "rejected": 3 + }, + "retained_accounting": "allocated entry table plus owned key strings and outputs; allocator bookkeeping and caller-owned output copies excluded", + "batches": [ + { + "name": "cold-agent-a", + "logical_calls": 4, + "cache_hits": 0, + "cache_misses": 4, + "backing_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 4, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + } + ] + }, + { + "name": "completed-values-agent-b", + "logical_calls": 4, + "cache_hits": 4, + "cache_misses": 0, + "backing_reads": 0, + "receipts": [ + { + "logical_id": 5, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 7, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + } + ] + }, + { + "name": "rejected-consumers", + "logical_calls": 3, + "cache_hits": 0, + "cache_misses": 0, + "backing_reads": 0, + "receipts": [ + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 11, + "agent_id": "mismatched-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "binding_mismatch", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/m2-2026-10-06/cache-1.stderr b/docs/agent-cast/evidence/m2-2026-10-06/cache-1.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/cache-1.stdout b/docs/agent-cast/evidence/m2-2026-10-06/cache-1.stdout new file mode 100644 index 0000000000..316e026b5c --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/cache-1.stdout @@ -0,0 +1,220 @@ +{ + "experimental_scope": "M2 synchronous owned completed-value cache; exact fresh fixture admissions; no production registry or performance claim", + "immutable_input_sha256": "3d55b51a98d198e5b5bf86d066c92f53c1349d4636253f2da34c573976850cf8", + "immutable_input_bytes": 1024, + "fixture_verified": true, + "logical_call_count": 11, + "limits": { + "max_entries": 8, + "max_bytes": 32768 + }, + "batch_limits": { + "max_result_bytes": 4096 + }, + "cache": { + "entries": 4, + "retained_bytes": 3388, + "metadata_bytes": 2112, + "hits": 4, + "misses": 4, + "backing_reads": 4, + "evictions": 0, + "not_retained": 0, + "rejected": 3 + }, + "retained_accounting": "allocated entry table plus owned key strings and outputs; allocator bookkeeping and caller-owned output copies excluded", + "batches": [ + { + "name": "cold-agent-a", + "logical_calls": 4, + "cache_hits": 0, + "cache_misses": 4, + "backing_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 2, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 4, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + } + ] + }, + { + "name": "completed-values-agent-b", + "logical_calls": 4, + "cache_hits": 4, + "cache_misses": 0, + "backing_reads": 0, + "receipts": [ + { + "logical_id": 5, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + } + ] + }, + { + "name": "rejected-consumers", + "logical_calls": 3, + "cache_hits": 0, + "cache_misses": 0, + "backing_reads": 0, + "receipts": [ + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 11, + "agent_id": "mismatched-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "binding_mismatch", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/m2-2026-10-06/cache-help.stderr b/docs/agent-cast/evidence/m2-2026-10-06/cache-help.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/cache-help.stdout b/docs/agent-cast/evidence/m2-2026-10-06/cache-help.stdout new file mode 100644 index 0000000000..a72c50ba4f --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/cache-help.stdout @@ -0,0 +1,10 @@ +Usage: agent-cast-cache-demo --fixture PATH + +Capture a real file of 256 bytes to 1 MiB. Run two independent admitted +four-window batches, then a batch of denied, stale, and mismatched calls. +JSON includes complete output bytes and actual hit, miss, and backing counts. +Hits still validate fixture admission, hash the capture, and copy output. +This synchronous experiment does not implement production registry validation. + + --fixture PATH Actual file to capture once + --help Show this help diff --git a/docs/agent-cast/evidence/m2-2026-10-06/cache-too-small.stderr b/docs/agent-cast/evidence/m2-2026-10-06/cache-too-small.stderr new file mode 100644 index 0000000000..3dd2a97737 --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/cache-too-small.stderr @@ -0,0 +1 @@ +agent-cast-cache-demo: FixtureTooSmall; see --help diff --git a/docs/agent-cast/evidence/m2-2026-10-06/cache-too-small.stdout b/docs/agent-cast/evidence/m2-2026-10-06/cache-too-small.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/compare-0.stderr b/docs/agent-cast/evidence/m2-2026-10-06/compare-0.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/compare-0.stdout b/docs/agent-cast/evidence/m2-2026-10-06/compare-0.stdout new file mode 100644 index 0000000000..1c374cb305 --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/compare-0.stdout @@ -0,0 +1,267 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "compare", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": true, + "runs": [ + { + "mode": "disabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 8, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 4, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 5, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 6, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 7, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + }, + { + "mode": "enabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/m2-2026-10-06/compare-1.stderr b/docs/agent-cast/evidence/m2-2026-10-06/compare-1.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/compare-1.stdout b/docs/agent-cast/evidence/m2-2026-10-06/compare-1.stdout new file mode 100644 index 0000000000..91330d8055 --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/compare-1.stdout @@ -0,0 +1,267 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "compare", + "immutable_input_sha256": "3d55b51a98d198e5b5bf86d066c92f53c1349d4636253f2da34c573976850cf8", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": true, + "runs": [ + { + "mode": "disabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 8, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 4, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 5, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 6, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 7, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + }, + { + "mode": "enabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/m2-2026-10-06/disabled.stderr b/docs/agent-cast/evidence/m2-2026-10-06/disabled.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/disabled.stdout b/docs/agent-cast/evidence/m2-2026-10-06/disabled.stdout new file mode 100644 index 0000000000..deee42bd2d --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/disabled.stdout @@ -0,0 +1,139 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "disabled", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": null, + "runs": [ + { + "mode": "disabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 8, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 4, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 5, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 6, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 7, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/m2-2026-10-06/enabled.stderr b/docs/agent-cast/evidence/m2-2026-10-06/enabled.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/enabled.stdout b/docs/agent-cast/evidence/m2-2026-10-06/enabled.stdout new file mode 100644 index 0000000000..541d8a14fa --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/enabled.stdout @@ -0,0 +1,139 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "enabled", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": null, + "runs": [ + { + "mode": "enabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/m2-2026-10-06/fixture-0.bin b/docs/agent-cast/evidence/m2-2026-10-06/fixture-0.bin new file mode 100644 index 0000000000..092edfcdaa --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/fixture-0.bin @@ -0,0 +1 @@ +fixture-0-window-00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfixture-0-window-01BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBfixture-0-window-02CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCfixture-0-window-03DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDfixture-0-window-04EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEfixture-0-window-05FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfixture-0-window-06GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGfixture-0-window-07HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHfixture-0-window-08IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIfixture-0-window-09JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJfixture-0-window-10KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKfixture-0-window-11LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLfixture-0-window-12MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMfixture-0-window-13NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNfixture-0-window-14OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOfixture-0-window-15PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP \ No newline at end of file diff --git a/docs/agent-cast/evidence/m2-2026-10-06/fixture-1.bin b/docs/agent-cast/evidence/m2-2026-10-06/fixture-1.bin new file mode 100644 index 0000000000..7023653028 --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/fixture-1.bin @@ -0,0 +1 @@ +fixture-1-window-00BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBfixture-1-window-01CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCfixture-1-window-02DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDfixture-1-window-03EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEfixture-1-window-04FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfixture-1-window-05GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGfixture-1-window-06HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHfixture-1-window-07IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIfixture-1-window-08JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJfixture-1-window-09KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKfixture-1-window-10LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLfixture-1-window-11MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMfixture-1-window-12NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNfixture-1-window-13OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOfixture-1-window-14PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPfixture-1-window-15QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ \ No newline at end of file diff --git a/docs/agent-cast/evidence/m2-2026-10-06/format.stderr b/docs/agent-cast/evidence/m2-2026-10-06/format.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/format.stdout b/docs/agent-cast/evidence/m2-2026-10-06/format.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/help.stderr b/docs/agent-cast/evidence/m2-2026-10-06/help.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/help.stdout b/docs/agent-cast/evidence/m2-2026-10-06/help.stdout new file mode 100644 index 0000000000..c4d8831f20 --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/help.stdout @@ -0,0 +1,10 @@ +Usage: agent-cast-demo --fixture PATH [--mode compare|enabled|disabled] + +Capture one file of 256 bytes to 1 MiB and exercise an experimental embedded +immutable content reader. Compare mode checks every logical receipt and prints +JSON with actual physical read counts. This is a correctness demonstration. +Host admissions are fixture contracts, with no production permission checks. + + --fixture PATH File to capture once into owned immutable storage + --mode MODE Select execution mode (default: compare) + --help Show this help diff --git a/docs/agent-cast/evidence/m2-2026-10-06/qualification.json b/docs/agent-cast/evidence/m2-2026-10-06/qualification.json new file mode 100644 index 0000000000..2d3283ff89 --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/qualification.json @@ -0,0 +1,234 @@ +{ + "schema_version": 1, + "recorded_at_utc": "2026-10-06T20:17:08.168395+00:00", + "scope": "local foundation/cache correctness and subscriber/framing unit checks; logical agents in one process; no broker or isolation qualification", + "platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O", + "machine": "arm64", + "source_sha256": { + "experiments/agent_cast/build.zig": "a8720adf37fc415ddf4f647d68f642f007ced0a996fbef66b8b730d1bea5e0d6", + "experiments/agent_cast/src/cache.zig": "7b26432c7b196c97589048aa2a05e26b0f7f91526ed23ce2f3714148ced207d0", + "experiments/agent_cast/src/cache_demo.zig": "f75346013a621eab61afcc8a34a29860ed95ab510f787c298c5fa9c3a771ea33", + "experiments/agent_cast/src/cached_executor.zig": "60d3f98158ab47f469f8483a9e5ff20ddc2a9a5f791c09d5441d9fff96b45992", + "experiments/agent_cast/src/contracts.zig": "d11df3241d4d9c5441da4b85250d192fb0e890ff7bdd613368c917a71ca2470e", + "experiments/agent_cast/src/demo.zig": "1b085e57e0663722c0a15a7af8b65f14a2a3d5b377ccaf50e11589e6276fb032", + "experiments/agent_cast/src/executor.zig": "5453b7d0505222bb34e6a3bd83522695cd57beb714858949341646ecde51380b", + "experiments/agent_cast/src/optimizer.zig": "e33266af767f303d2c3918a27d514afd1e62913aad495fdfacf6b42dcfa55a63", + "experiments/agent_cast/src/subscribers.zig": "034a2ecd3e181b61794987a3128ba93a5b5cb2c0bf47f9164ecd9a5899ed302f", + "experiments/agent_cast/src/tests.zig": "c03790db06fd05069fbd65c8e7d30f3867eba7644a36ab1edd290c6ef9551d09", + "experiments/agent_cast/src/wire.zig": "2696b554acdbebc2efde48b20085abaf528e92710c302bf823995ed71cfb9547" + }, + "checks": [ + { + "name": "zig-version", + "argv": [ + "", + "version" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0361342909745872, + "stdout": "zig-version.stdout", + "stderr": "zig-version.stderr" + }, + { + "name": "format", + "argv": [ + "", + "fmt", + "--check", + "src/", + "build.zig" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.038132166024297476, + "stdout": "format.stdout", + "stderr": "format.stderr" + }, + { + "name": "build", + "argv": [ + "", + "build", + "-Doptimize=ReleaseSafe", + "-j1" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.11184433312155306, + "stdout": "build.stdout", + "stderr": "build.stderr" + }, + { + "name": "unit", + "argv": [ + "", + "build", + "test", + "-Doptimize=ReleaseSafe", + "-j1", + "--summary", + "all" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 7.194370249984786, + "stdout": "unit.stdout", + "stderr": "unit.stderr" + }, + { + "name": "help", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--help" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0029537498485296965, + "stdout": "help.stdout", + "stderr": "help.stderr" + }, + { + "name": "cache-help", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-cache-demo", + "--help" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0028264999855309725, + "stdout": "cache-help.stdout", + "stderr": "cache-help.stderr" + }, + { + "name": "compare-0", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-0.bin", + "--mode", + "compare" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0026595001108944416, + "stdout": "compare-0.stdout", + "stderr": "compare-0.stderr" + }, + { + "name": "cache-0", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-cache-demo", + "--fixture", + "/fixture-0.bin" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0026218330021947622, + "stdout": "cache-0.stdout", + "stderr": "cache-0.stderr" + }, + { + "name": "compare-1", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-1.bin", + "--mode", + "compare" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0025265419390052557, + "stdout": "compare-1.stdout", + "stderr": "compare-1.stderr" + }, + { + "name": "cache-1", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-cache-demo", + "--fixture", + "/fixture-1.bin" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.002519291825592518, + "stdout": "cache-1.stdout", + "stderr": "cache-1.stderr" + }, + { + "name": "enabled", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-0.bin", + "--mode", + "enabled" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0026064161211252213, + "stdout": "enabled.stdout", + "stderr": "enabled.stderr" + }, + { + "name": "disabled", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-0.bin", + "--mode", + "disabled" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0025547919794917107, + "stdout": "disabled.stdout", + "stderr": "disabled.stderr" + }, + { + "name": "cache-too-small", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-cache-demo", + "--fixture", + "/too-small.bin" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 1, + "seconds": 0.0026719998568296432, + "stdout": "cache-too-small.stdout", + "stderr": "cache-too-small.stderr" + } + ], + "ci_status": "not_run", + "performance_claim": null, + "ok": true, + "zig_version": "0.16.0", + "unit_tests_passed": 52, + "binary_sha256": "30a7de8ea96cbe83b10a6d19364fab5f304be03eac286fa46e42c509f9f262e0", + "cache_binary_sha256": "98c7b732b1c88099f80829f994dd44fdc5988fb9085c0f156f1d0c1619bc4189", + "fixtures": [ + { + "sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "bytes": 1024, + "comparison": "compare-0.stdout", + "cache_comparison": "cache-0.stdout", + "logical_receipts_per_mode": 10, + "cache_logical_receipts": 11, + "disabled_backing_reads": 8, + "enabled_backing_reads": 4 + }, + { + "sha256": "3d55b51a98d198e5b5bf86d066c92f53c1349d4636253f2da34c573976850cf8", + "bytes": 1024, + "comparison": "compare-1.stdout", + "cache_comparison": "cache-1.stdout", + "logical_receipts_per_mode": 10, + "cache_logical_receipts": 11, + "disabled_backing_reads": 8, + "enabled_backing_reads": 4 + } + ], + "path_normalization": "Only machine-specific checkout, toolchain and receipt directory prefixes replaced; hashes, exits, timings and raw logs unchanged.", + "qualifier_sha256": "157002057aaaec3ec370460bc8daa6ef2672f71426ef7c86ce3dc6f980db471f" +} diff --git a/docs/agent-cast/evidence/m2-2026-10-06/too-small.bin b/docs/agent-cast/evidence/m2-2026-10-06/too-small.bin new file mode 100644 index 0000000000..ed0003c5be --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/too-small.bin @@ -0,0 +1 @@ +actual-invalid-input \ No newline at end of file diff --git a/docs/agent-cast/evidence/m2-2026-10-06/unit.stderr b/docs/agent-cast/evidence/m2-2026-10-06/unit.stderr new file mode 100644 index 0000000000..6d22499fdb --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/unit.stderr @@ -0,0 +1,5 @@ +Build Summary: 3/3 steps succeeded; 52/52 tests passed +test success ++- run test 52 pass (52 total) 11ms MaxRSS:6M + +- compile test ReleaseSafe native success 7s MaxRSS:478M + diff --git a/docs/agent-cast/evidence/m2-2026-10-06/unit.stdout b/docs/agent-cast/evidence/m2-2026-10-06/unit.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/zig-version.stderr b/docs/agent-cast/evidence/m2-2026-10-06/zig-version.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/m2-2026-10-06/zig-version.stdout b/docs/agent-cast/evidence/m2-2026-10-06/zig-version.stdout new file mode 100644 index 0000000000..04a373efe6 --- /dev/null +++ b/docs/agent-cast/evidence/m2-2026-10-06/zig-version.stdout @@ -0,0 +1 @@ +0.16.0 diff --git a/docs/agent-cast/evidence/m2-review.json b/docs/agent-cast/evidence/m2-review.json new file mode 100644 index 0000000000..9d619de7e5 --- /dev/null +++ b/docs/agent-cast/evidence/m2-review.json @@ -0,0 +1,34 @@ +{ + "observed_at_utc": "2026-10-06T20:21:58.277937+00:00", + "requested_model": "gpt-6-astra", + "source": "Independent static review responses in this session, followed by parent source inspection and actual combined qualification.", + "findings": [ + { + "finding": "Insertion could temporarily exceed nominal cache allocation quota", + "resolution": "Eviction precedes allocation; tracking-allocator peak test exercises private insertion and would fail original order." + }, + { + "finding": "Optional retention OOM could replace successful tool output with an error", + "resolution": "Copied success survives optional retention OOM; induced-failure sweeps cover empty cache and replacement after eviction." + }, + { + "finding": "Batch output copies could approach 1GiB outside tiny cache quota", + "resolution": "Explicit aggregate reservation defaults64KiB and has16MiB hard cap; overflow/projected excess reject before cache access." + }, + { + "finding": "Subscriber successful settlement discarded pending/undrained output", + "resolution": "Declared result size required; all active unexpired consumers must deliver exactly it, with no pending reservation. Four regression blocks cover partial/blocked/zero/cancelled consumers." + }, + { + "finding": "Explicit null RPC IDs were treated as notifications; null params bypassed object profile", + "resolution": "Dynamic parse preserves field presence; present non-string ID/non-object params rejects, missing ID stays notification." + } + ], + "runtime_evidence": "m2-2026-10-06/qualification.json", + "limits": [ + "Static review itself ran no code. Parent qualification covers52 unit tests and actual foundation/cache CLI paths.", + "Subscriber/framing modules have unit evidence; real broker/client cancellation and networking remain pending.", + "Fixture authority does not implement production permission issuance.", + "No end-to-end speedup measured; admitted cache hits still hash capture and copy output." + ] +} diff --git a/docs/agent-cast/evidence/m2.json b/docs/agent-cast/evidence/m2.json new file mode 100644 index 0000000000..3eaa7be3fd --- /dev/null +++ b/docs/agent-cast/evidence/m2.json @@ -0,0 +1,18 @@ +{ + "observed_at_utc": "2026-10-06T20:21:58.277937+00:00", + "status": "verified_local", + "scope": "Completed immutable-value cache in one process; subscriber and wire contracts unit-tested; actual multi-client broker pending.", + "qualification": "m2-2026-10-06/qualification.json", + "review": "m2-review.json", + "unit_tests_passed": 52, + "actual_fixture_comparisons": 2, + "cache_logical_receipts_per_fixture": 11, + "cold_backing_reader_calls": 4, + "second_agent_cache_hits": 4, + "second_agent_backing_reader_calls": 0, + "rejected_consumers_per_fixture": 3, + "successful_cli_stderr_clean": true, + "aggregate_output_copy_hard_limit_bytes": 16777216, + "performance_claim": null, + "ci_status": "not_run" +} diff --git a/docs/agent-cast/evidence/publication-route.json b/docs/agent-cast/evidence/publication-route.json new file mode 100644 index 0000000000..adb9a612fd --- /dev/null +++ b/docs/agent-cast/evidence/publication-route.json @@ -0,0 +1,15 @@ +{ + "observed_at_utc": "2026-10-06T20:21:58.277937+00:00", + "source": [ + "gh api user --jq .login", + "gh api --method POST repos/vercel-labs/fx/forks", + "gh repo view fork --json viewerPermission" + ], + "authenticated_api_account": "alvgeppetto", + "canonical_repository": "vercel-labs/fx", + "fork_created": "alvgeppetto/fx", + "fork_permission": "ADMIN", + "draft_pr": null, + "exact_commit_ci": "not_run", + "note": "Earlier gh auth status exposed a keychain account label. User endpoint is authoritative for the account used by these API calls. No account switch or credential value was requested." +} diff --git a/docs/agent-cast/milestones.md b/docs/agent-cast/milestones.md index 4252482c95..038c8cc65d 100644 --- a/docs/agent-cast/milestones.md +++ b/docs/agent-cast/milestones.md @@ -112,21 +112,25 @@ CI result. ## Current checkpoint -M0 and the bounded M1 slice have local evidence in -[evidence/foundation.json](evidence/foundation.json) and -[evidence/ledger.json](evidence/ledger.json). The qualification observed 18 Zig -tests, 10 ledger CLI tests, and two different actual fixtures. Each comparison -retained ten logical receipts while using eight or four captured-content reader -invocations. This is correctness evidence, with no measured speedup claim. - -The available GitHub account has read access to `vercel-labs/fx`. -[evidence/checkpoint.json](evidence/checkpoint.json) records the publication and -CI gate. The added `Agent-cast Foundation` workflow builds and exercises this -prototype on Linux and macOS when a permitted contributor publishes it. Its -checks supplement the existing fx product checks. - -The next task is `AC-0201`: owned bounded completed-value caching. Verify a new -logical admission before every hit, keep task/domain/snapshot/contract boundaries -in the key, and prove eviction plus output lifetime. Inflight cancellation and -actual multi-client sharing follow as separate tasks. Refine later milestone -scopes into concrete file ownership before dispatching them. +M0, the bounded M1 slice, and M2 completed-value caching have local evidence in +[evidence/m2.json](evidence/m2.json) and +[evidence/ledger.json](evidence/ledger.json). The refreshed qualification observed +52 Zig tests and two different actual fixtures. Foundation comparisons retain +ten logical receipts while using eight or four captured-content reader calls. +Cache comparisons retain eleven receipts, including four new logical consumers +served from completed values and three rejected consumers with no output. +The subscriber and framing contracts have unit evidence; their actual broker +runtime qualification remains pending. No measured speedup is claimed. + +The available account has read access to `vercel-labs/fx`; an owned fork provides +the publication route recorded in +[evidence/publication-route.json](evidence/publication-route.json). Draft PR and +exact-commit CI are pending. The `Agent-cast Foundation` workflow builds and +exercises this prototype on Linux and macOS. Its checks supplement the existing +fx product checks. + +The current work is `AC-0301`: broker framing and a host-owned authority registry, +followed by an actual broker executable. `AC-0202` still needs a real shared +producer and cancellation interaction. Later work retains its own runtime, +recovery, containment, service and red-team gates. Refine those scopes into +concrete file ownership before dispatching them. diff --git a/docs/agent-cast/tasks.json b/docs/agent-cast/tasks.json index f4f3f505a2..27a5489d15 100644 --- a/docs/agent-cast/tasks.json +++ b/docs/agent-cast/tasks.json @@ -7,7 +7,7 @@ "design": "docs/agent-cast-architecture.html", "scope": "Foundation first; no production permissions, broker, isolation or performance claim yet." }, - "last_updated_utc": "2026-10-06T19:55:56.443491+00:00", + "last_updated_utc": "2026-10-06T20:21:58.277937+00:00", "routing_policy": { "routine_model": "gpt-6-luna", "implementation_model": "gpt-6.1-sol", @@ -16,7 +16,7 @@ "repair_policy": "At most two repair rounds per delegated scope before parent review", "spending_limit": null, "usage_policy": "Null means unavailable; actual token counts require a provider receipt. No inferred totals.", - "current_batch": "M0/M1 locally verified; M2 queued; publication/CI pending" + "current_batch": "Completed cache locally verified; subscribers unit-tested; broker authority in progress; fork CI route available" }, "milestones": [ { @@ -251,14 +251,15 @@ ], "evidence": [ "docs/agent-cast/evidence/contract-review.json", - "docs/agent-cast/evidence/foundation.json" + "docs/agent-cast/evidence/foundation.json", + "docs/agent-cast/evidence/m2-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" }, { "id": "AC-0102", @@ -286,14 +287,15 @@ "Built demo uses input identity and actual executor counts; labels two logical agents without claiming multi-process execution" ], "evidence": [ - "docs/agent-cast/evidence/foundation.json" + "docs/agent-cast/evidence/foundation.json", + "docs/agent-cast/evidence/m2-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" }, { "id": "AC-0103", @@ -320,14 +322,15 @@ ], "evidence": [ "docs/agent-cast/evidence/contract-review.json", - "docs/agent-cast/evidence/foundation.json" + "docs/agent-cast/evidence/foundation.json", + "docs/agent-cast/evidence/m2-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" }, { "id": "AC-0104", @@ -356,14 +359,15 @@ ], "evidence": [ "docs/agent-cast/evidence/foundation.json", - "docs/agent-cast/evidence/foundation-2026-10-06/qualification.json" + "docs/agent-cast/evidence/foundation-2026-10-06/qualification.json", + "docs/agent-cast/evidence/m2-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T19:55:56.443491+00:00" + "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" }, { "id": "AC-0105", @@ -389,79 +393,92 @@ "Record absent publication authorization or unavailable checks; do not invent a successful gate" ], "evidence": [ - "docs/agent-cast/evidence/checkpoint.json" + "docs/agent-cast/evidence/checkpoint.json", + "docs/agent-cast/evidence/publication-route.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T19:55:56.443491+00:00", - "pending_reason": "Local CI configuration exists; available GitHub account has READ permission. Exact-commit publication and checks are pending." + "updated_at_utc": "2026-10-06T20:21:58.277937+00:00", + "pending_reason": "Fork publication route now available; draft PR and exact-commit CI pending." }, { "id": "AC-0201", "milestone": "M2", "title": "Add owned bounded completed-value cache", - "status": "planned", + "status": "verified_local", "depends_on": [ "AC-0104" ], "owner": { "role": "implementation", "model": "gpt-6.1-sol", - "reasoning_effort": "high" + "reasoning_effort": "high", + "agent": "/root/agent_cast_core" }, "scope": [ "experiments/agent_cast/src/cache.zig", - "cache-focused tests" + "experiments/agent_cast/src/cached_executor.zig", + "experiments/agent_cast/src/cache_demo.zig", + "experiments/agent_cast/build.zig", + "experiments/agent_cast/src/tests.zig" ], "acceptance": [ "Cache successful immutable canonical values after each current logical admission", "Prove two-agent sharing, domain/task isolation, invalidation, eviction and output lifetime" ], - "evidence": [], + "evidence": [ + "docs/agent-cast/evidence/m2.json", + "docs/agent-cast/evidence/m2-review.json" + ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" }, { "id": "AC-0202", "milestone": "M2", "title": "Qualify inflight sharing and independent consumers", - "status": "planned", + "status": "implemented", "depends_on": [ "AC-0201" ], "owner": { "role": "implementation", "model": "gpt-6.1-sol", - "reasoning_effort": "high" + "reasoning_effort": "high", + "agent": "/root/agent_cast_subscribers" }, "scope": [ - "cache/subscriber contracts", - "cancellation and deadline tests" + "experiments/agent_cast/src/subscribers.zig", + "broker subscriber runtime qualification" ], "acceptance": [ "Cancel one subscriber without stopping work required by another", "Keep independent deadlines/credits/audit/retry provenance; reject stale result delivery" ], - "evidence": [], + "evidence": [ + "docs/agent-cast/evidence/m2-2026-10-06/qualification.json", + "docs/agent-cast/evidence/m2-review.json" + ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + "updated_at_utc": "2026-10-06T20:21:58.277937+00:00", + "pending_reason": "Thirteen subscriber unit tests pass; actual shared producer/cancellation runtime interaction remains pending broker composition." }, { "id": "AC-0301", "milestone": "M3", "title": "Implement framed local broker and scoped enrollment", - "status": "planned", + "status": "in_progress", "depends_on": [ "AC-0202" ], @@ -471,8 +488,10 @@ "reasoning_effort": "high" }, "scope": [ + "experiments/agent_cast/src/wire.zig", + "experiments/agent_cast/src/authority.zig", "experiments/agent_cast/src/broker.zig", - "wire/enrollment contracts" + "experimental broker runtime qualification" ], "acceptance": [ "Bounds, framing, epochs, roles, authority and reconnect semantics tested", @@ -484,7 +503,8 @@ "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + "updated_at_utc": "2026-10-06T20:21:58.277937+00:00", + "implementation_note": "Parent authored framed strict JSON-RPC parser; Sol is implementing host-owned authority registry. Broker executable not yet implemented." }, { "id": "AC-0302", @@ -659,7 +679,8 @@ ], "acceptance": [ "Meaningful optimized baselines, equal work/resources, and independent outcomes", - "Exact-commit gates and local happy path verified; report failed/uncertain results" + "Exact-commit gates and local happy path verified; report failed/uncertain results", + "Independent adversarial scenarios include deliberate broken/featureless controls that must fail qualification." ], "evidence": [], "usage": { @@ -668,6 +689,44 @@ "source": null }, "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + }, + { + "id": "AC-0802", + "milestone": "M8", + "title": "Independently red-team and close material runtime findings", + "status": "planned", + "depends_on": [ + "AC-0801" + ], + "owner": { + "role": "independent critical review", + "model": "gpt-6-astra", + "reasoning_effort": "high" + }, + "scope": [ + "experiments/agent_cast/tests/red_team/", + "docs/agent-cast/evidence/red-team/" + ], + "acceptance": [ + "Exercise real built runtime adversarial authority, cache, framing, cancellation, recovery, reset and service-generation paths.", + "Independently compare required and extra effects; qualification rejects deliberate broken controls.", + "Every material finding is repaired and affected checks are rerun; unresolved findings block promotion." + ], + "evidence": [], + "usage": { + "input_tokens": null, + "output_tokens": null, + "source": null + }, + "updated_at_utc": "2026-10-06T20:02:55.247521+00:00" } - ] + ], + "goal": { + "status": "active", + "objective": "Implement M2 through M8 and independently red-team validate the full bounded agent-cast system.", + "requested_at_utc": "2026-10-06T19:59:10+00:00", + "token_budget": null, + "execution": "Overnight goal loop; honor operator stops; one heavy job per host; preserve existing data and failed receipts.", + "completion_rule": "All milestone acceptance and independent adversarial checks supported by current receipts; exact-commit CI and applicable ship gate remain mandatory." + } } diff --git a/experiments/agent_cast/README.md b/experiments/agent_cast/README.md index 7a52356d2a..72852701d2 100644 --- a/experiments/agent_cast/README.md +++ b/experiments/agent_cast/README.md @@ -10,8 +10,9 @@ results while using different backing read groups. Two agent identities appear in one embedded process; multi-process clients belong to the broker milestone. Host admission and current generation values are trusted fixture contracts. -Production permissions, expiry, cancellation, a shared completed-value cache, -worker supervision, service pools, and sandbox containment remain planned. +Production permissions, live expiry, broker cancellation, worker supervision, +service pools, and sandbox containment remain planned. Completed-value caching +is implemented in the separate embedded cache executor described below. ## Build and qualify @@ -25,9 +26,9 @@ PYTHONDONTWRITEBYTECODE=1 python3 scripts/qualify_agent_cast.py --zig "$AGENT_CA The runner creates a new temporary receipt directory and retains its fixtures, logs, and JSON evidence. `--output PATH` selects a new directory and rejects an existing path. It checks formatting, builds with one job, runs focused unit -tests, and drives the built binary against two distinct 1 KiB fixtures. Full -result bytes and rejected consumers are checked independently of the binary's -own parity flag. +tests, and drives the foundation and cache binaries against two distinct 1 KiB +fixtures. Full result bytes, provenance, logical identities, and rejected +consumers are checked independently of the binaries' own verification flags. To run individual checks, change to this directory: @@ -64,6 +65,39 @@ through execution. Receipt output slices borrow the snapshot; consume receipts and destroy executions before destroying the snapshot. Plan-owned arrays have explicit `deinit` methods. +## Completed-value cache + +Run the separately built cache demonstration with the same bounded input: + +```sh +./zig-out/bin/agent-cast-cache-demo --fixture /path/to/fixture +``` + +The first four-window batch executes backing reads. A second admitted agent +requests the same windows through four new logical calls and receives four +cache hits. A third batch of denied, stale, and mismatched consumers receives +no output. Each receipt reports its own identity and cache/backing provenance. + +Every lookup checks fresh exact fixture admission, capture integrity, and the +registered reader contract. Keys and values are owned. FIFO eviction charges +the allocated table plus retained strings and values to the cache quota; +replacement storage fits that quota before allocation. Optional retention +allocation failure preserves an already-copied successful output. + +Returned output copies survive eviction. They consume a separate explicit +batch budget: 64 KiB by default, capped at 16 MiB. Before any cache access, the +batch reserves admitted snapshot-read requested window lengths, including +windows that may later fail dispatch. Excess or overflow rejects the whole +batch without results or cache changes. Allocator overhead and the supplied +snapshot have separate ownership and accounting. + +The subscriber registry enforces independent deadlines, cancellation, credit +and output sequences, stale-handle rejection, and output drainage before +success. The wire module bounds framed payloads and rejects duplicate JSON +fields, excessive nesting, explicit null IDs, and non-object params. Their +current evidence is unit testing. Actual broker connections, shared producer +execution, and asynchronous cancellation qualification remain pending. + ## Work ledger From the fx root: diff --git a/experiments/agent_cast/build.zig b/experiments/agent_cast/build.zig index 356dbcedf6..d070f01cec 100644 --- a/experiments/agent_cast/build.zig +++ b/experiments/agent_cast/build.zig @@ -13,6 +13,16 @@ pub fn build(b: *std.Build) void { }), }); b.installArtifact(demo); + const cache_demo = b.addExecutable(.{ + .name = "agent-cast-cache-demo", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/cache_demo.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); + b.installArtifact(cache_demo); const tests = b.addTest(.{ .root_module = b.createModule(.{ @@ -23,7 +33,7 @@ pub fn build(b: *std.Build) void { }), }); const run_tests = b.addRunArtifact(tests); - const test_step = b.step("test", "Run the bounded foundation tests"); + const test_step = b.step("test", "Run the bounded experiment tests"); test_step.dependOn(&run_tests.step); const run_demo = b.addRunArtifact(demo); @@ -31,4 +41,10 @@ pub fn build(b: *std.Build) void { if (b.args) |args| run_demo.addArgs(args); const run_step = b.step("run", "Run the immutable-reader demonstration"); run_step.dependOn(&run_demo.step); + + const run_cache_demo = b.addRunArtifact(cache_demo); + run_cache_demo.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cache_demo.addArgs(args); + const cache_run_step = b.step("run-cache", "Run the completed-value cache demonstration"); + cache_run_step.dependOn(&run_cache_demo.step); } diff --git a/experiments/agent_cast/src/cache.zig b/experiments/agent_cast/src/cache.zig new file mode 100644 index 0000000000..fe1ec66d60 --- /dev/null +++ b/experiments/agent_cast/src/cache.zig @@ -0,0 +1,570 @@ +const std = @import("std"); +const contracts = @import("contracts.zig"); +const optimizer = @import("optimizer.zig"); +const executor = @import("executor.zig"); + +pub const Limits = struct { max_entries: usize, max_bytes: usize }; +pub const Source = enum { cache, backing, rejected }; + +// Output is caller-owned. Request strings remain borrowed, alive, and unchanged. +// Destroy this result with the allocator supplied to Cache.read. +pub const ReadResult = struct { + receipt: contracts.Receipt, + source: Source, + cache_hit: bool, + cache_miss: bool, + backing_reads: usize, + + pub fn deinit(self: *ReadResult, alloc: std.mem.Allocator) void { + alloc.free(self.receipt.output); + self.* = undefined; + } +}; + +pub const Stats = struct { + entries: usize, + retained_bytes: usize, + metadata_bytes: usize, + hits: usize, + misses: usize, + backing_reads: usize, + evictions: usize, + not_retained: usize, + rejected: usize, +}; + +const Entry = struct { + key: contracts.CallBinding, + storage: []u8, + output: []const u8, +}; + +// Test allocator observing only allocations made by the cache itself. +const TrackingAllocator = struct { + backing: std.mem.Allocator, + live_bytes: usize = 0, + peak_bytes: usize = 0, + + fn allocator(self: *TrackingAllocator) std.mem.Allocator { + return .{ .ptr = self, .vtable = &.{ .alloc = alloc_fn, .resize = std.mem.Allocator.noResize, .remap = std.mem.Allocator.noRemap, .free = free_fn } }; + } + + fn alloc_fn(context: *anyopaque, length: usize, alignment: std.mem.Alignment, return_address: usize) ?[*]u8 { + const self: *TrackingAllocator = @ptrCast(@alignCast(context)); + const memory = self.backing.rawAlloc(length, alignment, return_address) orelse return null; + self.live_bytes += length; + self.peak_bytes = @max(self.peak_bytes, self.live_bytes); + return memory; + } + + fn free_fn(context: *anyopaque, memory: []u8, alignment: std.mem.Alignment, return_address: usize) void { + const self: *TrackingAllocator = @ptrCast(@alignCast(context)); + self.live_bytes -= memory.len; + self.backing.rawFree(memory, alignment, return_address); + } +}; + +// Single-owner synchronous fixture cache. All methods use the same allocator. +// Retained bytes charge the allocated Entry table, owned key strings, and output. +// Allocator bookkeeping and the caller's Cache value are outside this nominal limit. +// Eviction is FIFO; hits do not change insertion order. There is no public insertion API. +pub const Cache = struct { + limits: Limits, + table: []Entry, + state: Stats, + + pub fn init(alloc: std.mem.Allocator, limits: Limits) (std.mem.Allocator.Error || error{TooManyEntries})!Cache { + if (limits.max_entries > 1024) return error.TooManyEntries; + const capacity = @min(limits.max_entries, limits.max_bytes / @sizeOf(Entry)); + const table = try alloc.alloc(Entry, capacity); + const metadata_bytes = capacity * @sizeOf(Entry); + return .{ + .limits = limits, + .table = table, + .state = .{ .entries = 0, .retained_bytes = metadata_bytes, .metadata_bytes = metadata_bytes, .hits = 0, .misses = 0, .backing_reads = 0, .evictions = 0, .not_retained = 0, .rejected = 0 }, + }; + } + + pub fn deinit(self: *Cache, alloc: std.mem.Allocator) void { + for (self.table[0..self.state.entries]) |entry| alloc.free(entry.storage); + alloc.free(self.table); + self.* = undefined; + } + + pub fn stats(self: *const Cache) Stats { + return self.state; + } + + // Every lookup revalidates exact fixture admission and the supplied capture. + // Only the existing registered executor can produce values retained by this cache. + pub fn read(self: *Cache, alloc: std.mem.Allocator, snapshot: *const executor.Snapshot, call: contracts.LogicalCall) !ReadResult { + var admission = try optimizer.build_plan(alloc, &.{call}, .disabled); + defer admission.deinit(alloc); + const admission_status = admission.logical_calls[0].status; + if (admission_status != .admitted) { + self.state.rejected += 1; + return rejected(call.request, admission_status, .admission_rejected); + } + + // Empty execution checks the actual captured hash without any backing read. + var capture_check = try executor.execute(alloc, snapshot, &.{}, .disabled); + defer capture_check.deinit(alloc); + if (unsupported_status(snapshot, call.request)) |status| { + self.state.rejected += 1; + return rejected(call.request, .admitted, status); + } + + for (self.table[0..self.state.entries]) |entry| { + if (same_key(entry.key, call.request)) { + const output = try alloc.dupe(u8, entry.output); + self.state.hits += 1; + return .{ + .receipt = .{ .request = call.request, .admission_status = .admitted, .physical_group = null, .status = .success, .output = output }, + .source = .cache, + .cache_hit = true, + .cache_miss = false, + .backing_reads = 0, + }; + } + } + + var backing = try executor.execute(alloc, snapshot, &.{call}, .disabled); + defer backing.deinit(alloc); + self.state.misses += 1; + self.state.backing_reads += backing.physical_read_count; + const receipt = backing.receipts[0]; + const output = try alloc.dupe(u8, receipt.output); + errdefer alloc.free(output); + // Retention is optional once a trusted caller-owned result exists. + if (receipt.status == .success) self.retain(alloc, receipt.request, receipt.output) catch { + self.state.not_retained += 1; + }; + var owned_receipt = receipt; + owned_receipt.output = output; + // Batch-local group indices are not reused across independent Cache.read calls. + owned_receipt.physical_group = null; + return .{ .receipt = owned_receipt, .source = .backing, .cache_hit = false, .cache_miss = true, .backing_reads = backing.physical_read_count }; + } + + fn retain(self: *Cache, alloc: std.mem.Allocator, request: contracts.CallBinding, output: []const u8) !void { + const storage_bytes = key_storage_bytes(request, output.len) orelse { + self.state.not_retained += 1; + return; + }; + if (self.table.len == 0 or storage_bytes > self.limits.max_bytes - self.state.metadata_bytes) { + self.state.not_retained += 1; + return; + } + // Evict before allocation so even pending cache storage fits the nominal bound. + while (self.state.entries == self.table.len or storage_bytes > self.limits.max_bytes - self.state.retained_bytes) self.evict_oldest(alloc); + const storage = try alloc.alloc(u8, storage_bytes); + errdefer alloc.free(storage); + var remaining = storage; + var key = request; + key.id = 0; + key.agent_id = ""; + key.principal_domain = copy_string(&remaining, request.principal_domain); + key.task_id = copy_string(&remaining, request.task_id); + key.tool_id = copy_string(&remaining, request.tool_id); + key.snapshot = .{ + .id = copy_string(&remaining, request.snapshot.?.id), + .version = copy_string(&remaining, request.snapshot.?.version), + }; + key.args.path = copy_string(&remaining, request.args.path); + key.args.options = copy_string(&remaining, request.args.options); + key.result.schema_id = copy_string(&remaining, request.result.schema_id); + key.authority.partition = copy_string(&remaining, request.authority.partition); + const stored_output = copy_string(&remaining, output); + std.debug.assert(remaining.len == 0); + self.table[self.state.entries] = .{ .key = key, .storage = storage, .output = stored_output }; + self.state.entries += 1; + self.state.retained_bytes += storage_bytes; + } + + fn evict_oldest(self: *Cache, alloc: std.mem.Allocator) void { + std.debug.assert(self.state.entries > 0); + const first = self.table[0]; + self.state.retained_bytes -= first.storage.len; + alloc.free(first.storage); + self.state.entries -= 1; + std.mem.copyForwards(Entry, self.table[0..self.state.entries], self.table[1..][0..self.state.entries]); + self.state.evictions += 1; + } +}; + +fn rejected(request: contracts.CallBinding, admission_status: contracts.AdmissionStatus, status: contracts.ReceiptStatus) ReadResult { + return .{ + .receipt = .{ .request = request, .admission_status = admission_status, .physical_group = null, .status = status, .output = "" }, + .source = .rejected, + .cache_hit = false, + .cache_miss = false, + .backing_reads = 0, + }; +} + +fn unsupported_status(snapshot: *const executor.Snapshot, request: contracts.CallBinding) ?contracts.ReceiptStatus { + if (request.effect != .snapshot_read or !equal(request.tool_id, "embedded-content-reader") or + !equal(request.args.options, "raw") or !equal(request.result.schema_id, "content") or + request.result.version != 1 or request.result.encoding != .bytes) return .unsupported_effect; + const identity = snapshot.identity(); + const requested = request.snapshot orelse return .snapshot_mismatch; + if (!equal(request.args.path, snapshot.path) or !equal(requested.id, identity.id) or !equal(requested.version, identity.version)) return .snapshot_mismatch; + if (request.args.window.offset > snapshot.bytes.len or request.args.window.length > snapshot.bytes.len - request.args.window.offset) return .invalid_window; + if (request.args.window.length > request.output_budget) return .output_budget_exceeded; + return null; +} + +fn same_key(a: contracts.CallBinding, b: contracts.CallBinding) bool { + return equal(a.principal_domain, b.principal_domain) and equal(a.task_id, b.task_id) and + equal(a.tool_id, b.tool_id) and a.effect == b.effect and + equal(a.snapshot.?.id, b.snapshot.?.id) and equal(a.snapshot.?.version, b.snapshot.?.version) and + equal(a.args.path, b.args.path) and equal(a.args.options, b.args.options) and + a.args.window.offset == b.args.window.offset and a.args.window.length == b.args.window.length and + equal(a.result.schema_id, b.result.schema_id) and a.result.version == b.result.version and a.result.encoding == b.result.encoding and + a.output_budget == b.output_budget and equal(a.authority.partition, b.authority.partition) and + a.authority.epoch == b.authority.epoch and a.authority.generation == b.authority.generation; +} + +fn equal(a: []const u8, b: []const u8) bool { + return std.mem.eql(u8, a, b); +} + +fn key_storage_bytes(request: contracts.CallBinding, output_bytes: usize) ?usize { + const snapshot = request.snapshot orelse return null; + var total = output_bytes; + for ([_][]const u8{ request.principal_domain, request.task_id, request.tool_id, snapshot.id, snapshot.version, request.args.path, request.args.options, request.result.schema_id, request.authority.partition }) |value| { + total = std.math.add(usize, total, value.len) catch return null; + } + return total; +} + +fn copy_string(remaining: *[]u8, input: []const u8) []const u8 { + const result = remaining.*[0..input.len]; + @memcpy(result, input); + remaining.* = remaining.*[input.len..]; + return result; +} + +fn fixture(snapshot: *const executor.Snapshot, id: u64, agent: []const u8, offset: usize) contracts.LogicalCall { + const request: contracts.CallBinding = .{ + .id = id, + .agent_id = agent, + .principal_domain = "operator", + .task_id = "task", + .tool_id = "embedded-content-reader", + .effect = .snapshot_read, + .snapshot = snapshot.identity(), + .args = .{ .path = snapshot.path, .window = .{ .offset = offset, .length = 3 }, .options = "raw" }, + .result = .{ .schema_id = "content", .version = 1, .encoding = .bytes }, + .output_budget = 64, + .authority = .{ .partition = "fixture-read", .epoch = 1, .generation = 1 }, + }; + return admit(request); +} + +fn admit(request: contracts.CallBinding) contracts.LogicalCall { + return .{ .request = request, .admission = .{ .admitted = .{ .binding = request, .current_authority = request.authority, .immutable_snapshot = request.snapshot } } }; +} + +test "separately admitted agents receive independent receipts and copied completed values" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdefgh"); + defer snapshot.deinit(std.testing.allocator); + var cache = try Cache.init(std.testing.allocator, .{ .max_entries = 4, .max_bytes = 8192 }); + defer cache.deinit(std.testing.allocator); + var first = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 1, "agent-a", 0)); + defer first.deinit(std.testing.allocator); + var second = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 2, "agent-b", 0)); + defer second.deinit(std.testing.allocator); + try std.testing.expectEqual(Source.backing, first.source); + try std.testing.expectEqual(Source.cache, second.source); + try std.testing.expectEqual(@as(u64, 2), second.receipt.request.id); + try std.testing.expectEqualStrings("agent-b", second.receipt.request.agent_id); + try std.testing.expectEqualStrings("abc", second.receipt.output); + try std.testing.expect(first.receipt.output.ptr != second.receipt.output.ptr); + try std.testing.expectEqual(@as(usize, 1), cache.stats().backing_reads); +} + +test "every key partition isolates reuse and unsupported contracts are never retained" { + inline for (0..17) |change| { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdefgh"); + defer snapshot.deinit(std.testing.allocator); + var cache = try Cache.init(std.testing.allocator, .{ .max_entries = 4, .max_bytes = 8192 }); + defer cache.deinit(std.testing.allocator); + var first = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 1, "a", 0)); + defer first.deinit(std.testing.allocator); + var request = fixture(&snapshot, 2, "b", 0).request; + switch (change) { + 0 => request.principal_domain = "other-domain", + 1 => request.task_id = "other-task", + 2 => request.snapshot.?.id = "other-snapshot", + 3 => request.snapshot.?.version = "other-version", + 4 => request.tool_id = "other-tool", + 5 => request.args.path = "other-path", + 6 => request.args.options = "other-options", + 7 => request.args.window.offset = 1, + 8 => request.args.window.length = 2, + 9 => request.result.schema_id = "other-schema", + 10 => request.result.version = 2, + 11 => request.result.encoding = .utf8, + 12 => request.output_budget = 63, + 13 => request.authority.partition = "other-authority", + 14 => request.authority.epoch = 2, + 15 => request.authority.generation = 2, + 16 => request.effect = .opaque_exec, + else => unreachable, + } + var changed = try cache.read(std.testing.allocator, &snapshot, admit(request)); + defer changed.deinit(std.testing.allocator); + try std.testing.expect(!changed.cache_hit); + try std.testing.expectEqual(@as(usize, 0), cache.stats().hits); + if (changed.receipt.status != .success) try std.testing.expectEqual(@as(usize, 1), cache.stats().entries); + } +} + +test "denied stale binding mismatched and uncertified fresh calls cannot see populated values" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdefgh"); + defer snapshot.deinit(std.testing.allocator); + var cache = try Cache.init(std.testing.allocator, .{ .max_entries = 2, .max_bytes = 8192 }); + defer cache.deinit(std.testing.allocator); + var first = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 1, "a", 0)); + defer first.deinit(std.testing.allocator); + inline for (0..4) |change| { + var call = fixture(&snapshot, 2, "b", 0); + switch (change) { + 0 => call.admission = .denied, + 1 => call.admission.admitted.current_authority.generation += 1, + 2 => call.admission.admitted.binding.agent_id = "other-agent", + 3 => call.admission.admitted.immutable_snapshot = null, + else => unreachable, + } + var result = try cache.read(std.testing.allocator, &snapshot, call); + defer result.deinit(std.testing.allocator); + try std.testing.expectEqual(Source.rejected, result.source); + try std.testing.expectEqualStrings("", result.receipt.output); + try std.testing.expectEqual(@as(usize, 0), result.backing_reads); + try std.testing.expect(!result.cache_hit and !result.cache_miss); + } + try std.testing.expectEqual(@as(usize, 0), cache.stats().hits); + try std.testing.expectEqual(@as(usize, 1), cache.stats().backing_reads); +} + +test "FIFO eviction preserves caller owned output and hits do not refresh order" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdefghij"); + defer snapshot.deinit(std.testing.allocator); + var cache = try Cache.init(std.testing.allocator, .{ .max_entries = 2, .max_bytes = 8192 }); + defer cache.deinit(std.testing.allocator); + var a = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 1, "a", 0)); + defer a.deinit(std.testing.allocator); + var b = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 2, "a", 3)); + defer b.deinit(std.testing.allocator); + var hit = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 3, "b", 0)); + defer hit.deinit(std.testing.allocator); + var c = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 4, "a", 6)); + defer c.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 1), cache.stats().evictions); + try std.testing.expectEqualStrings("abc", hit.receipt.output); + var oldest = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 5, "c", 0)); + defer oldest.deinit(std.testing.allocator); + try std.testing.expectEqual(Source.backing, oldest.source); + try std.testing.expectEqualStrings("abc", a.receipt.output); +} + +test "zero tiny and metadata-only budgets disable retention while returning trusted values" { + for ([_]Limits{ .{ .max_entries = 0, .max_bytes = 8192 }, .{ .max_entries = 4, .max_bytes = 0 }, .{ .max_entries = 4, .max_bytes = 1 }, .{ .max_entries = 1, .max_bytes = @sizeOf(Entry) } }) |limits| { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + var cache = try Cache.init(std.testing.allocator, limits); + defer cache.deinit(std.testing.allocator); + var first = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 1, "a", 0)); + defer first.deinit(std.testing.allocator); + var second = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 2, "b", 0)); + defer second.deinit(std.testing.allocator); + try std.testing.expectEqualStrings("abc", second.receipt.output); + try std.testing.expectEqual(Source.backing, second.source); + try std.testing.expectEqual(@as(usize, 0), cache.stats().entries); + try std.testing.expect(cache.stats().retained_bytes <= limits.max_bytes); + } + try std.testing.expectError(error.TooManyEntries, Cache.init(std.testing.allocator, .{ .max_entries = 1025, .max_bytes = std.math.maxInt(usize) })); +} + +test "byte accounting evicts before reaching the entry limit and rejects overflow totals" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + const call = fixture(&snapshot, 1, "a", 0); + const payload_bytes = key_storage_bytes(call.request, 3).?; + const limits: Limits = .{ .max_entries = 2, .max_bytes = 2 * @sizeOf(Entry) + payload_bytes }; + var cache = try Cache.init(std.testing.allocator, limits); + defer cache.deinit(std.testing.allocator); + var a = try cache.read(std.testing.allocator, &snapshot, call); + defer a.deinit(std.testing.allocator); + var b = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 2, "a", 3)); + defer b.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 1), cache.stats().entries); + try std.testing.expectEqual(@as(usize, 1), cache.stats().evictions); + try std.testing.expectEqual(limits.max_bytes, cache.stats().retained_bytes); + try std.testing.expectEqualStrings("abc", a.receipt.output); + try std.testing.expectEqual(@as(?usize, null), key_storage_bytes(call.request, std.math.maxInt(usize))); +} + +test "optional retention allocation failure preserves successful trusted output" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + var observed_retention_failure = false; + for (0..32) |allocation_budget| { + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); + const alloc = failing.allocator(); + const limits: Limits = .{ .max_entries = 1, .max_bytes = 8192 }; + var cache = try Cache.init(alloc, limits); + defer cache.deinit(alloc); + failing.fail_index = failing.alloc_index + allocation_budget; + var result = cache.read(alloc, &snapshot, fixture(&snapshot, 1, "a", 0)) catch |err| { + if (err == error.OutOfMemory) continue; + return err; + }; + defer result.deinit(alloc); + if (failing.has_induced_failure) { + try std.testing.expectEqual(contracts.ReceiptStatus.success, result.receipt.status); + try std.testing.expectEqualStrings("abc", result.receipt.output); + try std.testing.expectEqual(Source.backing, result.source); + try std.testing.expectEqual(@as(usize, 1), result.backing_reads); + try std.testing.expectEqual(@as(usize, 1), cache.stats().backing_reads); + try std.testing.expectEqual(@as(usize, 1), cache.stats().not_retained); + try std.testing.expectEqual(@as(usize, 0), cache.stats().entries); + try std.testing.expect(cache.stats().retained_bytes <= limits.max_bytes); + try std.testing.expectEqual(cache.stats().retained_bytes + result.receipt.output.len, failing.allocated_bytes - failing.freed_bytes); + observed_retention_failure = true; + break; + } + } + try std.testing.expect(observed_retention_failure); +} + +test "byte-driven replacement never allocates cache storage above the nominal limit" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + const first_call = fixture(&snapshot, 1, "a", 0); + const second_call = fixture(&snapshot, 2, "b", 3); + var first = try executor.execute(std.testing.allocator, &snapshot, &.{first_call}, .disabled); + defer first.deinit(std.testing.allocator); + var second = try executor.execute(std.testing.allocator, &snapshot, &.{second_call}, .disabled); + defer second.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.ReceiptStatus.success, first.receipts[0].status); + try std.testing.expectEqual(contracts.ReceiptStatus.success, second.receipts[0].status); + const payload_bytes = key_storage_bytes(first_call.request, first.receipts[0].output.len).?; + const limits: Limits = .{ .max_entries = 2, .max_bytes = 2 * @sizeOf(Entry) + payload_bytes }; + var tracking: TrackingAllocator = .{ .backing = std.testing.allocator }; + const alloc = tracking.allocator(); + var cache = try Cache.init(alloc, limits); + defer cache.deinit(alloc); + // Direct private insertion isolates table/key/output allocation peaks from + // temporary admission work and caller-owned output copies. Values came from + // the registered reader immediately above. + try cache.retain(alloc, first.receipts[0].request, first.receipts[0].output); + try cache.retain(alloc, second.receipts[0].request, second.receipts[0].output); + try std.testing.expectEqual(@as(usize, 1), cache.stats().entries); + try std.testing.expectEqual(@as(usize, 1), cache.stats().evictions); + try std.testing.expectEqual(limits.max_bytes, tracking.peak_bytes); + try std.testing.expectEqual(cache.stats().retained_bytes, tracking.live_bytes); + try std.testing.expect(tracking.peak_bytes <= limits.max_bytes); +} + +test "optional retention failure after byte-driven eviction still returns trusted output" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + const first_call = fixture(&snapshot, 1, "a", 0); + const payload_bytes = key_storage_bytes(first_call.request, 3).?; + const limits: Limits = .{ .max_entries = 2, .max_bytes = 2 * @sizeOf(Entry) + payload_bytes }; + var observed_failure_after_eviction = false; + for (0..32) |allocation_budget| { + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); + const alloc = failing.allocator(); + var cache = try Cache.init(alloc, limits); + defer cache.deinit(alloc); + var first = try cache.read(alloc, &snapshot, first_call); + first.deinit(alloc); + failing.fail_index = failing.alloc_index + allocation_budget; + var second = cache.read(alloc, &snapshot, fixture(&snapshot, 2, "b", 3)) catch |err| { + if (err == error.OutOfMemory) continue; + return err; + }; + defer second.deinit(alloc); + if (failing.has_induced_failure) { + try std.testing.expectEqual(contracts.ReceiptStatus.success, second.receipt.status); + try std.testing.expectEqualStrings("def", second.receipt.output); + try std.testing.expectEqual(Source.backing, second.source); + try std.testing.expectEqual(@as(usize, 1), cache.stats().evictions); + try std.testing.expectEqual(@as(usize, 0), cache.stats().entries); + try std.testing.expectEqual(@as(usize, 1), cache.stats().not_retained); + try std.testing.expectEqual(@as(usize, 2), cache.stats().backing_reads); + try std.testing.expect(cache.stats().retained_bytes <= limits.max_bytes); + try std.testing.expectEqual(cache.stats().retained_bytes + second.receipt.output.len, failing.allocated_bytes - failing.freed_bytes); + observed_failure_after_eviction = true; + break; + } + } + try std.testing.expect(observed_failure_after_eviction); +} + +test "owned keys survive source mutation and accounting includes key and metadata bytes" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + var cache = try Cache.init(std.testing.allocator, .{ .max_entries = 1, .max_bytes = 8192 }); + defer cache.deinit(std.testing.allocator); + var task = [_]u8{ 't', 'a', 's', 'k' }; + var request = fixture(&snapshot, 1, "a", 0).request; + request.task_id = &task; + var first = try cache.read(std.testing.allocator, &snapshot, admit(request)); + first.deinit(std.testing.allocator); + task[0] = 'X'; + var second = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 2, "b", 0)); + defer second.deinit(std.testing.allocator); + try std.testing.expectEqual(Source.cache, second.source); + const nominal = @sizeOf(Entry) + key_storage_bytes(fixture(&snapshot, 3, "c", 0).request, 3).?; + try std.testing.expectEqual(nominal, cache.stats().retained_bytes); + try std.testing.expectEqual(@as(usize, @sizeOf(Entry)), cache.stats().metadata_bytes); +} + +test "actual distinct captured content and paths require independent backing values" { + var first_snapshot = try executor.Snapshot.init(std.testing.allocator, "file-a", "abcdef"); + defer first_snapshot.deinit(std.testing.allocator); + var changed_content = try executor.Snapshot.init(std.testing.allocator, "file-a", "XYZdef"); + defer changed_content.deinit(std.testing.allocator); + var changed_path = try executor.Snapshot.init(std.testing.allocator, "file-b", "abcdef"); + defer changed_path.deinit(std.testing.allocator); + var cache = try Cache.init(std.testing.allocator, .{ .max_entries = 4, .max_bytes = 8192 }); + defer cache.deinit(std.testing.allocator); + var a = try cache.read(std.testing.allocator, &first_snapshot, fixture(&first_snapshot, 1, "a", 0)); + defer a.deinit(std.testing.allocator); + var b = try cache.read(std.testing.allocator, &changed_content, fixture(&changed_content, 2, "b", 0)); + defer b.deinit(std.testing.allocator); + var c = try cache.read(std.testing.allocator, &changed_path, fixture(&changed_path, 3, "c", 0)); + defer c.deinit(std.testing.allocator); + try std.testing.expectEqual(Source.backing, b.source); + try std.testing.expectEqual(Source.backing, c.source); + try std.testing.expectEqualStrings("XYZ", b.receipt.output); + try std.testing.expectEqualStrings("abc", c.receipt.output); + try std.testing.expectEqual(@as(usize, 3), cache.stats().backing_reads); +} + +test "failed windows budgets and tampered captures never return or retain cached data" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + var cache = try Cache.init(std.testing.allocator, .{ .max_entries = 2, .max_bytes = 8192 }); + defer cache.deinit(std.testing.allocator); + var first = try cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 1, "a", 0)); + defer first.deinit(std.testing.allocator); + var invalid = fixture(&snapshot, 2, "b", 0).request; + invalid.args.window.length = std.math.maxInt(usize); + var failed = try cache.read(std.testing.allocator, &snapshot, admit(invalid)); + defer failed.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.ReceiptStatus.invalid_window, failed.receipt.status); + invalid = fixture(&snapshot, 3, "b", 0).request; + invalid.output_budget = 1; + var budget = try cache.read(std.testing.allocator, &snapshot, admit(invalid)); + defer budget.deinit(std.testing.allocator); + try std.testing.expectEqual(contracts.ReceiptStatus.output_budget_exceeded, budget.receipt.status); + try std.testing.expectEqual(@as(usize, 1), cache.stats().entries); + @constCast(snapshot.bytes)[0] = 'X'; + try std.testing.expectError(error.SnapshotTampered, cache.read(std.testing.allocator, &snapshot, fixture(&snapshot, 4, "b", 0))); + try std.testing.expectEqual(@as(usize, 0), cache.stats().hits); +} diff --git a/experiments/agent_cast/src/cache_demo.zig b/experiments/agent_cast/src/cache_demo.zig new file mode 100644 index 0000000000..155c5b93cc --- /dev/null +++ b/experiments/agent_cast/src/cache_demo.zig @@ -0,0 +1,206 @@ +const std = @import("std"); +const contracts = @import("contracts.zig"); +const executor = @import("executor.zig"); +const cache_mod = @import("cache.zig"); +const cached_executor = @import("cached_executor.zig"); + +const help = + \\Usage: agent-cast-cache-demo --fixture PATH + \\ + \\Capture a real file of 256 bytes to 1 MiB. Run two independent admitted + \\four-window batches, then a batch of denied, stale, and mismatched calls. + \\JSON includes complete output bytes and actual hit, miss, and backing counts. + \\Hits still validate fixture admission, hash the capture, and copy output. + \\This synchronous experiment does not implement production registry validation. + \\ + \\ --fixture PATH Actual file to capture once + \\ --help Show this help + \\ +; + +pub fn main(init: std.process.Init) !void { + run(init) catch |err| { + var buffer: [256]u8 = undefined; + const message = try std.fmt.bufPrint(&buffer, "agent-cast-cache-demo: {s}; see --help\n", .{@errorName(err)}); + try std.Io.File.stderr().writeStreamingAll(init.io, message); + std.process.exit(1); + }; +} + +fn run(init: std.process.Init) !void { + var arena = std.heap.ArenaAllocator.init(init.gpa); + defer arena.deinit(); + const report_alloc = arena.allocator(); + const alloc = init.gpa; + const args = try init.minimal.args.toSlice(report_alloc); + var path: ?[]const u8 = null; + var index: usize = 1; + while (index < args.len) : (index += 1) { + if (std.mem.eql(u8, args[index], "--help")) { + try std.Io.File.stdout().writeStreamingAll(init.io, help); + return; + } + if (!std.mem.eql(u8, args[index], "--fixture")) return error.UnknownArgument; + index += 1; + if (index >= args.len) return error.MissingFixturePath; + if (path != null) return error.DuplicateFixtureOption; + path = args[index]; + } + const fixture_path = path orelse return error.MissingFixturePath; + const file_bytes = try std.Io.Dir.cwd().readFileAlloc(init.io, fixture_path, alloc, .limited(1024 * 1024 + 1)); + defer alloc.free(file_bytes); + if (file_bytes.len < 256) return error.FixtureTooSmall; + var snapshot = try executor.Snapshot.init(alloc, fixture_path, file_bytes); + defer snapshot.deinit(alloc); + const limits: cache_mod.Limits = .{ .max_entries = 8, .max_bytes = 32768 }; + const batch_limits: cached_executor.BatchLimits = .{ .max_result_bytes = 4096 }; + var cache = try cache_mod.Cache.init(alloc, limits); + defer cache.deinit(alloc); + + var cold_calls: [4]contracts.LogicalCall = undefined; + var hot_calls: [4]contracts.LogicalCall = undefined; + for (0..4) |window| { + cold_calls[window] = fixture_call(&snapshot, @intCast(window + 1), "agent-a", window * 64); + hot_calls[window] = fixture_call(&snapshot, @intCast(window + 5), "agent-b", window * 64); + } + var cold = try cached_executor.execute(alloc, &cache, &snapshot, &cold_calls, batch_limits); + defer cold.deinit(alloc); + var hot = try cached_executor.execute(alloc, &cache, &snapshot, &hot_calls, batch_limits); + defer hot.deinit(alloc); + var rejected_calls = [_]contracts.LogicalCall{ + fixture_call(&snapshot, 9, "denied-agent", 0), + fixture_call(&snapshot, 10, "stale-agent", 0), + fixture_call(&snapshot, 11, "mismatched-agent", 0), + }; + rejected_calls[0].admission = .denied; + rejected_calls[1].admission.admitted.current_authority.generation += 1; + rejected_calls[2].admission.admitted.binding.agent_id = "other-agent"; + var blocked = try cached_executor.execute(alloc, &cache, &snapshot, &rejected_calls, batch_limits); + defer blocked.deinit(alloc); + try validate_batches(&snapshot, cold, hot, blocked); + + const batches = [_]BatchReport{ + try batch_report(report_alloc, "cold-agent-a", cold), + try batch_report(report_alloc, "completed-values-agent-b", hot), + try batch_report(report_alloc, "rejected-consumers", blocked), + }; + const report = .{ + .experimental_scope = "M2 synchronous owned completed-value cache; exact fresh fixture admissions; no production registry or performance claim", + .immutable_input_sha256 = @as([]const u8, &snapshot.input_hash_hex), + .immutable_input_bytes = snapshot.bytes.len, + .fixture_verified = true, + .logical_call_count = cold.results.len + hot.results.len + blocked.results.len, + .limits = limits, + .batch_limits = batch_limits, + .cache = cache.stats(), + .retained_accounting = "allocated entry table plus owned key strings and outputs; allocator bookkeeping and caller-owned output copies excluded", + .batches = &batches, + }; + var writer: std.Io.Writer.Allocating = .init(report_alloc); + defer writer.deinit(); + try std.json.Stringify.value(report, .{ .whitespace = .indent_2 }, &writer.writer); + try writer.writer.writeByte('\n'); + try std.Io.File.stdout().writeStreamingAll(init.io, writer.written()); +} + +fn fixture_call(snapshot: *const executor.Snapshot, id: u64, agent_id: []const u8, offset: usize) contracts.LogicalCall { + const request: contracts.CallBinding = .{ + .id = id, + .agent_id = agent_id, + .principal_domain = "fixture-operator", + .task_id = "cache-fixture-task", + .tool_id = "embedded-content-reader", + .effect = .snapshot_read, + .snapshot = snapshot.identity(), + .args = .{ .path = snapshot.path, .window = .{ .offset = offset, .length = 64 }, .options = "raw" }, + .result = .{ .schema_id = "content", .version = 1, .encoding = .bytes }, + .output_budget = 64, + .authority = .{ .partition = "fixture-read", .epoch = 1, .generation = 1 }, + }; + return .{ .request = request, .admission = .{ .admitted = .{ .binding = request, .current_authority = request.authority, .immutable_snapshot = request.snapshot } } }; +} + +const CallReport = struct { + logical_id: u64, + agent_id: []const u8, + principal_domain: []const u8, + task_id: []const u8, + admission_status: []const u8, + status: []const u8, + source: []const u8, + cache_hit: bool, + cache_miss: bool, + backing_reads: usize, + output_bytes: usize, + output_sha256: []const u8, + output_hex: []const u8, +}; + +const BatchReport = struct { + name: []const u8, + logical_calls: usize, + cache_hits: usize, + cache_misses: usize, + backing_reads: usize, + receipts: []CallReport, +}; + +fn batch_report(alloc: std.mem.Allocator, name: []const u8, execution: cached_executor.Execution) !BatchReport { + const receipts = try alloc.alloc(CallReport, execution.results.len); + for (execution.results, 0..) |result, index| { + const receipt = result.receipt; + var hash: [32]u8 = undefined; + std.crypto.hash.sha2.Sha256.hash(receipt.output, &hash, .{}); + receipts[index] = .{ + .logical_id = receipt.request.id, + .agent_id = receipt.request.agent_id, + .principal_domain = receipt.request.principal_domain, + .task_id = receipt.request.task_id, + .admission_status = @tagName(receipt.admission_status), + .status = @tagName(receipt.status), + .source = @tagName(result.source), + .cache_hit = result.cache_hit, + .cache_miss = result.cache_miss, + .backing_reads = result.backing_reads, + .output_bytes = receipt.output.len, + .output_sha256 = try hex_owned(alloc, &hash), + .output_hex = try hex_owned(alloc, receipt.output), + }; + } + return .{ .name = name, .logical_calls = execution.results.len, .cache_hits = execution.cache_hits, .cache_misses = execution.cache_misses, .backing_reads = execution.backing_reads, .receipts = receipts }; +} + +fn hex_owned(alloc: std.mem.Allocator, bytes: []const u8) ![]const u8 { + const hex = try alloc.alloc(u8, bytes.len * 2); + const alphabet = "0123456789abcdef"; + for (bytes, 0..) |byte, index| { + hex[index * 2] = alphabet[byte >> 4]; + hex[index * 2 + 1] = alphabet[byte & 15]; + } + return hex; +} + +fn validate_batches(snapshot: *const executor.Snapshot, cold: cached_executor.Execution, hot: cached_executor.Execution, blocked: cached_executor.Execution) !void { + if (cold.results.len != 4 or hot.results.len != 4 or blocked.results.len != 3) return error.FixtureReceiptCountMismatch; + if (cold.cache_hits != 0 or cold.cache_misses != 4 or cold.backing_reads != 4 or hot.cache_hits != 4 or hot.cache_misses != 0 or hot.backing_reads != 0) + return error.FixtureProvenanceMismatch; + for (cold.results, hot.results, 0..) |first, second, index| { + const expected = snapshot.bytes[index * 64 ..][0..64]; + if (first.receipt.request.id != index + 1 or second.receipt.request.id != index + 5 or + !std.mem.eql(u8, first.receipt.request.agent_id, "agent-a") or !std.mem.eql(u8, second.receipt.request.agent_id, "agent-b")) return error.FixtureIdentityMismatch; + if (first.receipt.admission_status != .admitted or second.receipt.admission_status != .admitted or first.receipt.status != .success or second.receipt.status != .success or + first.source != .backing or second.source != .cache or !std.mem.eql(u8, first.receipt.output, expected) or !std.mem.eql(u8, second.receipt.output, expected)) return error.FixtureOutputMismatch; + } + const statuses = [_]contracts.AdmissionStatus{ .denied, .stale_authority, .binding_mismatch }; + for (blocked.results, statuses, 0..) |result, expected, index| { + if (result.receipt.request.id != index + 9 or result.receipt.admission_status != expected or result.receipt.status != .admission_rejected or + result.source != .rejected or result.receipt.output.len != 0 or result.cache_hit or result.cache_miss or result.backing_reads != 0) return error.FixtureRejectedCallExecuted; + } +} + +test "batch verification rejects a degenerate empty execution" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + const empty: cached_executor.Execution = .{ .results = &.{}, .cache_hits = 0, .cache_misses = 0, .backing_reads = 0 }; + try std.testing.expectError(error.FixtureReceiptCountMismatch, validate_batches(&snapshot, empty, empty, empty)); +} diff --git a/experiments/agent_cast/src/cached_executor.zig b/experiments/agent_cast/src/cached_executor.zig new file mode 100644 index 0000000000..4fe2a7bc86 --- /dev/null +++ b/experiments/agent_cast/src/cached_executor.zig @@ -0,0 +1,133 @@ +const std = @import("std"); +const contracts = @import("contracts.zig"); +const optimizer = @import("optimizer.zig"); +const executor = @import("executor.zig"); +const cache_mod = @import("cache.zig"); + +const hard_max_result_bytes = 16 * 1024 * 1024; + +pub const BatchLimits = struct { + max_result_bytes: usize = 64 * 1024, +}; + +// Owns result array and every output copy; request strings remain frozen and borrowed. +pub const Execution = struct { + results: []cache_mod.ReadResult, + cache_hits: usize, + cache_misses: usize, + backing_reads: usize, + + pub fn deinit(self: *Execution, alloc: std.mem.Allocator) void { + for (self.results) |*result| result.deinit(alloc); + alloc.free(self.results); + self.* = undefined; + } +}; + +// Independent sequential logical calls. Completed values may be reused between +// batches; this API supplies no broker, in-flight coalescing, or cancellation. +// Reserve every admitted snapshot-read requested window conservatively before +// cache access or output allocation, including windows that later fail dispatch. +// A limit above 16 MiB or an exceeded/overflowed reservation rejects the entire +// batch with an error and no receipts or cache changes. Cache and plan metadata +// have separate bounds; this limit covers caller-owned output copies only. +pub fn execute(alloc: std.mem.Allocator, cache: *cache_mod.Cache, snapshot: *const executor.Snapshot, calls: []const contracts.LogicalCall, limits: BatchLimits) !Execution { + if (limits.max_result_bytes > hard_max_result_bytes) return error.BatchLimitTooLarge; + var validation = try optimizer.build_plan(alloc, calls, .disabled); + defer validation.deinit(alloc); + var reserved_output_bytes: usize = 0; + for (validation.logical_calls) |planned| { + if (planned.status != .admitted or planned.call.request.effect != .snapshot_read) continue; + reserved_output_bytes = std.math.add(usize, reserved_output_bytes, planned.call.request.args.window.length) catch return error.BatchResultLimitExceeded; + if (reserved_output_bytes > limits.max_result_bytes) return error.BatchResultLimitExceeded; + } + const results = try alloc.alloc(cache_mod.ReadResult, calls.len); + var initialized: usize = 0; + errdefer { + for (results[0..initialized]) |*result| result.deinit(alloc); + alloc.free(results); + } + var hits: usize = 0; + var misses: usize = 0; + var backing_reads: usize = 0; + for (calls, 0..) |call, index| { + results[index] = try cache.read(alloc, snapshot, call); + initialized += 1; + hits += @intFromBool(results[index].cache_hit); + misses += @intFromBool(results[index].cache_miss); + backing_reads += results[index].backing_reads; + } + return .{ .results = results, .cache_hits = hits, .cache_misses = misses, .backing_reads = backing_reads }; +} + +test "duplicate logical IDs reject a batch before cache access" { + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", "abcdef"); + defer snapshot.deinit(std.testing.allocator); + var cache = try cache_mod.Cache.init(std.testing.allocator, .{ .max_entries = 2, .max_bytes = 8192 }); + defer cache.deinit(std.testing.allocator); + const request: contracts.CallBinding = .{ + .id = 1, + .agent_id = "a", + .principal_domain = "operator", + .task_id = "task", + .tool_id = "embedded-content-reader", + .effect = .snapshot_read, + .snapshot = snapshot.identity(), + .args = .{ .path = snapshot.path, .window = .{ .offset = 0, .length = 3 }, .options = "raw" }, + .result = .{ .schema_id = "content", .version = 1, .encoding = .bytes }, + .output_budget = 64, + .authority = .{ .partition = "fixture-read", .epoch = 1, .generation = 1 }, + }; + const call: contracts.LogicalCall = .{ .request = request, .admission = .{ .admitted = .{ .binding = request, .current_authority = request.authority, .immutable_snapshot = request.snapshot } } }; + try std.testing.expectError(error.DuplicateLogicalId, execute(std.testing.allocator, &cache, &snapshot, &.{ call, call }, .{})); + try std.testing.expectEqual(@as(usize, 0), cache.stats().entries); +} + +test "aggregate valid windows reject before cache access without allocating projected outputs" { + const source = try std.testing.allocator.alloc(u8, 32 * 1024); + defer std.testing.allocator.free(source); + @memset(source, 'x'); + var snapshot = try executor.Snapshot.init(std.testing.allocator, "file", source); + defer snapshot.deinit(std.testing.allocator); + var cache = try cache_mod.Cache.init(std.testing.allocator, .{ .max_entries = 2, .max_bytes = 8192 }); + defer cache.deinit(std.testing.allocator); + var calls: [513]contracts.LogicalCall = undefined; + for (&calls, 0..) |*call, index| { + const request: contracts.CallBinding = .{ + .id = @intCast(index + 1), + .agent_id = "a", + .principal_domain = "operator", + .task_id = "task", + .tool_id = "embedded-content-reader", + .effect = .snapshot_read, + .snapshot = snapshot.identity(), + .args = .{ .path = snapshot.path, .window = .{ .offset = 0, .length = source.len }, .options = "raw" }, + .result = .{ .schema_id = "content", .version = 1, .encoding = .bytes }, + .output_budget = source.len, + .authority = .{ .partition = "fixture-read", .epoch = 1, .generation = 1 }, + }; + call.* = .{ .request = request, .admission = .{ .admitted = .{ .binding = request, .current_authority = request.authority, .immutable_snapshot = request.snapshot } } }; + } + const before = cache.stats(); + try std.testing.expectError(error.BatchResultLimitExceeded, execute(std.testing.allocator, &cache, &snapshot, &calls, .{ .max_result_bytes = hard_max_result_bytes })); + try std.testing.expectEqual(before.entries, cache.stats().entries); + try std.testing.expectEqual(before.retained_bytes, cache.stats().retained_bytes); + try std.testing.expectEqual(@as(usize, 0), cache.stats().hits); + try std.testing.expectEqual(@as(usize, 0), cache.stats().misses); + try std.testing.expectEqual(@as(usize, 0), cache.stats().backing_reads); + try std.testing.expectEqual(@as(usize, 0), cache.stats().rejected); + try std.testing.expectError(error.BatchLimitTooLarge, execute(std.testing.allocator, &cache, &snapshot, &.{}, .{ .max_result_bytes = hard_max_result_bytes + 1 })); + calls[0].request.args.window.length = 1; + calls[0].admission.admitted.binding = calls[0].request; + calls[1].request.args.window.length = std.math.maxInt(usize); + calls[1].admission.admitted.binding = calls[1].request; + try std.testing.expectError(error.BatchResultLimitExceeded, execute(std.testing.allocator, &cache, &snapshot, calls[0..2], .{ .max_result_bytes = hard_max_result_bytes })); + try std.testing.expectEqual(@as(usize, 0), cache.stats().misses); + try std.testing.expectEqual(before.retained_bytes, cache.stats().retained_bytes); + // Denied calls have no output reservation, even with the zero-byte limit. + calls[0].admission = .denied; + var denied = try execute(std.testing.allocator, &cache, &snapshot, calls[0..1], .{ .max_result_bytes = 0 }); + defer denied.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 0), denied.backing_reads); + try std.testing.expectEqual(contracts.AdmissionStatus.denied, denied.results[0].receipt.admission_status); +} diff --git a/experiments/agent_cast/src/executor.zig b/experiments/agent_cast/src/executor.zig index fc863f6e02..200fe41a2b 100644 --- a/experiments/agent_cast/src/executor.zig +++ b/experiments/agent_cast/src/executor.zig @@ -185,7 +185,7 @@ test "execution detects mutation of captured bytes before admitting calls" { try std.testing.expectError(error.SnapshotTampered, execute(std.testing.allocator, &snapshot, &.{call}, .enabled)); } -test "windows reject out of range offsets lengths and addition overflow without reading" { +test "invalid windows return no content or successful reader result" { var snapshot = try Snapshot.init(std.testing.allocator, "file", "abcdef"); defer snapshot.deinit(std.testing.allocator); const windows = [_]contracts.Window{ @@ -228,7 +228,7 @@ test "unsupported effects tools options and result contracts reject before dispa } } -test "output budget rejects before content is read" { +test "output budget rejects without returning content or a successful reader result" { var snapshot = try Snapshot.init(std.testing.allocator, "file", "abcdef"); defer snapshot.deinit(std.testing.allocator); var request = fixture(&snapshot, 1, "agent-a", 0, 3).request; diff --git a/experiments/agent_cast/src/subscribers.zig b/experiments/agent_cast/src/subscribers.zig new file mode 100644 index 0000000000..4b7feeb963 --- /dev/null +++ b/experiments/agent_cast/src/subscribers.zig @@ -0,0 +1,731 @@ +const std = @import("std"); + +// Experimental, single-owner state machine. The trusted parent certifies sharing +// eligibility and supplies physical keys, epochs, job IDs, and monotonic time. +// This module performs no I/O, permission review, persistence, or retries. +// The parent owns payloads, pinned copies, and global pending-output accounting. +// A reservation is accounting metadata only; it does not allocate payload space. +pub const Options = struct { + max_jobs: usize = 64, + max_consumers: usize = 256, +}; + +pub const ConsumerIdentity = struct { client_id: u64, call_id: u64 }; +pub const ConsumerRef = struct { token: u64 }; +pub const JobRef = struct { token: u64, backing_id: u64, epoch: u64 }; +pub const JobState = enum { queued, running, aborting, settled }; +pub const Phase = enum { not_started, settled, uncertain }; +pub const Outcome = enum { success, failed, stopped, cancelled, deadline_exceeded, worker_lost }; +pub const Settlement = enum { success, failed, stopped }; +pub const Action = enum { none, not_started, stop_requested }; +pub const Receipt = struct { outcome: Outcome, phase: Phase, delivered_bytes: u64 }; +pub const LeaveEvent = struct { consumer: ConsumerRef, job: JobRef, receipt: Receipt, action: Action }; +pub const Subscription = struct { consumer: ConsumerRef, job: JobRef, shared: bool }; +pub const Subscribe = struct { + physical_key: [32]u8, + epoch: u64, + backing_id: u64, + identity: ConsumerIdentity, + deadline: u64, + output_budget: u64, + initial_credit: u64 = 0, +}; +pub const Reservation = struct { consumer: ConsumerRef, job: JobRef, sequence: u64, bytes: u64 }; +pub const ConsumerSnapshot = struct { + identity: ConsumerIdentity, + job: JobRef, + deadline: u64, + credit: u64, + remaining_output_budget: u64, + delivered_bytes: u64, + next_output_sequence: u64, + next_grant_sequence: u64, + pending: ?Reservation, + receipt: ?Receipt, +}; +pub const JobSnapshot = struct { state: JobState, active_consumers: usize, expected_result_bytes: ?u64, receipt: ?Receipt }; + +pub const Error = error{ + InvalidOptions, + JobCapacity, + ConsumerCapacity, + DuplicateConsumer, + DuplicateJob, + UnknownJob, + UnknownConsumer, + StaleEpoch, + JobNotQueued, + JobNotRunning, + ConsumerTerminal, + DeadlineExpired, + NotExpired, + ClockRegression, + InvalidCredit, + InvalidGrantSequence, + InsufficientCredit, + OutputBudgetExceeded, + ResultSizeUndeclared, + ResultSizeAlreadyDeclared, + ResultSizeExceeded, + ResultNotDrained, + OutputPending, + InvalidReservation, + EmptyOutput, + SequenceOverflow, + TokenOverflow, + BufferTooSmall, + ConsumersRetained, + NotTerminal, +}; + +const Job = struct { + ref: JobRef, + physical_key: [32]u8, + state: JobState = .queued, + active_consumers: usize = 0, + retained_consumers: usize = 0, + expected_result_bytes: ?u64 = null, + receipt: ?Receipt = null, +}; +const Consumer = struct { + ref: ConsumerRef, + data: ConsumerSnapshot, +}; + +pub const Registry = struct { + allocator: std.mem.Allocator, + jobs: []?Job, + consumers: []?Consumer, + next_job_token: u64 = 1, + next_consumer_token: u64 = 1, + last_time: u64 = 0, + + // Registry owns both fixed tables. No operation allocates after init. + // The caller must serialize access and call deinit exactly once. + pub fn init(allocator: std.mem.Allocator, options: Options) (Error || std.mem.Allocator.Error)!Registry { + if (options.max_jobs == 0 or options.max_jobs > 1024 or + options.max_consumers == 0 or options.max_consumers > 8192) return error.InvalidOptions; + const jobs = try allocator.alloc(?Job, options.max_jobs); + errdefer allocator.free(jobs); + const consumers = try allocator.alloc(?Consumer, options.max_consumers); + @memset(jobs, null); + @memset(consumers, null); + return .{ .allocator = allocator, .jobs = jobs, .consumers = consumers }; + } + + pub fn deinit(self: *Registry) void { + self.allocator.free(self.jobs); + self.allocator.free(self.consumers); + self.* = undefined; + } + + pub fn subscribe(self: *Registry, args: Subscribe, now: u64) Error!Subscription { + try self.observe_time(now); + if (args.deadline <= now) return error.DeadlineExpired; + if (args.initial_credit > args.output_budget) return error.InvalidCredit; + var free_consumer: ?usize = null; + for (self.consumers, 0..) |entry, index| { + if (entry) |consumer| { + if (std.meta.eql(consumer.data.identity, args.identity)) return error.DuplicateConsumer; + } else if (free_consumer == null) free_consumer = index; + } + const consumer_index = free_consumer orelse return error.ConsumerCapacity; + if (self.next_consumer_token == std.math.maxInt(u64)) return error.TokenOverflow; + var matching_job: ?usize = null; + var free_job: ?usize = null; + for (self.jobs, 0..) |entry, index| { + if (entry) |job| { + if (job.ref.epoch == args.epoch and std.mem.eql(u8, &job.physical_key, &args.physical_key) and + (job.state == .queued or job.state == .running) and job.active_consumers != 0) + { + // An expired final consumer must be swept before a new + // arrival can share, so it cannot revive abandoned work. + var live = false; + for (self.consumers) |consumer_entry| { + if (consumer_entry) |consumer| { + if (std.meta.eql(consumer.data.job, job.ref) and consumer.data.receipt == null and + consumer.data.deadline > now) live = true; + } + } + if (!live) return error.DeadlineExpired; + matching_job = index; + } + } else if (free_job == null) free_job = index; + } + const shared = matching_job != null; + const job_index = matching_job orelse free_job orelse return error.JobCapacity; + if (!shared) { + if (self.next_job_token == std.math.maxInt(u64)) return error.TokenOverflow; + for (self.jobs) |entry| { + if (entry) |job| { + if (job.ref.backing_id == args.backing_id and job.ref.epoch == args.epoch) return error.DuplicateJob; + } + } + self.jobs[job_index] = .{ + .ref = .{ .token = self.next_job_token, .backing_id = args.backing_id, .epoch = args.epoch }, + .physical_key = args.physical_key, + }; + self.next_job_token += 1; + } + const job = &self.jobs[job_index].?; + const consumer_ref: ConsumerRef = .{ .token = self.next_consumer_token }; + self.next_consumer_token += 1; + self.consumers[consumer_index] = .{ .ref = consumer_ref, .data = .{ + .identity = args.identity, + .job = job.ref, + .deadline = args.deadline, + .credit = args.initial_credit, + .remaining_output_budget = args.output_budget, + .delivered_bytes = 0, + .next_output_sequence = 0, + .next_grant_sequence = 0, + .pending = null, + .receipt = null, + } }; + job.active_consumers += 1; + job.retained_consumers += 1; + return .{ .consumer = consumer_ref, .job = job.ref, .shared = shared }; + } + + // Queue time counts. Run expire first when any queued consumer is overdue. + pub fn start(self: *Registry, ref: JobRef, now: u64) Error!void { + try self.observe_time(now); + const job = try self.find_job(ref); + if (job.state != .queued) return error.JobNotQueued; + for (self.consumers) |entry| { + if (entry) |consumer| { + if (std.meta.eql(consumer.data.job, ref) and consumer.data.receipt == null and + consumer.data.deadline <= now) return error.DeadlineExpired; + } + } + job.state = .running; + } + + // Grant sequences start at zero and must be contiguous, rejecting replay. + // Remaining budget excludes the one pending reservation, if any. + pub fn grant_credit(self: *Registry, ref: ConsumerRef, grant_sequence: u64, bytes: u64, now: u64) Error!void { + try self.observe_time(now); + const consumer = try self.live_consumer(ref, now); + if (grant_sequence != consumer.data.next_grant_sequence) return error.InvalidGrantSequence; + if (grant_sequence == std.math.maxInt(u64)) return error.SequenceOverflow; + if (bytes == 0 or bytes > consumer.data.remaining_output_budget - consumer.data.credit) return error.InvalidCredit; + consumer.data.credit += bytes; + consumer.data.next_grant_sequence += 1; + } + + // The trusted parent validates payload length and declares it exactly once. + // Zero is an explicit empty result. New consumers of a running shared job + // must still receive this full size; replay and payload storage are parent-owned. + pub fn declare_result_size(self: *Registry, ref: JobRef, expected_bytes: u64, now: u64) Error!void { + try self.observe_time(now); + const job = try self.find_job(ref); + if (job.state != .running) return error.JobNotRunning; + if (job.expected_result_bytes != null) return error.ResultSizeAlreadyDeclared; + job.expected_result_bytes = expected_bytes; + } + + // Reserve before queuing parent-owned bytes. At most one reservation exists + // per consumer; bytes immediately consume that consumer's output budget. + pub fn reserve_output(self: *Registry, ref: JobRef, consumer_ref: ConsumerRef, bytes: u64, now: u64) Error!Reservation { + try self.observe_time(now); + const job = try self.find_job(ref); + if (job.state != .running) return error.JobNotRunning; + const expected_bytes = job.expected_result_bytes orelse return error.ResultSizeUndeclared; + const consumer = try self.live_consumer(consumer_ref, now); + if (!std.meta.eql(consumer.data.job, ref)) return error.InvalidReservation; + if (consumer.data.pending != null) return error.OutputPending; + if (bytes == 0) return error.EmptyOutput; + if (consumer.data.next_output_sequence == std.math.maxInt(u64)) return error.SequenceOverflow; + if (bytes > consumer.data.remaining_output_budget) return error.OutputBudgetExceeded; + if (bytes > expected_bytes - consumer.data.delivered_bytes) return error.ResultSizeExceeded; + if (bytes > consumer.data.credit) return error.InsufficientCredit; + const reservation: Reservation = .{ + .consumer = consumer_ref, + .job = ref, + .sequence = consumer.data.next_output_sequence, + .bytes = bytes, + }; + consumer.data.next_output_sequence += 1; + consumer.data.remaining_output_budget -= bytes; + consumer.data.credit -= bytes; + consumer.data.pending = reservation; + return reservation; + } + + // Call only when the parent has delivered the reserved bytes. Repeating a + // delivery or using an old epoch cannot advance accounting twice. + pub fn deliver_output(self: *Registry, reservation: Reservation, now: u64) Error!void { + try self.observe_time(now); + const job = try self.find_job(reservation.job); + if (job.state != .running) return error.JobNotRunning; + const consumer = try self.live_consumer(reservation.consumer, now); + const pending = consumer.data.pending orelse return error.InvalidReservation; + if (!std.meta.eql(pending, reservation)) return error.InvalidReservation; + const sum = @addWithOverflow(consumer.data.delivered_bytes, reservation.bytes); + if (sum[1] != 0) return error.SequenceOverflow; + consumer.data.delivered_bytes = sum[0]; + consumer.data.pending = null; + } + + pub fn cancel(self: *Registry, ref: ConsumerRef, now: u64) Error!LeaveEvent { + try self.observe_time(now); + const consumer = try self.find_consumer(ref); + // Deadlines win over cancellation at or after the absolute deadline. + const outcome: Outcome = if (consumer.data.deadline <= now) .deadline_exceeded else .cancelled; + return self.leave(consumer, outcome); + } + + pub fn expire_consumer(self: *Registry, ref: ConsumerRef, now: u64) Error!LeaveEvent { + try self.observe_time(now); + const consumer = try self.find_consumer(ref); + if (consumer.data.receipt == null and consumer.data.deadline > now) return error.NotExpired; + return self.leave(consumer, .deadline_exceeded); + } + + // Returns events in caller-owned storage. A short buffer makes no lifecycle + // changes, allowing the parent to handle every final-consumer stop request. + pub fn expire(self: *Registry, now: u64, events: []LeaveEvent) Error![]LeaveEvent { + try self.observe_time(now); + var count: usize = 0; + for (self.consumers) |entry| { + if (entry) |consumer| { + if (consumer.data.receipt == null and consumer.data.deadline <= now) count += 1; + } + } + if (events.len < count) return error.BufferTooSmall; + var index: usize = 0; + for (self.consumers) |*entry| { + if (entry.*) |*consumer| { + if (consumer.data.receipt == null and consumer.data.deadline <= now) { + events[index] = try self.leave(consumer, .deadline_exceeded); + index += 1; + } + } + } + return events[0..index]; + } + + // Parent supplies a worker's terminal acknowledgement. Success requires an + // explicit size and full delivery to every active, unexpired consumer. + // A rejected success changes no state, including clock observation. Failed + // or stopped work may discard reservations. Stopped is an acknowledged stop. + pub fn settle(self: *Registry, ref: JobRef, result: Settlement, now: u64) Error!void { + if (now < self.last_time) return error.ClockRegression; + const job = try self.find_job(ref); + if (job.state != .running and job.state != .aborting) return error.JobNotRunning; + if (result == .success) { + const expected_bytes = job.expected_result_bytes orelse return error.ResultSizeUndeclared; + for (self.consumers) |entry| { + if (entry) |consumer| { + if (std.meta.eql(consumer.data.job, ref) and consumer.data.receipt == null and consumer.data.deadline > now) { + if (consumer.data.pending != null) return error.OutputPending; + if (consumer.data.delivered_bytes != expected_bytes) return error.ResultNotDrained; + } + } + } + } + try self.observe_time(now); + const outcome: Outcome = switch (result) { + .success => .success, + .failed => .failed, + .stopped => .stopped, + }; + self.finish_job(job, outcome, .settled, now); + } + + // Losing a started worker leaves effects uncertain. There is no retry API. + pub fn worker_loss(self: *Registry, ref: JobRef, now: u64) Error!void { + try self.observe_time(now); + const job = try self.find_job(ref); + if (job.state == .settled) return error.JobNotRunning; + const phase: Phase = if (job.state == .queued) .not_started else .uncertain; + self.finish_job(job, .worker_lost, phase, now); + } + + pub fn query_consumer(self: *Registry, ref: ConsumerRef) Error!ConsumerSnapshot { + return (try self.find_consumer(ref)).data; + } + + pub fn query_job(self: *Registry, ref: JobRef) Error!JobSnapshot { + const job = try self.find_job(ref); + return .{ .state = job.state, .active_consumers = job.active_consumers, .expected_result_bytes = job.expected_result_bytes, .receipt = job.receipt }; + } + + // Terminal records are retained until the parent consumes their receipts. + pub fn reclaim_consumer(self: *Registry, ref: ConsumerRef) Error!void { + for (self.consumers) |*entry| { + if (entry.*) |consumer| { + if (consumer.ref.token == ref.token) { + if (consumer.data.receipt == null) return error.NotTerminal; + const job = try self.find_job(consumer.data.job); + job.retained_consumers -= 1; + entry.* = null; + return; + } + } + } + return error.UnknownConsumer; + } + + // Even terminal jobs cannot be reclaimed while any receipt references them. + pub fn reclaim_job(self: *Registry, ref: JobRef) Error!void { + const job = try self.find_job(ref); + if (job.state != .settled) return error.NotTerminal; + if (job.retained_consumers != 0) return error.ConsumersRetained; + for (self.jobs) |*entry| { + if (entry.*) |value| { + if (std.meta.eql(value.ref, ref)) { + entry.* = null; + return; + } + } + } + unreachable; + } + + fn observe_time(self: *Registry, now: u64) Error!void { + if (now < self.last_time) return error.ClockRegression; + self.last_time = now; + } + + fn find_job(self: *Registry, ref: JobRef) Error!*Job { + for (self.jobs) |*entry| { + if (entry.*) |*job| { + if (job.ref.token == ref.token) { + if (job.ref.epoch != ref.epoch) return error.StaleEpoch; + if (job.ref.backing_id != ref.backing_id) return error.UnknownJob; + return job; + } + } + } + return error.UnknownJob; + } + + fn find_consumer(self: *Registry, ref: ConsumerRef) Error!*Consumer { + for (self.consumers) |*entry| { + if (entry.*) |*consumer| { + if (consumer.ref.token == ref.token) return consumer; + } + } + return error.UnknownConsumer; + } + + fn live_consumer(self: *Registry, ref: ConsumerRef, now: u64) Error!*Consumer { + const consumer = try self.find_consumer(ref); + if (consumer.data.receipt != null) return error.ConsumerTerminal; + if (consumer.data.deadline <= now) return error.DeadlineExpired; + return consumer; + } + + fn leave(self: *Registry, consumer: *Consumer, outcome: Outcome) Error!LeaveEvent { + const job = try self.find_job(consumer.data.job); + var action: Action = .none; + if (consumer.data.receipt == null) { + const phase: Phase = if (job.state == .queued) .not_started else .uncertain; + consumer.data.receipt = .{ .outcome = outcome, .phase = phase, .delivered_bytes = consumer.data.delivered_bytes }; + consumer.data.pending = null; + consumer.data.credit = 0; + job.active_consumers -= 1; + if (job.active_consumers == 0) { + if (job.state == .queued) { + job.state = .settled; + job.receipt = .{ .outcome = outcome, .phase = .not_started, .delivered_bytes = 0 }; + action = .not_started; + } else if (job.state == .running) { + job.state = .aborting; + action = .stop_requested; + } + } + } + return .{ .consumer = consumer.ref, .job = job.ref, .receipt = consumer.data.receipt.?, .action = action }; + } + + fn finish_job(self: *Registry, job: *Job, outcome: Outcome, phase: Phase, now: u64) void { + for (self.consumers) |*entry| { + if (entry.*) |*consumer| { + if (std.meta.eql(consumer.data.job, job.ref) and consumer.data.receipt == null) { + const expired = consumer.data.deadline <= now; + consumer.data.receipt = .{ + .outcome = if (expired) .deadline_exceeded else outcome, + .phase = if (expired and phase != .not_started) .uncertain else phase, + .delivered_bytes = consumer.data.delivered_bytes, + }; + consumer.data.pending = null; + consumer.data.credit = 0; + } + } + } + job.active_consumers = 0; + job.state = .settled; + job.receipt = .{ .outcome = outcome, .phase = phase, .delivered_bytes = 0 }; + } +}; + +fn request(client_id: u64, deadline: u64) Subscribe { + return .{ .physical_key = [_]u8{7} ** 32, .epoch = 3, .backing_id = client_id, .identity = .{ .client_id = client_id, .call_id = 1 }, .deadline = deadline, .output_budget = 20, .initial_credit = 10 }; +} + +test "two consumers share and cancellation preserves the other" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const first = try registry.subscribe(request(1, 10), 0); + const second = try registry.subscribe(request(2, 30), 0); + try std.testing.expect(second.shared); + try std.testing.expectEqual(first.job, second.job); + try registry.start(first.job, 1); + const cancelled = try registry.cancel(first.consumer, 2); + try std.testing.expectEqual(Action.none, cancelled.action); + try std.testing.expectEqual(Phase.uncertain, cancelled.receipt.phase); + try registry.declare_result_size(second.job, 5, 3); + const reserved = try registry.reserve_output(second.job, second.consumer, 5, 3); + try registry.deliver_output(reserved, 3); + try registry.settle(second.job, .success, 4); + try std.testing.expectEqual(Outcome.cancelled, (try registry.query_consumer(first.consumer)).receipt.?.outcome); + try std.testing.expectEqual(@as(u64, 5), (try registry.query_consumer(second.consumer)).receipt.?.delivered_bytes); +} + +test "queue deadlines expire independently and start uses surviving deadline" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const first = try registry.subscribe(request(1, 10), 0); + const second = try registry.subscribe(request(2, 30), 0); + try std.testing.expectError(error.DeadlineExpired, registry.start(first.job, 10)); + var empty: [0]LeaveEvent = .{}; + try std.testing.expectError(error.BufferTooSmall, registry.expire(10, &empty)); + try std.testing.expectEqual(@as(usize, 2), (try registry.query_job(first.job)).active_consumers); + var events: [2]LeaveEvent = undefined; + const expired = try registry.expire(10, &events); + try std.testing.expectEqual(@as(usize, 1), expired.len); + try std.testing.expectEqual(Phase.not_started, expired[0].receipt.phase); + try registry.start(second.job, 10); + try registry.declare_result_size(second.job, 1, 10); + try std.testing.expectEqual(@as(u64, 30), (try registry.query_consumer(second.consumer)).deadline); + try std.testing.expectError(error.DeadlineExpired, registry.reserve_output(second.job, second.consumer, 1, 30)); + const last = try registry.expire_consumer(second.consumer, 30); + try std.testing.expectEqual(Action.stop_requested, last.action); +} + +test "last queued cancellation never starts and draining job refuses new attachment" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const queued = try registry.subscribe(request(1, 100), 0); + const event = try registry.cancel(queued.consumer, 1); + try std.testing.expectEqual(Action.not_started, event.action); + try std.testing.expectEqual(Phase.not_started, event.receipt.phase); + try std.testing.expectError(error.JobNotQueued, registry.start(queued.job, 1)); + const first = try registry.subscribe(request(2, 100), 1); + const second = try registry.subscribe(request(3, 100), 1); + try registry.start(first.job, 2); + _ = try registry.cancel(first.consumer, 3); + const last = try registry.cancel(second.consumer, 3); + try std.testing.expectEqual(Action.stop_requested, last.action); + try std.testing.expectEqual(JobState.aborting, (try registry.query_job(first.job)).state); + const third = try registry.subscribe(request(4, 100), 4); + try std.testing.expect(!third.shared); + try std.testing.expect(third.job.token != first.job.token); + try std.testing.expectError(error.JobNotRunning, registry.reserve_output(first.job, first.consumer, 1, 4)); + try registry.settle(first.job, .stopped, 5); + try std.testing.expectEqual(Phase.uncertain, (try registry.query_consumer(second.consumer)).receipt.?.phase); + try std.testing.expectEqual(Outcome.stopped, (try registry.query_job(first.job)).receipt.?.outcome); +} + +test "credits sequences and output budget reject duplicate or overflowing accounting" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const sub = try registry.subscribe(request(1, 100), 0); + try registry.start(sub.job, 0); + try registry.declare_result_size(sub.job, 20, 0); + try registry.grant_credit(sub.consumer, 0, 10, 0); + try std.testing.expectError(error.InvalidGrantSequence, registry.grant_credit(sub.consumer, 0, 1, 0)); + try std.testing.expectError(error.InvalidCredit, registry.grant_credit(sub.consumer, 1, 1, 0)); + const output = try registry.reserve_output(sub.job, sub.consumer, 20, 0); + try std.testing.expectError(error.OutputPending, registry.reserve_output(sub.job, sub.consumer, 1, 0)); + try std.testing.expectError(error.InvalidCredit, registry.grant_credit(sub.consumer, 1, 1, 0)); + try registry.deliver_output(output, 0); + try std.testing.expectError(error.InvalidReservation, registry.deliver_output(output, 0)); + try std.testing.expectError(error.OutputBudgetExceeded, registry.reserve_output(sub.job, sub.consumer, 1, 0)); + const consumer = try registry.find_consumer(sub.consumer); + consumer.data.next_output_sequence = std.math.maxInt(u64); + try std.testing.expectError(error.SequenceOverflow, registry.reserve_output(sub.job, sub.consumer, 1, 0)); + consumer.data.next_grant_sequence = std.math.maxInt(u64); + try std.testing.expectError(error.SequenceOverflow, registry.grant_credit(sub.consumer, std.math.maxInt(u64), 1, 0)); +} + +test "maximum byte credit and stale epochs cannot overflow or deliver late output" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + var args = request(1, 100); + args.output_budget = std.math.maxInt(u64); + args.initial_credit = std.math.maxInt(u64); + const sub = try registry.subscribe(args, 0); + try registry.start(sub.job, 0); + try registry.declare_result_size(sub.job, std.math.maxInt(u64), 0); + try std.testing.expectError(error.InvalidCredit, registry.grant_credit(sub.consumer, 0, 1, 0)); + var stale = sub.job; + stale.epoch += 1; + try std.testing.expectError(error.StaleEpoch, registry.reserve_output(stale, sub.consumer, 1, 0)); + try std.testing.expectError(error.StaleEpoch, registry.settle(stale, .success, 0)); + const output = try registry.reserve_output(sub.job, sub.consumer, std.math.maxInt(u64), 0); + var stale_output = output; + stale_output.job = stale; + try std.testing.expectError(error.StaleEpoch, registry.deliver_output(stale_output, 0)); + try registry.deliver_output(output, 0); + try std.testing.expectEqual(@as(u64, std.math.maxInt(u64)), (try registry.query_consumer(sub.consumer)).delivered_bytes); + try registry.worker_loss(sub.job, 1); + try std.testing.expectEqual(Phase.uncertain, (try registry.query_consumer(sub.consumer)).receipt.?.phase); + try std.testing.expectError(error.JobNotRunning, registry.deliver_output(output, 1)); + try std.testing.expectError(error.JobNotQueued, registry.start(sub.job, 1)); +} + +test "duplicates capacities receipts and reclamation protect stale handles" { + var registry = try Registry.init(std.testing.allocator, .{ .max_jobs = 1, .max_consumers = 2 }); + defer registry.deinit(); + const sub = try registry.subscribe(request(1, 100), 0); + try std.testing.expectError(error.DuplicateConsumer, registry.subscribe(request(1, 100), 0)); + var unique = request(3, 100); + unique.physical_key[0] = 1; + try std.testing.expectError(error.JobCapacity, registry.subscribe(unique, 0)); + const second = try registry.subscribe(request(2, 100), 0); + try std.testing.expectError(error.ConsumerCapacity, registry.subscribe(request(3, 100), 0)); + try std.testing.expectError(error.NotTerminal, registry.reclaim_consumer(sub.consumer)); + _ = try registry.cancel(sub.consumer, 1); + _ = try registry.cancel(second.consumer, 1); + try std.testing.expectError(error.DuplicateConsumer, registry.subscribe(request(1, 100), 1)); + try std.testing.expectError(error.ConsumersRetained, registry.reclaim_job(sub.job)); + try std.testing.expectEqual(Outcome.cancelled, (try registry.query_consumer(sub.consumer)).receipt.?.outcome); + try registry.reclaim_consumer(sub.consumer); + try registry.reclaim_consumer(second.consumer); + try registry.reclaim_job(sub.job); + const replacement = try registry.subscribe(request(1, 100), 2); + try std.testing.expect(replacement.consumer.token != sub.consumer.token); + try std.testing.expect(replacement.job.token != sub.job.token); + try std.testing.expectError(error.UnknownConsumer, registry.cancel(sub.consumer, 2)); + try std.testing.expectError(error.UnknownJob, registry.start(sub.job, 2)); + try std.testing.expectError(error.ClockRegression, registry.cancel(replacement.consumer, 1)); +} + +test "settlement enforces absolute deadline and discards pending delivery" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const sub = try registry.subscribe(request(1, 10), 0); + try registry.start(sub.job, 1); + try registry.declare_result_size(sub.job, 5, 1); + const pending = try registry.reserve_output(sub.job, sub.consumer, 5, 2); + try registry.settle(sub.job, .success, 10); + const receipt = (try registry.query_consumer(sub.consumer)).receipt.?; + try std.testing.expectEqual(Outcome.deadline_exceeded, receipt.outcome); + try std.testing.expectEqual(@as(u64, 0), receipt.delivered_bytes); + try std.testing.expectError(error.JobNotRunning, registry.deliver_output(pending, 10)); + const queued = try registry.subscribe(request(2, 100), 10); + try registry.worker_loss(queued.job, 11); + try std.testing.expectEqual(Phase.not_started, (try registry.query_consumer(queued.consumer)).receipt.?.phase); +} + +test "an expired final subscriber cannot revive running work before a sweep" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const original = try registry.subscribe(request(1, 5), 0); + try registry.start(original.job, 0); + try std.testing.expectError(error.DeadlineExpired, registry.subscribe(request(2, 50), 5)); + var events: [1]LeaveEvent = undefined; + _ = try registry.expire(5, &events); + try std.testing.expectEqual(Action.stop_requested, events[0].action); + const replacement = try registry.subscribe(request(2, 50), 5); + try std.testing.expect(!replacement.shared); + try std.testing.expect(original.job.token != replacement.job.token); +} + +test "pending output rejects success without changing state and drained output settles" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const sub = try registry.subscribe(request(1, 100), 0); + try registry.start(sub.job, 1); + try std.testing.expectError(error.ResultSizeUndeclared, registry.reserve_output(sub.job, sub.consumer, 5, 1)); + try registry.declare_result_size(sub.job, 5, 1); + try std.testing.expectError(error.ResultSizeExceeded, registry.reserve_output(sub.job, sub.consumer, 6, 1)); + const pending = try registry.reserve_output(sub.job, sub.consumer, 5, 2); + const before_consumer = try registry.query_consumer(sub.consumer); + const before_job = try registry.query_job(sub.job); + try std.testing.expectError(error.OutputPending, registry.settle(sub.job, .success, 3)); + try std.testing.expect(std.meta.eql(before_consumer, try registry.query_consumer(sub.consumer))); + try std.testing.expect(std.meta.eql(before_job, try registry.query_job(sub.job))); + try std.testing.expectEqual(@as(u64, 2), registry.last_time); + try registry.deliver_output(pending, 2); + try registry.settle(sub.job, .success, 3); + const receipt = (try registry.query_consumer(sub.consumer)).receipt.?; + try std.testing.expectEqual(Outcome.success, receipt.outcome); + try std.testing.expectEqual(@as(u64, 5), receipt.delivered_bytes); +} + +test "credit blocked subscriber must drain before shared job succeeds" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const first = try registry.subscribe(request(1, 100), 0); + var blocked_args = request(2, 100); + blocked_args.initial_credit = 0; + const blocked = try registry.subscribe(blocked_args, 0); + try registry.start(first.job, 1); + try registry.declare_result_size(first.job, 5, 1); + try registry.deliver_output(try registry.reserve_output(first.job, first.consumer, 5, 1), 1); + try std.testing.expectError(error.InsufficientCredit, registry.reserve_output(blocked.job, blocked.consumer, 5, 1)); + try std.testing.expectError(error.ResultNotDrained, registry.settle(first.job, .success, 2)); + try std.testing.expectEqual(JobState.running, (try registry.query_job(first.job)).state); + try std.testing.expect((try registry.query_consumer(first.consumer)).receipt == null); + try std.testing.expect((try registry.query_consumer(blocked.consumer)).receipt == null); + try registry.grant_credit(blocked.consumer, 0, 5, 1); + const pending = try registry.reserve_output(blocked.job, blocked.consumer, 5, 1); + try std.testing.expectError(error.OutputPending, registry.settle(first.job, .success, 2)); + try registry.deliver_output(pending, 1); + try registry.settle(first.job, .success, 2); + try std.testing.expectEqual(Outcome.success, (try registry.query_consumer(blocked.consumer)).receipt.?.outcome); +} + +test "zero byte success needs an explicit immutable declaration" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const sub = try registry.subscribe(request(1, 100), 0); + try std.testing.expectError(error.JobNotRunning, registry.declare_result_size(sub.job, 0, 0)); + try registry.start(sub.job, 1); + try std.testing.expectError(error.ResultSizeUndeclared, registry.settle(sub.job, .success, 2)); + try registry.declare_result_size(sub.job, 0, 1); + try std.testing.expectError(error.ResultSizeAlreadyDeclared, registry.declare_result_size(sub.job, 0, 1)); + try std.testing.expectError(error.ResultSizeAlreadyDeclared, registry.declare_result_size(sub.job, 1, 1)); + try std.testing.expectEqual(@as(?u64, 0), (try registry.query_job(sub.job)).expected_result_bytes); + try std.testing.expectError(error.ResultSizeExceeded, registry.reserve_output(sub.job, sub.consumer, 1, 1)); + var stale = sub.job; + stale.epoch += 1; + try std.testing.expectError(error.StaleEpoch, registry.declare_result_size(stale, 0, 1)); + try registry.settle(sub.job, .success, 2); + try std.testing.expectEqual(@as(u64, 0), (try registry.query_consumer(sub.consumer)).receipt.?.delivered_bytes); +} + +test "cancelled and expired pending output cannot block another consumer drain" { + var registry = try Registry.init(std.testing.allocator, .{}); + defer registry.deinit(); + const cancelled = try registry.subscribe(request(1, 100), 0); + const expired = try registry.subscribe(request(2, 5), 0); + const survivor = try registry.subscribe(request(3, 100), 0); + try registry.start(survivor.job, 1); + try registry.declare_result_size(survivor.job, 5, 1); + _ = try registry.reserve_output(cancelled.job, cancelled.consumer, 5, 1); + const expired_pending = try registry.reserve_output(expired.job, expired.consumer, 5, 1); + try registry.deliver_output(try registry.reserve_output(survivor.job, survivor.consumer, 5, 1), 1); + _ = try registry.cancel(cancelled.consumer, 2); + try registry.settle(survivor.job, .success, 5); + try std.testing.expectEqual(Outcome.cancelled, (try registry.query_consumer(cancelled.consumer)).receipt.?.outcome); + try std.testing.expectEqual(Outcome.deadline_exceeded, (try registry.query_consumer(expired.consumer)).receipt.?.outcome); + try std.testing.expectEqual(@as(u64, 0), (try registry.query_consumer(expired.consumer)).receipt.?.delivered_bytes); + try std.testing.expectEqual(Outcome.success, (try registry.query_consumer(survivor.consumer)).receipt.?.outcome); + try std.testing.expectError(error.JobNotRunning, registry.deliver_output(expired_pending, 5)); +} + +fn allocation_check(allocator: std.mem.Allocator) !void { + var registry = try Registry.init(allocator, .{ .max_jobs = 2, .max_consumers = 3 }); + defer registry.deinit(); +} + +test "invalid capacities reject before allocation and partial allocations clean up" { + try std.testing.expectError(error.InvalidOptions, Registry.init(std.testing.allocator, .{ .max_jobs = std.math.maxInt(usize) })); + try std.testing.expectError(error.InvalidOptions, Registry.init(std.testing.allocator, .{ .max_consumers = 0 })); + try std.testing.checkAllAllocationFailures(std.testing.allocator, allocation_check, .{}); +} diff --git a/experiments/agent_cast/src/tests.zig b/experiments/agent_cast/src/tests.zig index d704f5f289..1273ea0845 100644 --- a/experiments/agent_cast/src/tests.zig +++ b/experiments/agent_cast/src/tests.zig @@ -3,4 +3,9 @@ test { _ = @import("optimizer.zig"); _ = @import("executor.zig"); _ = @import("demo.zig"); + _ = @import("cache.zig"); + _ = @import("cached_executor.zig"); + _ = @import("cache_demo.zig"); + _ = @import("subscribers.zig"); + _ = @import("wire.zig"); } diff --git a/experiments/agent_cast/src/wire.zig b/experiments/agent_cast/src/wire.zig new file mode 100644 index 0000000000..6d1d6eef81 --- /dev/null +++ b/experiments/agent_cast/src/wire.zig @@ -0,0 +1,245 @@ +const std = @import("std"); + +pub const protocol_version = "agent-cast/0.1"; +pub const default_frame_bytes = 64 * 1024; +pub const hard_frame_bytes = 1024 * 1024; +const max_json_depth = 32; + +pub const Limits = struct { frame_bytes: usize = default_frame_bytes }; + +// One owned, bounded payload buffer. A returned frame borrows it until release(). +// The caller must release a complete frame before feeding more data. Errors poison +// the framer, so an attacker cannot resynchronize after an invalid length. +pub const Framer = struct { + payload: []u8, + header: [4]u8 = @splat(0), + header_used: usize = 0, + payload_expected: usize = 0, + payload_used: usize = 0, + ready: bool = false, + poisoned: bool = false, + + pub fn init(alloc: std.mem.Allocator, limits: Limits) !Framer { + if (limits.frame_bytes == 0 or limits.frame_bytes > hard_frame_bytes) return error.InvalidFrameLimit; + return .{ .payload = try alloc.alloc(u8, limits.frame_bytes) }; + } + + pub fn deinit(self: *Framer, alloc: std.mem.Allocator) void { + alloc.free(self.payload); + self.* = undefined; + } + + pub const Feed = struct { consumed: usize, frame: ?[]const u8 }; + + pub fn push(self: *Framer, input: []const u8) !Feed { + if (self.poisoned) return error.FramerPoisoned; + if (self.ready) return error.FrameNotReleased; + var consumed: usize = 0; + if (self.header_used < self.header.len) { + const count = @min(self.header.len - self.header_used, input.len); + @memcpy(self.header[self.header_used..][0..count], input[0..count]); + self.header_used += count; + consumed += count; + if (self.header_used < self.header.len) return .{ .consumed = consumed, .frame = null }; + self.payload_expected = std.mem.readInt(u32, &self.header, .big); + if (self.payload_expected == 0 or self.payload_expected > self.payload.len) { + self.poisoned = true; + return error.InvalidFrameLength; + } + } + const count = @min(self.payload_expected - self.payload_used, input.len - consumed); + @memcpy(self.payload[self.payload_used..][0..count], input[consumed..][0..count]); + self.payload_used += count; + consumed += count; + self.ready = self.payload_used == self.payload_expected; + return .{ .consumed = consumed, .frame = if (self.ready) self.payload[0..self.payload_expected] else null }; + } + + pub fn release(self: *Framer) !void { + if (!self.ready) return error.NoCompleteFrame; + self.header_used = 0; + self.payload_used = 0; + self.payload_expected = 0; + self.ready = false; + } + + pub fn finish(self: *const Framer) !void { + if (self.poisoned) return error.FramerPoisoned; + if (self.header_used != 0 or self.payload_used != 0 or self.ready) return error.TruncatedFrame; + } +}; + +// Caller owns the encoded frame. Header is an unsigned big-endian byte length. +pub fn encode_frame(alloc: std.mem.Allocator, body: []const u8, limits: Limits) ![]u8 { + if (limits.frame_bytes == 0 or limits.frame_bytes > hard_frame_bytes) return error.InvalidFrameLimit; + if (body.len == 0 or body.len > limits.frame_bytes) return error.InvalidFrameLength; + const output = try alloc.alloc(u8, body.len + 4); + std.mem.writeInt(u32, output[0..4], @intCast(body.len), .big); + @memcpy(output[4..], body); + return output; +} + +// JSON-RPC calls use bounded string IDs. Missing IDs are notifications; the +// broker restricts notifications to permitted event methods. Params are objects. +pub const Request = struct { + jsonrpc: []const u8, + id: ?[]const u8 = null, + method: []const u8, + params: ?std.json.Value = null, +}; + +pub const ParsedRequest = struct { + storage: std.json.Parsed(std.json.Value), + value: Request, + + pub fn deinit(self: ParsedRequest) void { + self.storage.deinit(); + } +}; + +// Parsed strings and nested values are owned by ParsedRequest; caller deinitializes it. +// Duplicate fields are rejected at every object depth, including params. +// Field presence is preserved: absent id denotes notification; explicit null rejects. +pub fn parse_request(alloc: std.mem.Allocator, body: []const u8, limits: Limits) !ParsedRequest { + if (limits.frame_bytes == 0 or limits.frame_bytes > hard_frame_bytes) return error.InvalidFrameLimit; + if (body.len == 0 or body.len > limits.frame_bytes) return error.InvalidFrameLength; + try preflight_json(body); + const parsed = try std.json.parseFromSlice(std.json.Value, alloc, body, .{ + .duplicate_field_behavior = .@"error", + .ignore_unknown_fields = false, + .allocate = .alloc_always, + .max_value_len = limits.frame_bytes, + }); + errdefer parsed.deinit(); + if (parsed.value != .object) return error.InvalidEnvelope; + const object = parsed.value.object; + for (object.keys()) |key| { + if (!std.mem.eql(u8, key, "jsonrpc") and !std.mem.eql(u8, key, "id") and + !std.mem.eql(u8, key, "method") and !std.mem.eql(u8, key, "params")) return error.UnknownField; + } + const rpc = object.get("jsonrpc") orelse return error.MissingField; + if (rpc != .string or !std.mem.eql(u8, rpc.string, "2.0")) return error.InvalidRpcVersion; + const method = object.get("method") orelse return error.MissingField; + if (method != .string or !identifier(method.string, 64)) return error.InvalidMethod; + var id: ?[]const u8 = null; + if (object.get("id")) |field| { + if (field != .string or !identifier(field.string, 96)) return error.InvalidRequestId; + id = field.string; + } + const params = object.get("params"); + if (params) |field| { + if (field != .object) return error.InvalidParams; + } + return .{ .storage = parsed, .value = .{ .jsonrpc = rpc.string, .id = id, .method = method.string, .params = params } }; +} + +fn identifier(value: []const u8, max_bytes: usize) bool { + if (value.len == 0 or value.len > max_bytes) return false; + for (value) |byte| { + if (!std.ascii.isAlphanumeric(byte) and byte != '.' and byte != '_' and byte != '-' and byte != ':') return false; + } + return true; +} + +fn preflight_json(body: []const u8) !void { + if (!std.unicode.utf8ValidateSlice(body)) return error.InvalidUtf8; + var in_string = false; + var escaped = false; + var depth: usize = 0; + for (body) |byte| { + if (in_string) { + if (escaped) { + escaped = false; + } else if (byte == '\\') { + escaped = true; + } else if (byte == '"') { + in_string = false; + } + } else switch (byte) { + '"' => in_string = true, + '{', '[' => { + depth += 1; + if (depth > max_json_depth) return error.JsonTooDeep; + }, + '}', ']' => { + if (depth == 0) return error.InvalidJsonNesting; + depth -= 1; + }, + else => {}, + } + } + if (depth != 0 or in_string) return error.InvalidJsonNesting; +} + +test "every header and body split reconstructs the exact frame" { + const body = "{\"jsonrpc\":\"2.0\",\"id\":\"r1\",\"method\":\"hello\"}"; + const frame = try encode_frame(std.testing.allocator, body, .{}); + defer std.testing.allocator.free(frame); + for (0..frame.len + 1) |split| { + var framer = try Framer.init(std.testing.allocator, .{}); + defer framer.deinit(std.testing.allocator); + const first = try framer.push(frame[0..split]); + try std.testing.expectEqual(split, first.consumed); + const value = if (first.frame) |complete| complete else (try framer.push(frame[split..])).frame.?; + try std.testing.expectEqualStrings(body, value); + try std.testing.expectError(error.FrameNotReleased, framer.push("")); + try framer.release(); + try framer.finish(); + } +} + +test "concatenated frames consume only one message and EOF rejects truncation" { + const bytes = [_]u8{ 0, 0, 0, 2, '{', '}', 0, 0, 0, 2, '[', ']' }; + var framer = try Framer.init(std.testing.allocator, .{ .frame_bytes = 4 }); + defer framer.deinit(std.testing.allocator); + const first = try framer.push(&bytes); + try std.testing.expectEqual(@as(usize, 6), first.consumed); + try std.testing.expectEqualStrings("{}", first.frame.?); + try framer.release(); + const second = try framer.push(bytes[first.consumed..]); + try std.testing.expectEqualStrings("[]", second.frame.?); + try framer.release(); + _ = try framer.push(bytes[0..3]); + try std.testing.expectError(error.TruncatedFrame, framer.finish()); +} + +test "invalid lengths poison the connection before payload growth" { + for ([_][4]u8{ .{ 0, 0, 0, 0 }, .{ 0xff, 0xff, 0xff, 0xff } }) |header| { + var framer = try Framer.init(std.testing.allocator, .{ .frame_bytes = 8 }); + defer framer.deinit(std.testing.allocator); + const pointer = framer.payload.ptr; + try std.testing.expectError(error.InvalidFrameLength, framer.push(&header)); + try std.testing.expectEqual(pointer, framer.payload.ptr); + try std.testing.expectEqual(@as(usize, 8), framer.payload.len); + try std.testing.expectError(error.FramerPoisoned, framer.push("{}")); + } + try std.testing.expectError(error.InvalidFrameLimit, Framer.init(std.testing.allocator, .{ .frame_bytes = hard_frame_bytes + 1 })); +} + +test "JSON-RPC parsing rejects duplicate nested fields and owns its input" { + try std.testing.expectError(error.DuplicateField, parse_request(std.testing.allocator, "{\"jsonrpc\":\"2.0\",\"method\":\"hello\",\"method\":\"read\"}", .{})); + try std.testing.expectError(error.DuplicateField, parse_request(std.testing.allocator, "{\"jsonrpc\":\"2.0\",\"method\":\"read\",\"params\":{\"agent\":\"a\",\"agent\":\"b\"}}", .{})); + var input = "{\"jsonrpc\":\"2.0\",\"id\":\"r1\",\"method\":\"hello\",\"params\":{}}".*; + const parsed = try parse_request(std.testing.allocator, &input, .{}); + defer parsed.deinit(); + @memset(&input, 'X'); + try std.testing.expectEqualStrings("hello", parsed.value.method); + try std.testing.expectEqualStrings("r1", parsed.value.id.?); +} + +test "invalid RPC identity params UTF8 and excessive nesting fail" { + try std.testing.expectError(error.InvalidRpcVersion, parse_request(std.testing.allocator, "{\"jsonrpc\":\"1.0\",\"method\":\"hello\"}", .{})); + try std.testing.expectError(error.InvalidRequestId, parse_request(std.testing.allocator, "{\"jsonrpc\":\"2.0\",\"id\":\"../oops\",\"method\":\"hello\"}", .{})); + try std.testing.expectError(error.InvalidParams, parse_request(std.testing.allocator, "{\"jsonrpc\":\"2.0\",\"method\":\"hello\",\"params\":[]}", .{})); + try std.testing.expectError(error.InvalidUtf8, parse_request(std.testing.allocator, "\xff", .{})); + const too_deep = "[" ** 33 ++ "]" ** 33; + try std.testing.expectError(error.JsonTooDeep, parse_request(std.testing.allocator, too_deep, .{})); +} + +test "absent ID is a notification but present null ID or params rejects" { + const notification = try parse_request(std.testing.allocator, "{\"jsonrpc\":\"2.0\",\"method\":\"event\"}", .{}); + defer notification.deinit(); + try std.testing.expectEqual(@as(?[]const u8, null), notification.value.id); + try std.testing.expectError(error.InvalidRequestId, parse_request(std.testing.allocator, "{\"jsonrpc\":\"2.0\",\"id\":null,\"method\":\"hello\"}", .{})); + try std.testing.expectError(error.InvalidParams, parse_request(std.testing.allocator, "{\"jsonrpc\":\"2.0\",\"method\":\"hello\",\"params\":null}", .{})); +} diff --git a/scripts/qualify_agent_cast.py b/scripts/qualify_agent_cast.py index 635edd0aae..820d771da8 100644 --- a/scripts/qualify_agent_cast.py +++ b/scripts/qualify_agent_cast.py @@ -6,6 +6,7 @@ import hashlib import json import platform +import re import subprocess import tempfile import time @@ -76,12 +77,63 @@ def verify_report(report, content, mode): raise ValueError("single-mode run claims comparative parity") +def verify_cache_report(report, content): + if report["fixture_verified"] is not True or report["logical_call_count"] != 11: + raise ValueError("cache demo did not retain its useful logical workload") + if report["immutable_input_sha256"] != hashlib.sha256(content).hexdigest() or report["immutable_input_bytes"] != len(content): + raise ValueError("cache demo did not capture the actual supplied fixture") + limits, stats = report["limits"], report["cache"] + if not 0 < stats["metadata_bytes"] <= stats["retained_bytes"] <= limits["max_bytes"]: + raise ValueError("cache retained byte accounting exceeds its stated limits") + if stats["entries"] != 4 or stats["entries"] > limits["max_entries"]: + raise ValueError("cache did not retain four independent actual values") + if (stats["hits"], stats["misses"], stats["backing_reads"], stats["rejected"]) != (4, 4, 4, 3): + raise ValueError("cache reported incorrect physical work or rejected consumers") + if not 0 < report["batch_limits"]["max_result_bytes"] <= 16 * 1024 * 1024: + raise ValueError("cache demo has no bounded aggregate output reservation") + names = ["cold-agent-a", "completed-values-agent-b", "rejected-consumers"] + if [batch["name"] for batch in report["batches"]] != names: + raise ValueError("wrong independent cache batches") + next_id = 1 + for index, batch in enumerate(report["batches"]): + expected_count = 3 if index == 2 else 4 + if batch["logical_calls"] != expected_count or len(batch["receipts"]) != expected_count: + raise ValueError("cache logical calls disappeared") + expected_counts = [(0, 4, 4), (4, 0, 0), (0, 0, 0)][index] + if (batch["cache_hits"], batch["cache_misses"], batch["backing_reads"]) != expected_counts: + raise ValueError("wrong batch provenance counters") + for window, call in enumerate(batch["receipts"]): + if call["logical_id"] != next_id: + raise ValueError("cache changed per-logical identity") + next_id += 1 + if call["principal_domain"] != "fixture-operator" or call["task_id"] != "cache-fixture-task": + raise ValueError("cache changed the admitted task or principal") + if index < 2: + expected = content[window * 64:(window + 1) * 64] + if call["agent_id"] != ("agent-a" if index == 0 else "agent-b"): + raise ValueError("cache changed consumer identity") + if call["admission_status"] != "admitted" or call["status"] != "success": + raise ValueError("cache allowed read did not succeed") + if (call["source"], call["cache_hit"], call["cache_miss"], call["backing_reads"]) != [("backing", False, True, 1), ("cache", True, False, 0)][index]: + raise ValueError("cache hit or backing provenance is incorrect") + else: + expected = b"" + admission = ["denied", "stale_authority", "binding_mismatch"][window] + agent = ["denied-agent", "stale-agent", "mismatched-agent"][window] + if call["admission_status"] != admission or call["agent_id"] != agent or call["status"] != "admission_rejected": + raise ValueError("rejected cache consumer changed identity or received admission") + if (call["source"], call["cache_hit"], call["cache_miss"], call["backing_reads"]) != ("rejected", False, False, 0): + raise ValueError("rejected consumer performed cache or backing work") + if bytes.fromhex(call["output_hex"]) != expected or call["output_bytes"] != len(expected) or call["output_sha256"] != hashlib.sha256(expected).hexdigest(): + raise ValueError("cache output differs from actual independently supplied bytes") + + def qualify(zig, output): checks = [] receipt = { "schema_version": 1, "recorded_at_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(), - "scope": "local foundation correctness; logical agent identities in one process", + "scope": "local foundation/cache correctness and subscriber/framing unit checks; logical agents in one process; no broker or isolation qualification", "platform": platform.platform(), "machine": platform.machine(), "source_sha256": {str(p.relative_to(ROOT)): digest(p) for p in sorted(PROJECT.rglob("*.zig")) if "zig-out" not in p.parts and ".zig-cache" not in p.parts}, @@ -91,14 +143,14 @@ def qualify(zig, output): "ok": False, } - def run(name, argv, cwd=PROJECT): + def run(name, argv, cwd=PROJECT, expected_exit=0): start = time.monotonic() result = subprocess.run(argv, cwd=cwd, capture_output=True, text=True, timeout=300) (output / f"{name}.stdout").write_text(result.stdout) (output / f"{name}.stderr").write_text(result.stderr) check = {"name": name, "argv": [str(a) for a in argv], "cwd": str(cwd), "exit_code": result.returncode, "seconds": time.monotonic() - start, "stdout": f"{name}.stdout", "stderr": f"{name}.stderr"} checks.append(check) - if result.returncode: + if result.returncode != expected_exit: raise RuntimeError(f"{name} failed: {result.stderr[-2000:]}") return result @@ -109,12 +161,21 @@ def run(name, argv, cwd=PROJECT): receipt["zig_version"] = version run("format", [zig, "fmt", "--check", "src/", "build.zig"]) run("build", [zig, "build", "-Doptimize=ReleaseSafe", "-j1"]) - run("unit", [zig, "build", "test", "-Doptimize=ReleaseSafe", "-j1", "--summary", "all"]) + unit = run("unit", [zig, "build", "test", "-Doptimize=ReleaseSafe", "-j1", "--summary", "all"]) + counts = re.search(r"(\d+)/(\d+) tests passed", unit.stderr) + if not counts or counts[1] != counts[2] or int(counts[1]) < 30: + raise ValueError("unit receipt does not prove the nonempty foundation/cache test owner ran") + receipt["unit_tests_passed"] = int(counts[1]) binary = PROJECT / "zig-out" / "bin" / "agent-cast-demo" receipt["binary_sha256"] = digest(binary) help_result = run("help", [binary, "--help"]) if help_result.stderr or "--fixture" not in help_result.stdout: raise ValueError("built CLI help interaction failed") + cache_binary = PROJECT / "zig-out" / "bin" / "agent-cast-cache-demo" + receipt["cache_binary_sha256"] = digest(cache_binary) + cache_help = run("cache-help", [cache_binary, "--help"]) + if cache_help.stderr or "--fixture" not in cache_help.stdout: + raise ValueError("built cache CLI help failed") fixtures = [] for fixture_index in range(2): content = b"".join(f"fixture-{fixture_index}-window-{i:02d}".encode().ljust(64, bytes([65 + fixture_index + i])) for i in range(16)) @@ -127,7 +188,11 @@ def run(name, argv, cwd=PROJECT): raise ValueError("built comparison wrote unexpected stderr") report = json.loads(result.stdout) verify_report(report, content, "compare") - fixtures.append({"sha256": digest(path), "bytes": len(content), "comparison": f"compare-{fixture_index}.stdout", "logical_receipts_per_mode": 10, "disabled_backing_reads": report["runs"][0]["physical_reads"], "enabled_backing_reads": report["runs"][1]["physical_reads"]}) + cached = run(f"cache-{fixture_index}", [cache_binary, "--fixture", path]) + if cached.stderr: + raise ValueError("built cache interaction wrote unexpected stderr") + verify_cache_report(json.loads(cached.stdout), content) + fixtures.append({"sha256": digest(path), "bytes": len(content), "comparison": f"compare-{fixture_index}.stdout", "cache_comparison": f"cache-{fixture_index}.stdout", "logical_receipts_per_mode": 10, "cache_logical_receipts": 11, "disabled_backing_reads": report["runs"][0]["physical_reads"], "enabled_backing_reads": report["runs"][1]["physical_reads"]}) if fixtures[0]["sha256"] == fixtures[1]["sha256"]: raise ValueError("the independent fixtures have identical content") receipt["fixtures"] = fixtures @@ -137,6 +202,11 @@ def run(name, argv, cwd=PROJECT): if result.stderr: raise ValueError("single-mode interaction wrote unexpected stderr") verify_report(json.loads(result.stdout), content, mode) + tiny = output / "too-small.bin" + tiny.write_bytes(b"actual-invalid-input") + rejected = run("cache-too-small", [cache_binary, "--fixture", tiny], expected_exit=1) + if rejected.stdout or rejected.stderr != "agent-cast-cache-demo: FixtureTooSmall; see --help\n": + raise ValueError("invalid cache input did not produce the expected ordinary error") receipt["ok"] = True except (OSError, subprocess.TimeoutExpired, RuntimeError, ValueError, KeyError) as error: receipt["error"] = str(error) From 813dc435c86e4d099fb3b4b87703f1bbd967c6ee Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Tue, 6 Oct 2026 22:53:14 +0200 Subject: [PATCH 3/3] Bind host authority and harden qualification oracles --- .github/workflows/agent-cast.yml | 3 +- docs/agent-cast/events.jsonl | 1 + .../format.stderr | 0 .../format.stdout | 1 + .../qualification.json | 56 ++ .../zig-version.stderr | 0 .../zig-version.stdout | 1 + .../agent-cast/evidence/authority-review.json | 15 + .../known-weak-control.stderr | 37 + .../oracle-controls-2026-10-06/receipt.json | 27 + .../repaired-oracle.stderr | 13 + .../evidence/registry-2026-10-06/build.stderr | 0 .../evidence/registry-2026-10-06/build.stdout | 0 .../registry-2026-10-06/cache-0.stderr | 0 .../registry-2026-10-06/cache-0.stdout | 220 ++++++ .../registry-2026-10-06/cache-1.stderr | 0 .../registry-2026-10-06/cache-1.stdout | 220 ++++++ .../registry-2026-10-06/cache-help.stderr | 0 .../registry-2026-10-06/cache-help.stdout | 10 + .../cache-too-small.stderr | 1 + .../cache-too-small.stdout | 0 .../registry-2026-10-06/compare-0.stderr | 0 .../registry-2026-10-06/compare-0.stdout | 267 +++++++ .../registry-2026-10-06/compare-1.stderr | 0 .../registry-2026-10-06/compare-1.stdout | 267 +++++++ .../registry-2026-10-06/disabled.stderr | 0 .../registry-2026-10-06/disabled.stdout | 139 ++++ .../registry-2026-10-06/enabled.stderr | 0 .../registry-2026-10-06/enabled.stdout | 139 ++++ .../registry-2026-10-06/fixture-0.bin | 1 + .../registry-2026-10-06/fixture-1.bin | 1 + .../registry-2026-10-06/format.stderr | 0 .../registry-2026-10-06/format.stdout | 0 .../evidence/registry-2026-10-06/help.stderr | 0 .../evidence/registry-2026-10-06/help.stdout | 10 + .../registry-2026-10-06/qualification.json | 235 ++++++ .../registry-2026-10-06/too-small.bin | 1 + .../evidence/registry-2026-10-06/unit.stderr | 5 + .../evidence/registry-2026-10-06/unit.stdout | 0 .../registry-2026-10-06/zig-version.stderr | 0 .../registry-2026-10-06/zig-version.stdout | 1 + docs/agent-cast/milestones.md | 9 +- docs/agent-cast/tasks.json | 61 +- experiments/agent_cast/src/authority.zig | 727 ++++++++++++++++++ experiments/agent_cast/src/tests.zig | 1 + scripts/qualify_agent_cast.py | 17 +- .../tests/test_agent_cast_qualification.py | 136 ++++ 47 files changed, 2593 insertions(+), 29 deletions(-) create mode 100644 docs/agent-cast/evidence/authority-format-failure-2026-10-06/format.stderr create mode 100644 docs/agent-cast/evidence/authority-format-failure-2026-10-06/format.stdout create mode 100644 docs/agent-cast/evidence/authority-format-failure-2026-10-06/qualification.json create mode 100644 docs/agent-cast/evidence/authority-format-failure-2026-10-06/zig-version.stderr create mode 100644 docs/agent-cast/evidence/authority-format-failure-2026-10-06/zig-version.stdout create mode 100644 docs/agent-cast/evidence/authority-review.json create mode 100644 docs/agent-cast/evidence/oracle-controls-2026-10-06/known-weak-control.stderr create mode 100644 docs/agent-cast/evidence/oracle-controls-2026-10-06/receipt.json create mode 100644 docs/agent-cast/evidence/oracle-controls-2026-10-06/repaired-oracle.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/build.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/build.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/cache-0.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/cache-0.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/cache-1.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/cache-1.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/cache-help.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/cache-help.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/cache-too-small.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/cache-too-small.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/compare-0.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/compare-0.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/compare-1.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/compare-1.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/disabled.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/disabled.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/enabled.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/enabled.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/fixture-0.bin create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/fixture-1.bin create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/format.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/format.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/help.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/help.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/qualification.json create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/too-small.bin create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/unit.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/unit.stdout create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/zig-version.stderr create mode 100644 docs/agent-cast/evidence/registry-2026-10-06/zig-version.stdout create mode 100644 experiments/agent_cast/src/authority.zig create mode 100644 scripts/tests/test_agent_cast_qualification.py diff --git a/.github/workflows/agent-cast.yml b/.github/workflows/agent-cast.yml index 99f9d599b1..e1c3e11546 100644 --- a/.github/workflows/agent-cast.yml +++ b/.github/workflows/agent-cast.yml @@ -7,7 +7,7 @@ on: - 'docs/agent-cast/**' - 'scripts/agent_cast_ledger.py' - 'scripts/qualify_agent_cast.py' - - 'scripts/tests/test_agent_cast_ledger.py' + - 'scripts/tests/test_agent_cast_*.py' - '.github/workflows/agent-cast.yml' - '.github/actions/setup-zig/**' workflow_dispatch: @@ -40,6 +40,7 @@ jobs: run: | python3 scripts/agent_cast_ledger.py validate --json python3 -m unittest scripts.tests.test_agent_cast_ledger -v + python3 -m unittest scripts.tests.test_agent_cast_qualification -v - name: Build and drive the prototype run: python3 scripts/qualify_agent_cast.py --output "$RUNNER_TEMP/agent-cast-receipts" - name: Retain qualification evidence diff --git a/docs/agent-cast/events.jsonl b/docs/agent-cast/events.jsonl index 20306cdb0f..72e624d8ca 100644 --- a/docs/agent-cast/events.jsonl +++ b/docs/agent-cast/events.jsonl @@ -8,3 +8,4 @@ {"sequence": 8, "observed_at_utc": "2026-10-06T20:21:58.277937+00:00", "event": "m2_completed_cache_verified", "details": {"evidence": "docs/agent-cast/evidence/m2.json", "unit_tests": 52, "qualified_actual_fixtures": 2, "cache_receipts_per_fixture": 11}} {"sequence": 9, "observed_at_utc": "2026-10-06T20:21:58.277937+00:00", "event": "red_team_findings_repaired", "details": {"evidence": "docs/agent-cast/evidence/m2-review.json", "actual_broker_validation": "pending"}} {"sequence": 10, "observed_at_utc": "2026-10-06T20:21:58.277937+00:00", "event": "fork_publication_route_created", "details": {"evidence": "docs/agent-cast/evidence/publication-route.json", "ci_result": "not_run"}} +{"sequence": 11, "observed_at_utc": "2026-10-06T20:53:14.195117+00:00", "event": "registry_and_oracles_verified", "details": {"runtime_receipt": "docs/agent-cast/evidence/registry-2026-10-06/qualification.json", "unit_tests": 62, "oracle_control_receipt": "docs/agent-cast/evidence/oracle-controls-2026-10-06/receipt.json", "actual_broker_and_clients": "in_progress", "full_goal": "active_through_M8"}} diff --git a/docs/agent-cast/evidence/authority-format-failure-2026-10-06/format.stderr b/docs/agent-cast/evidence/authority-format-failure-2026-10-06/format.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/authority-format-failure-2026-10-06/format.stdout b/docs/agent-cast/evidence/authority-format-failure-2026-10-06/format.stdout new file mode 100644 index 0000000000..683b727a89 --- /dev/null +++ b/docs/agent-cast/evidence/authority-format-failure-2026-10-06/format.stdout @@ -0,0 +1 @@ +src/authority.zig diff --git a/docs/agent-cast/evidence/authority-format-failure-2026-10-06/qualification.json b/docs/agent-cast/evidence/authority-format-failure-2026-10-06/qualification.json new file mode 100644 index 0000000000..f79dcfa7a6 --- /dev/null +++ b/docs/agent-cast/evidence/authority-format-failure-2026-10-06/qualification.json @@ -0,0 +1,56 @@ +{ + "schema_version": 1, + "recorded_at_utc": "2026-10-06T20:26:21.574962+00:00", + "scope": "local foundation/cache correctness and subscriber/framing unit checks; logical agents in one process; no broker or isolation qualification", + "platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O", + "machine": "arm64", + "source_sha256": { + "experiments/agent_cast/build.zig": "a8720adf37fc415ddf4f647d68f642f007ced0a996fbef66b8b730d1bea5e0d6", + "experiments/agent_cast/src/authority.zig": "8b43a63572e6571e30e556ffc721f59ed175e9277420ff8e409f1bd8984afaba", + "experiments/agent_cast/src/cache.zig": "7b26432c7b196c97589048aa2a05e26b0f7f91526ed23ce2f3714148ced207d0", + "experiments/agent_cast/src/cache_demo.zig": "f75346013a621eab61afcc8a34a29860ed95ab510f787c298c5fa9c3a771ea33", + "experiments/agent_cast/src/cached_executor.zig": "60d3f98158ab47f469f8483a9e5ff20ddc2a9a5f791c09d5441d9fff96b45992", + "experiments/agent_cast/src/contracts.zig": "d11df3241d4d9c5441da4b85250d192fb0e890ff7bdd613368c917a71ca2470e", + "experiments/agent_cast/src/demo.zig": "1b085e57e0663722c0a15a7af8b65f14a2a3d5b377ccaf50e11589e6276fb032", + "experiments/agent_cast/src/executor.zig": "5453b7d0505222bb34e6a3bd83522695cd57beb714858949341646ecde51380b", + "experiments/agent_cast/src/optimizer.zig": "e33266af767f303d2c3918a27d514afd1e62913aad495fdfacf6b42dcfa55a63", + "experiments/agent_cast/src/subscribers.zig": "034a2ecd3e181b61794987a3128ba93a5b5cb2c0bf47f9164ecd9a5899ed302f", + "experiments/agent_cast/src/tests.zig": "c03790db06fd05069fbd65c8e7d30f3867eba7644a36ab1edd290c6ef9551d09", + "experiments/agent_cast/src/wire.zig": "2696b554acdbebc2efde48b20085abaf528e92710c302bf823995ed71cfb9547" + }, + "checks": [ + { + "name": "zig-version", + "argv": [ + "", + "version" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.03716295794583857, + "stdout": "zig-version.stdout", + "stderr": "zig-version.stderr" + }, + { + "name": "format", + "argv": [ + "", + "fmt", + "--check", + "src/", + "build.zig" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 1, + "seconds": 0.03903308417648077, + "stdout": "format.stdout", + "stderr": "format.stderr" + } + ], + "ci_status": "not_run", + "performance_claim": null, + "ok": false, + "zig_version": "0.16.0", + "error": "format failed: ", + "path_normalization": "Machine-specific path prefixes replaced; failed gate retained, not counted as verification." +} diff --git a/docs/agent-cast/evidence/authority-format-failure-2026-10-06/zig-version.stderr b/docs/agent-cast/evidence/authority-format-failure-2026-10-06/zig-version.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/authority-format-failure-2026-10-06/zig-version.stdout b/docs/agent-cast/evidence/authority-format-failure-2026-10-06/zig-version.stdout new file mode 100644 index 0000000000..04a373efe6 --- /dev/null +++ b/docs/agent-cast/evidence/authority-format-failure-2026-10-06/zig-version.stdout @@ -0,0 +1 @@ +0.16.0 diff --git a/docs/agent-cast/evidence/authority-review.json b/docs/agent-cast/evidence/authority-review.json new file mode 100644 index 0000000000..a2acef3658 --- /dev/null +++ b/docs/agent-cast/evidence/authority-review.json @@ -0,0 +1,15 @@ +{ + "observed_at_utc": "2026-10-06T20:53:14.195117+00:00", + "source": "Independent Astra static review followed by parent62-test qualification with patched authority retry case.", + "requested_model": "gpt-6-astra", + "finding": "Exact duplicate issuance using the original deadline failed after expiry before retained-ID lookup.", + "resolution": "Future-deadline validation now applies only to new issuance. Exact expired retry returns retained permit and unchanged deadline; changed binding and reclaimed ID reject.", + "evidence": "registry-2026-10-06/qualification.json", + "integration_requirements": [ + "Actual control-FD exclusivity and authenticated agent role must be runtime-tested.", + "Cache namespace must bind opaque task lifetime plus resource/implementation generations so late old producers cannot repopulate a new scope.", + "Prepared borrowed bindings must be copied before asynchronous execution.", + "Transport delivery and client acknowledgment are different facts; reconnect may replay output without repeating the backing effect." + ], + "broker_runtime_qualified": false +} diff --git a/docs/agent-cast/evidence/oracle-controls-2026-10-06/known-weak-control.stderr b/docs/agent-cast/evidence/oracle-controls-2026-10-06/known-weak-control.stderr new file mode 100644 index 0000000000..30b2aeb8d0 --- /dev/null +++ b/docs/agent-cast/evidence/oracle-controls-2026-10-06/known-weak-control.stderr @@ -0,0 +1,37 @@ +test_cache_rejects_unauthorized_result_and_invented_counters (oracle_tests.AgentCastQualificationTests.test_cache_rejects_unauthorized_result_and_invented_counters) ... ok +test_cache_rejects_unbounded_accounting (oracle_tests.AgentCastQualificationTests.test_cache_rejects_unbounded_accounting) ... FAIL +test_cache_rejects_wrong_input_and_changed_principal_or_task (oracle_tests.AgentCastQualificationTests.test_cache_rejects_wrong_input_and_changed_principal_or_task) ... ok +test_compare_rejects_changed_logical_identity (oracle_tests.AgentCastQualificationTests.test_compare_rejects_changed_logical_identity) ... ok +test_compare_rejects_consistent_but_wrong_authority_and_identity (oracle_tests.AgentCastQualificationTests.test_compare_rejects_consistent_but_wrong_authority_and_identity) ... FAIL +test_compare_rejects_skipped_receipt_and_empty_or_pinned_outputs (oracle_tests.AgentCastQualificationTests.test_compare_rejects_skipped_receipt_and_empty_or_pinned_outputs) ... ok +test_compare_rejects_wrong_fixture_digest_length_and_logical_mismatch (oracle_tests.AgentCastQualificationTests.test_compare_rejects_wrong_fixture_digest_length_and_logical_mismatch) ... ok +test_receipt_backed_positive_controls (oracle_tests.AgentCastQualificationTests.test_receipt_backed_positive_controls) ... ok + +====================================================================== +FAIL: test_cache_rejects_unbounded_accounting (oracle_tests.AgentCastQualificationTests.test_cache_rejects_unbounded_accounting) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/alvarogpt/Code/fx/scripts/tests/test_agent_cast_qualification.py", line 132, in test_cache_rejects_unbounded_accounting + self.assert_cache_rejected(unbounded) + ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^ + File "/Users/alvarogpt/Code/fx/scripts/tests/test_agent_cast_qualification.py", line 44, in assert_cache_rejected + with self.assertRaises((ValueError, KeyError, TypeError)): + ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +AssertionError: (, , ) not raised + +====================================================================== +FAIL: test_compare_rejects_consistent_but_wrong_authority_and_identity (oracle_tests.AgentCastQualificationTests.test_compare_rejects_consistent_but_wrong_authority_and_identity) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/alvarogpt/Code/fx/scripts/tests/test_agent_cast_qualification.py", line 87, in test_compare_rejects_consistent_but_wrong_authority_and_identity + self.assert_compare_rejected(changed) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^ + File "/Users/alvarogpt/Code/fx/scripts/tests/test_agent_cast_qualification.py", line 40, in assert_compare_rejected + with self.assertRaises((ValueError, KeyError, TypeError)): + ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +AssertionError: (, , ) not raised + +---------------------------------------------------------------------- +Ran 8 tests in 0.002s + +FAILED (failures=2) diff --git a/docs/agent-cast/evidence/oracle-controls-2026-10-06/receipt.json b/docs/agent-cast/evidence/oracle-controls-2026-10-06/receipt.json new file mode 100644 index 0000000000..b3cb762edf --- /dev/null +++ b/docs/agent-cast/evidence/oracle-controls-2026-10-06/receipt.json @@ -0,0 +1,27 @@ +{ + "recorded_at_utc": "2026-10-06T20:36:46.168279+00:00", + "scope": "Qualification helper oracles only. Deliberately weak prior helper must fail two negative controls; repaired helper must reject all mutated reports. Not a broken native-binary/broker or full M8 gate.", + "source_sha256": { + "scripts/qualify_agent_cast.py": "3b1eab84faa80bcd3ad5b145d82c5261981de6cab3c3b7b29e24259be3c49b2c", + "scripts/tests/test_agent_cast_qualification.py": "b39091d2d998421b9eaeadb8f5f89d098d2b2e553d788cf2c06dc66c5b56d3f9" + }, + "weak_control_source_commit": "03cc8c91924138341dc2789c6581c0aa3a3bfed6", + "weak_control_source_sha256": "157002057aaaec3ec370460bc8daa6ef2672f71426ef7c86ce3dc6f980db471f", + "checks": [ + { + "name": "known-weak-control", + "tests_run": 8, + "failures": 2, + "errors": 0, + "log": "known-weak-control.stderr" + }, + { + "name": "repaired-oracle", + "tests_run": 8, + "failures": 0, + "errors": 0, + "log": "repaired-oracle.stderr" + } + ], + "ok": true +} diff --git a/docs/agent-cast/evidence/oracle-controls-2026-10-06/repaired-oracle.stderr b/docs/agent-cast/evidence/oracle-controls-2026-10-06/repaired-oracle.stderr new file mode 100644 index 0000000000..a255f7f808 --- /dev/null +++ b/docs/agent-cast/evidence/oracle-controls-2026-10-06/repaired-oracle.stderr @@ -0,0 +1,13 @@ +test_cache_rejects_unauthorized_result_and_invented_counters (oracle_tests.AgentCastQualificationTests.test_cache_rejects_unauthorized_result_and_invented_counters) ... ok +test_cache_rejects_unbounded_accounting (oracle_tests.AgentCastQualificationTests.test_cache_rejects_unbounded_accounting) ... ok +test_cache_rejects_wrong_input_and_changed_principal_or_task (oracle_tests.AgentCastQualificationTests.test_cache_rejects_wrong_input_and_changed_principal_or_task) ... ok +test_compare_rejects_changed_logical_identity (oracle_tests.AgentCastQualificationTests.test_compare_rejects_changed_logical_identity) ... ok +test_compare_rejects_consistent_but_wrong_authority_and_identity (oracle_tests.AgentCastQualificationTests.test_compare_rejects_consistent_but_wrong_authority_and_identity) ... ok +test_compare_rejects_skipped_receipt_and_empty_or_pinned_outputs (oracle_tests.AgentCastQualificationTests.test_compare_rejects_skipped_receipt_and_empty_or_pinned_outputs) ... ok +test_compare_rejects_wrong_fixture_digest_length_and_logical_mismatch (oracle_tests.AgentCastQualificationTests.test_compare_rejects_wrong_fixture_digest_length_and_logical_mismatch) ... ok +test_receipt_backed_positive_controls (oracle_tests.AgentCastQualificationTests.test_receipt_backed_positive_controls) ... ok + +---------------------------------------------------------------------- +Ran 8 tests in 0.001s + +OK diff --git a/docs/agent-cast/evidence/registry-2026-10-06/build.stderr b/docs/agent-cast/evidence/registry-2026-10-06/build.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/build.stdout b/docs/agent-cast/evidence/registry-2026-10-06/build.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/cache-0.stderr b/docs/agent-cast/evidence/registry-2026-10-06/cache-0.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/cache-0.stdout b/docs/agent-cast/evidence/registry-2026-10-06/cache-0.stdout new file mode 100644 index 0000000000..cdc813f0f7 --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/cache-0.stdout @@ -0,0 +1,220 @@ +{ + "experimental_scope": "M2 synchronous owned completed-value cache; exact fresh fixture admissions; no production registry or performance claim", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "fixture_verified": true, + "logical_call_count": 11, + "limits": { + "max_entries": 8, + "max_bytes": 32768 + }, + "batch_limits": { + "max_result_bytes": 4096 + }, + "cache": { + "entries": 4, + "retained_bytes": 3388, + "metadata_bytes": 2112, + "hits": 4, + "misses": 4, + "backing_reads": 4, + "evictions": 0, + "not_retained": 0, + "rejected": 3 + }, + "retained_accounting": "allocated entry table plus owned key strings and outputs; allocator bookkeeping and caller-owned output copies excluded", + "batches": [ + { + "name": "cold-agent-a", + "logical_calls": 4, + "cache_hits": 0, + "cache_misses": 4, + "backing_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 4, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + } + ] + }, + { + "name": "completed-values-agent-b", + "logical_calls": 4, + "cache_hits": 4, + "cache_misses": 0, + "backing_reads": 0, + "receipts": [ + { + "logical_id": 5, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 7, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + } + ] + }, + { + "name": "rejected-consumers", + "logical_calls": 3, + "cache_hits": 0, + "cache_misses": 0, + "backing_reads": 0, + "receipts": [ + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 11, + "agent_id": "mismatched-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "binding_mismatch", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/registry-2026-10-06/cache-1.stderr b/docs/agent-cast/evidence/registry-2026-10-06/cache-1.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/cache-1.stdout b/docs/agent-cast/evidence/registry-2026-10-06/cache-1.stdout new file mode 100644 index 0000000000..316e026b5c --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/cache-1.stdout @@ -0,0 +1,220 @@ +{ + "experimental_scope": "M2 synchronous owned completed-value cache; exact fresh fixture admissions; no production registry or performance claim", + "immutable_input_sha256": "3d55b51a98d198e5b5bf86d066c92f53c1349d4636253f2da34c573976850cf8", + "immutable_input_bytes": 1024, + "fixture_verified": true, + "logical_call_count": 11, + "limits": { + "max_entries": 8, + "max_bytes": 32768 + }, + "batch_limits": { + "max_result_bytes": 4096 + }, + "cache": { + "entries": 4, + "retained_bytes": 3388, + "metadata_bytes": 2112, + "hits": 4, + "misses": 4, + "backing_reads": 4, + "evictions": 0, + "not_retained": 0, + "rejected": 3 + }, + "retained_accounting": "allocated entry table plus owned key strings and outputs; allocator bookkeeping and caller-owned output copies excluded", + "batches": [ + { + "name": "cold-agent-a", + "logical_calls": 4, + "cache_hits": 0, + "cache_misses": 4, + "backing_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 2, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 4, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "backing", + "cache_hit": false, + "cache_miss": true, + "backing_reads": 1, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + } + ] + }, + { + "name": "completed-values-agent-b", + "logical_calls": 4, + "cache_hits": 4, + "cache_misses": 0, + "backing_reads": 0, + "receipts": [ + { + "logical_id": 5, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "admitted", + "status": "success", + "source": "cache", + "cache_hit": true, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + } + ] + }, + { + "name": "rejected-consumers", + "logical_calls": 3, + "cache_hits": 0, + "cache_misses": 0, + "backing_reads": 0, + "receipts": [ + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 11, + "agent_id": "mismatched-agent", + "principal_domain": "fixture-operator", + "task_id": "cache-fixture-task", + "admission_status": "binding_mismatch", + "status": "admission_rejected", + "source": "rejected", + "cache_hit": false, + "cache_miss": false, + "backing_reads": 0, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/registry-2026-10-06/cache-help.stderr b/docs/agent-cast/evidence/registry-2026-10-06/cache-help.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/cache-help.stdout b/docs/agent-cast/evidence/registry-2026-10-06/cache-help.stdout new file mode 100644 index 0000000000..a72c50ba4f --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/cache-help.stdout @@ -0,0 +1,10 @@ +Usage: agent-cast-cache-demo --fixture PATH + +Capture a real file of 256 bytes to 1 MiB. Run two independent admitted +four-window batches, then a batch of denied, stale, and mismatched calls. +JSON includes complete output bytes and actual hit, miss, and backing counts. +Hits still validate fixture admission, hash the capture, and copy output. +This synchronous experiment does not implement production registry validation. + + --fixture PATH Actual file to capture once + --help Show this help diff --git a/docs/agent-cast/evidence/registry-2026-10-06/cache-too-small.stderr b/docs/agent-cast/evidence/registry-2026-10-06/cache-too-small.stderr new file mode 100644 index 0000000000..3dd2a97737 --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/cache-too-small.stderr @@ -0,0 +1 @@ +agent-cast-cache-demo: FixtureTooSmall; see --help diff --git a/docs/agent-cast/evidence/registry-2026-10-06/cache-too-small.stdout b/docs/agent-cast/evidence/registry-2026-10-06/cache-too-small.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/compare-0.stderr b/docs/agent-cast/evidence/registry-2026-10-06/compare-0.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/compare-0.stdout b/docs/agent-cast/evidence/registry-2026-10-06/compare-0.stdout new file mode 100644 index 0000000000..1c374cb305 --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/compare-0.stdout @@ -0,0 +1,267 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "compare", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": true, + "runs": [ + { + "mode": "disabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 8, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 4, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 5, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 6, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 7, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + }, + { + "mode": "enabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/registry-2026-10-06/compare-1.stderr b/docs/agent-cast/evidence/registry-2026-10-06/compare-1.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/compare-1.stdout b/docs/agent-cast/evidence/registry-2026-10-06/compare-1.stdout new file mode 100644 index 0000000000..91330d8055 --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/compare-1.stdout @@ -0,0 +1,267 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "compare", + "immutable_input_sha256": "3d55b51a98d198e5b5bf86d066c92f53c1349d4636253f2da34c573976850cf8", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": true, + "runs": [ + { + "mode": "disabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 8, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 4, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 5, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 6, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 7, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + }, + { + "mode": "enabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "78ad0b32461ba8571cf48a68ea13270a107c2870582c9ad66785e85b496b9c65", + "output_hex": "666978747572652d312d77696e646f772d3030424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "e326211393fafc090cd9c5a9f2b80bfd1ae6d25aa4b8e2da00a3722b22985ddf", + "output_hex": "666978747572652d312d77696e646f772d3031434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "1db91568e4de19f31a1617a7444db895e788d140bc9ffff1bfcecda1c0393aa1", + "output_hex": "666978747572652d312d77696e646f772d3032444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "6f5a12f8e0fb4b34655f48660dfe6fe7681b8c041ceab9e41167718968848468", + "output_hex": "666978747572652d312d77696e646f772d3033454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/registry-2026-10-06/disabled.stderr b/docs/agent-cast/evidence/registry-2026-10-06/disabled.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/disabled.stdout b/docs/agent-cast/evidence/registry-2026-10-06/disabled.stdout new file mode 100644 index 0000000000..deee42bd2d --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/disabled.stdout @@ -0,0 +1,139 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "disabled", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": null, + "runs": [ + { + "mode": "disabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 8, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 4, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 5, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 6, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 7, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/registry-2026-10-06/enabled.stderr b/docs/agent-cast/evidence/registry-2026-10-06/enabled.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/enabled.stdout b/docs/agent-cast/evidence/registry-2026-10-06/enabled.stdout new file mode 100644 index 0000000000..541d8a14fa --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/enabled.stdout @@ -0,0 +1,139 @@ +{ + "experimental_scope": "certified embedded immutable content-reader sharing; fixture host admissions; no production permission integration or performance claim", + "mode": "enabled", + "immutable_input_sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "immutable_input_bytes": 1024, + "logical_call_count": 10, + "fixture_contract_verified": true, + "parity": null, + "runs": [ + { + "mode": "enabled", + "logical_call_count": 10, + "receipt_count": 10, + "physical_reads": 4, + "receipts": [ + { + "logical_id": 1, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 2, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 0, + "output_bytes": 64, + "output_sha256": "0fb69e1a6a6e12658bdcf8ed8d19ce52375eb1c736a5ed4bfc9fd2eca6c19e02", + "output_hex": "666978747572652d302d77696e646f772d3030414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141" + }, + { + "logical_id": 3, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 4, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 1, + "output_bytes": 64, + "output_sha256": "7dac214a98f01ffc3302613c8fd5f6c8ca1391bd35021d87a3a0da56747b887b", + "output_hex": "666978747572652d302d77696e646f772d3031424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242" + }, + { + "logical_id": 5, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 6, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 2, + "output_bytes": 64, + "output_sha256": "e55b9510bc6f11fe4e0dfd80c1f31b4fecfcdb0df8da9f92abc6204b35e73f28", + "output_hex": "666978747572652d302d77696e646f772d3032434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343" + }, + { + "logical_id": 7, + "agent_id": "agent-a", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 8, + "agent_id": "agent-b", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "admitted", + "status": "success", + "physical_group": 3, + "output_bytes": 64, + "output_sha256": "c05b6652972fd8071b8f38ffa0040ffec1144a6f13653fc492171aaf216229db", + "output_hex": "666978747572652d302d77696e646f772d3033444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + }, + { + "logical_id": 9, + "agent_id": "denied-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "denied", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + }, + { + "logical_id": 10, + "agent_id": "stale-agent", + "principal_domain": "fixture-operator", + "task_id": "fixture-task", + "admission_status": "stale_authority", + "status": "admission_rejected", + "physical_group": null, + "output_bytes": 0, + "output_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_hex": "" + } + ] + } + ] +} diff --git a/docs/agent-cast/evidence/registry-2026-10-06/fixture-0.bin b/docs/agent-cast/evidence/registry-2026-10-06/fixture-0.bin new file mode 100644 index 0000000000..092edfcdaa --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/fixture-0.bin @@ -0,0 +1 @@ +fixture-0-window-00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfixture-0-window-01BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBfixture-0-window-02CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCfixture-0-window-03DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDfixture-0-window-04EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEfixture-0-window-05FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfixture-0-window-06GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGfixture-0-window-07HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHfixture-0-window-08IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIfixture-0-window-09JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJfixture-0-window-10KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKfixture-0-window-11LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLfixture-0-window-12MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMfixture-0-window-13NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNfixture-0-window-14OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOfixture-0-window-15PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP \ No newline at end of file diff --git a/docs/agent-cast/evidence/registry-2026-10-06/fixture-1.bin b/docs/agent-cast/evidence/registry-2026-10-06/fixture-1.bin new file mode 100644 index 0000000000..7023653028 --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/fixture-1.bin @@ -0,0 +1 @@ +fixture-1-window-00BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBfixture-1-window-01CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCfixture-1-window-02DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDfixture-1-window-03EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEfixture-1-window-04FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfixture-1-window-05GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGfixture-1-window-06HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHfixture-1-window-07IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIfixture-1-window-08JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJfixture-1-window-09KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKfixture-1-window-10LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLfixture-1-window-11MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMfixture-1-window-12NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNfixture-1-window-13OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOfixture-1-window-14PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPfixture-1-window-15QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ \ No newline at end of file diff --git a/docs/agent-cast/evidence/registry-2026-10-06/format.stderr b/docs/agent-cast/evidence/registry-2026-10-06/format.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/format.stdout b/docs/agent-cast/evidence/registry-2026-10-06/format.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/help.stderr b/docs/agent-cast/evidence/registry-2026-10-06/help.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/help.stdout b/docs/agent-cast/evidence/registry-2026-10-06/help.stdout new file mode 100644 index 0000000000..c4d8831f20 --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/help.stdout @@ -0,0 +1,10 @@ +Usage: agent-cast-demo --fixture PATH [--mode compare|enabled|disabled] + +Capture one file of 256 bytes to 1 MiB and exercise an experimental embedded +immutable content reader. Compare mode checks every logical receipt and prints +JSON with actual physical read counts. This is a correctness demonstration. +Host admissions are fixture contracts, with no production permission checks. + + --fixture PATH File to capture once into owned immutable storage + --mode MODE Select execution mode (default: compare) + --help Show this help diff --git a/docs/agent-cast/evidence/registry-2026-10-06/qualification.json b/docs/agent-cast/evidence/registry-2026-10-06/qualification.json new file mode 100644 index 0000000000..88508605fe --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/qualification.json @@ -0,0 +1,235 @@ +{ + "schema_version": 1, + "recorded_at_utc": "2026-10-06T20:42:19.725789+00:00", + "scope": "Current embedded foundation/cache binaries, subscriber/framing and authority-registry unit tests. Broker/client runtime is still in progress.", + "platform": "macOS-26.5.2-arm64-arm-64bit-Mach-O", + "machine": "arm64", + "source_sha256": { + "experiments/agent_cast/build.zig": "a8720adf37fc415ddf4f647d68f642f007ced0a996fbef66b8b730d1bea5e0d6", + "experiments/agent_cast/src/authority.zig": "95311a6dc503ed86bfe3ea4cb2dfd3ebdcc3161832203d3f851027ac15d127ae", + "experiments/agent_cast/src/cache.zig": "7b26432c7b196c97589048aa2a05e26b0f7f91526ed23ce2f3714148ced207d0", + "experiments/agent_cast/src/cache_demo.zig": "f75346013a621eab61afcc8a34a29860ed95ab510f787c298c5fa9c3a771ea33", + "experiments/agent_cast/src/cached_executor.zig": "60d3f98158ab47f469f8483a9e5ff20ddc2a9a5f791c09d5441d9fff96b45992", + "experiments/agent_cast/src/contracts.zig": "d11df3241d4d9c5441da4b85250d192fb0e890ff7bdd613368c917a71ca2470e", + "experiments/agent_cast/src/demo.zig": "1b085e57e0663722c0a15a7af8b65f14a2a3d5b377ccaf50e11589e6276fb032", + "experiments/agent_cast/src/executor.zig": "5453b7d0505222bb34e6a3bd83522695cd57beb714858949341646ecde51380b", + "experiments/agent_cast/src/optimizer.zig": "e33266af767f303d2c3918a27d514afd1e62913aad495fdfacf6b42dcfa55a63", + "experiments/agent_cast/src/subscribers.zig": "034a2ecd3e181b61794987a3128ba93a5b5cb2c0bf47f9164ecd9a5899ed302f", + "experiments/agent_cast/src/tests.zig": "5faeed3dccf107f4857256767ffcd7f5dee7a819f0bb8f2b09afb762b2aa7060", + "experiments/agent_cast/src/wire.zig": "2696b554acdbebc2efde48b20085abaf528e92710c302bf823995ed71cfb9547" + }, + "checks": [ + { + "name": "zig-version", + "argv": [ + "", + "version" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.03589291707612574, + "stdout": "zig-version.stdout", + "stderr": "zig-version.stderr" + }, + { + "name": "format", + "argv": [ + "", + "fmt", + "--check", + "src/", + "build.zig" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.038131958805024624, + "stdout": "format.stdout", + "stderr": "format.stderr" + }, + { + "name": "build", + "argv": [ + "", + "build", + "-Doptimize=ReleaseSafe", + "-j1" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.11155183403752744, + "stdout": "build.stdout", + "stderr": "build.stderr" + }, + { + "name": "unit", + "argv": [ + "", + "build", + "test", + "-Doptimize=ReleaseSafe", + "-j1", + "--summary", + "all" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 7.712655792012811, + "stdout": "unit.stdout", + "stderr": "unit.stderr" + }, + { + "name": "help", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--help" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0030959590803831816, + "stdout": "help.stdout", + "stderr": "help.stderr" + }, + { + "name": "cache-help", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-cache-demo", + "--help" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.003202041843906045, + "stdout": "cache-help.stdout", + "stderr": "cache-help.stderr" + }, + { + "name": "compare-0", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-0.bin", + "--mode", + "compare" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.00301641714759171, + "stdout": "compare-0.stdout", + "stderr": "compare-0.stderr" + }, + { + "name": "cache-0", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-cache-demo", + "--fixture", + "/fixture-0.bin" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0027991668321192265, + "stdout": "cache-0.stdout", + "stderr": "cache-0.stderr" + }, + { + "name": "compare-1", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-1.bin", + "--mode", + "compare" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.0027846661396324635, + "stdout": "compare-1.stdout", + "stderr": "compare-1.stderr" + }, + { + "name": "cache-1", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-cache-demo", + "--fixture", + "/fixture-1.bin" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.002895750105381012, + "stdout": "cache-1.stdout", + "stderr": "cache-1.stderr" + }, + { + "name": "enabled", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-0.bin", + "--mode", + "enabled" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.002741500036790967, + "stdout": "enabled.stdout", + "stderr": "enabled.stderr" + }, + { + "name": "disabled", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-demo", + "--fixture", + "/fixture-0.bin", + "--mode", + "disabled" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 0, + "seconds": 0.002921333070844412, + "stdout": "disabled.stdout", + "stderr": "disabled.stderr" + }, + { + "name": "cache-too-small", + "argv": [ + "/experiments/agent_cast/zig-out/bin/agent-cast-cache-demo", + "--fixture", + "/too-small.bin" + ], + "cwd": "/experiments/agent_cast", + "exit_code": 1, + "seconds": 0.002551458077505231, + "stdout": "cache-too-small.stdout", + "stderr": "cache-too-small.stderr" + } + ], + "ci_status": "not_run", + "performance_claim": null, + "ok": true, + "zig_version": "0.16.0", + "unit_tests_passed": 62, + "binary_sha256": "30a7de8ea96cbe83b10a6d19364fab5f304be03eac286fa46e42c509f9f262e0", + "cache_binary_sha256": "98c7b732b1c88099f80829f994dd44fdc5988fb9085c0f156f1d0c1619bc4189", + "fixtures": [ + { + "sha256": "2745f274e3490392ffccab9ace5346efc80e5057787b8be2eeed28e1d4a56618", + "bytes": 1024, + "comparison": "compare-0.stdout", + "cache_comparison": "cache-0.stdout", + "logical_receipts_per_mode": 10, + "cache_logical_receipts": 11, + "disabled_backing_reads": 8, + "enabled_backing_reads": 4 + }, + { + "sha256": "3d55b51a98d198e5b5bf86d066c92f53c1349d4636253f2da34c573976850cf8", + "bytes": 1024, + "comparison": "compare-1.stdout", + "cache_comparison": "cache-1.stdout", + "logical_receipts_per_mode": 10, + "cache_logical_receipts": 11, + "disabled_backing_reads": 8, + "enabled_backing_reads": 4 + } + ], + "qualifier_sha256": "3b1eab84faa80bcd3ad5b145d82c5261981de6cab3c3b7b29e24259be3c49b2c", + "path_normalization": "Only machine-specific path prefixes replaced; hashes, exits, timings and raw logs unchanged." +} diff --git a/docs/agent-cast/evidence/registry-2026-10-06/too-small.bin b/docs/agent-cast/evidence/registry-2026-10-06/too-small.bin new file mode 100644 index 0000000000..ed0003c5be --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/too-small.bin @@ -0,0 +1 @@ +actual-invalid-input \ No newline at end of file diff --git a/docs/agent-cast/evidence/registry-2026-10-06/unit.stderr b/docs/agent-cast/evidence/registry-2026-10-06/unit.stderr new file mode 100644 index 0000000000..8f1df2f392 --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/unit.stderr @@ -0,0 +1,5 @@ +Build Summary: 3/3 steps succeeded; 62/62 tests passed +test success ++- run test 62 pass (62 total) 12ms MaxRSS:6M + +- compile test ReleaseSafe native success 7s MaxRSS:505M + diff --git a/docs/agent-cast/evidence/registry-2026-10-06/unit.stdout b/docs/agent-cast/evidence/registry-2026-10-06/unit.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/zig-version.stderr b/docs/agent-cast/evidence/registry-2026-10-06/zig-version.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/agent-cast/evidence/registry-2026-10-06/zig-version.stdout b/docs/agent-cast/evidence/registry-2026-10-06/zig-version.stdout new file mode 100644 index 0000000000..04a373efe6 --- /dev/null +++ b/docs/agent-cast/evidence/registry-2026-10-06/zig-version.stdout @@ -0,0 +1 @@ +0.16.0 diff --git a/docs/agent-cast/milestones.md b/docs/agent-cast/milestones.md index 038c8cc65d..df8bf5fcfe 100644 --- a/docs/agent-cast/milestones.md +++ b/docs/agent-cast/milestones.md @@ -115,13 +115,20 @@ CI result. M0, the bounded M1 slice, and M2 completed-value caching have local evidence in [evidence/m2.json](evidence/m2.json) and [evidence/ledger.json](evidence/ledger.json). The refreshed qualification observed -52 Zig tests and two different actual fixtures. Foundation comparisons retain +62 Zig tests and two different actual fixtures. Foundation comparisons retain ten logical receipts while using eight or four captured-content reader calls. Cache comparisons retain eleven receipts, including four new logical consumers served from completed values and three rejected consumers with no output. The subscriber and framing contracts have unit evidence; their actual broker runtime qualification remains pending. No measured speedup is claimed. +The authority registry owns exact bindings, derives opaque handles with HMAC, +and checks registry generations and deadlines before start. Reclaimed permits +remain charged tombstones for the task lifetime. Its ten tests are included in +the latest qualification. Eight helper-oracle tests reject mutated reports; the +earlier weak checker fails the two controls that exposed its identity and quota +gaps. These checks supplement later runtime red-team validation. + The available account has read access to `vercel-labs/fx`; an owned fork provides the publication route recorded in [evidence/publication-route.json](evidence/publication-route.json). Draft PR and diff --git a/docs/agent-cast/tasks.json b/docs/agent-cast/tasks.json index 27a5489d15..df110064c4 100644 --- a/docs/agent-cast/tasks.json +++ b/docs/agent-cast/tasks.json @@ -7,7 +7,7 @@ "design": "docs/agent-cast-architecture.html", "scope": "Foundation first; no production permissions, broker, isolation or performance claim yet." }, - "last_updated_utc": "2026-10-06T20:21:58.277937+00:00", + "last_updated_utc": "2026-10-06T20:53:14.195117+00:00", "routing_policy": { "routine_model": "gpt-6-luna", "implementation_model": "gpt-6.1-sol", @@ -16,7 +16,7 @@ "repair_policy": "At most two repair rounds per delegated scope before parent review", "spending_limit": null, "usage_policy": "Null means unavailable; actual token counts require a provider receipt. No inferred totals.", - "current_batch": "Completed cache locally verified; subscribers unit-tested; broker authority in progress; fork CI route available" + "current_batch": "M3 actual broker and Python/Node clients in parallel;62 tests and13 embedded CLI checks passed; draft CI checkpoint next" }, "milestones": [ { @@ -252,14 +252,15 @@ "evidence": [ "docs/agent-cast/evidence/contract-review.json", "docs/agent-cast/evidence/foundation.json", - "docs/agent-cast/evidence/m2-2026-10-06/qualification.json" + "docs/agent-cast/evidence/m2-2026-10-06/qualification.json", + "docs/agent-cast/evidence/registry-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" + "updated_at_utc": "2026-10-06T20:53:14.195117+00:00" }, { "id": "AC-0102", @@ -288,14 +289,15 @@ ], "evidence": [ "docs/agent-cast/evidence/foundation.json", - "docs/agent-cast/evidence/m2-2026-10-06/qualification.json" + "docs/agent-cast/evidence/m2-2026-10-06/qualification.json", + "docs/agent-cast/evidence/registry-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" + "updated_at_utc": "2026-10-06T20:53:14.195117+00:00" }, { "id": "AC-0103", @@ -323,14 +325,15 @@ "evidence": [ "docs/agent-cast/evidence/contract-review.json", "docs/agent-cast/evidence/foundation.json", - "docs/agent-cast/evidence/m2-2026-10-06/qualification.json" + "docs/agent-cast/evidence/m2-2026-10-06/qualification.json", + "docs/agent-cast/evidence/registry-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" + "updated_at_utc": "2026-10-06T20:53:14.195117+00:00" }, { "id": "AC-0104", @@ -360,14 +363,15 @@ "evidence": [ "docs/agent-cast/evidence/foundation.json", "docs/agent-cast/evidence/foundation-2026-10-06/qualification.json", - "docs/agent-cast/evidence/m2-2026-10-06/qualification.json" + "docs/agent-cast/evidence/m2-2026-10-06/qualification.json", + "docs/agent-cast/evidence/registry-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" + "updated_at_utc": "2026-10-06T20:53:14.195117+00:00" }, { "id": "AC-0105", @@ -431,14 +435,15 @@ ], "evidence": [ "docs/agent-cast/evidence/m2.json", - "docs/agent-cast/evidence/m2-review.json" + "docs/agent-cast/evidence/m2-review.json", + "docs/agent-cast/evidence/registry-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T20:21:58.277937+00:00" + "updated_at_utc": "2026-10-06T20:53:14.195117+00:00" }, { "id": "AC-0202", @@ -464,14 +469,15 @@ ], "evidence": [ "docs/agent-cast/evidence/m2-2026-10-06/qualification.json", - "docs/agent-cast/evidence/m2-review.json" + "docs/agent-cast/evidence/m2-review.json", + "docs/agent-cast/evidence/registry-2026-10-06/qualification.json" ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T20:21:58.277937+00:00", + "updated_at_utc": "2026-10-06T20:53:14.195117+00:00", "pending_reason": "Thirteen subscriber unit tests pass; actual shared producer/cancellation runtime interaction remains pending broker composition." }, { @@ -497,31 +503,36 @@ "Bounds, framing, epochs, roles, authority and reconnect semantics tested", "Two actual clients exercise shared state; same-UID process is not advertised as containment" ], - "evidence": [], + "evidence": [ + "docs/agent-cast/evidence/authority-review.json", + "docs/agent-cast/evidence/registry-2026-10-06/qualification.json" + ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, "updated_at_utc": "2026-10-06T20:21:58.277937+00:00", - "implementation_note": "Parent authored framed strict JSON-RPC parser; Sol is implementing host-owned authority registry. Broker executable not yet implemented." + "implementation_note": "Registry/framing contracts compile and pass tests; actual async broker implementation has begun with strict control FD, acknowledged output and generation-scoped cache." }, { "id": "AC-0302", "milestone": "M3", "title": "Add Python and Node adapters", - "status": "planned", + "status": "in_progress", "depends_on": [ "AC-0301" ], "owner": { "role": "implementation", "model": "gpt-6.1-sol", - "reasoning_effort": "medium" + "reasoning_effort": "medium", + "agent": "/root/agent_cast_clients" }, "scope": [ - "experimental Python/Node clients", - "adapter integration fixtures" + "experiments/agent_cast/clients/python/agent_cast.py", + "experiments/agent_cast/clients/node/agent_cast.mjs", + "client-only transport tests" ], "acceptance": [ "Identical operation contract and ordinary results across both languages", @@ -533,7 +544,8 @@ "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T19:38:41.491825+00:00" + "updated_at_utc": "2026-10-06T20:53:14.195117+00:00", + "pending_reason": "Clients are being implemented against locked broker DTOs; actual broker interoperability is pending." }, { "id": "AC-0401", @@ -712,13 +724,16 @@ "Independently compare required and extra effects; qualification rejects deliberate broken controls.", "Every material finding is repaired and affected checks are rerun; unresolved findings block promotion." ], - "evidence": [], + "evidence": [ + "docs/agent-cast/evidence/oracle-controls-2026-10-06/receipt.json" + ], "usage": { "input_tokens": null, "output_tokens": null, "source": null }, - "updated_at_utc": "2026-10-06T20:02:55.247521+00:00" + "updated_at_utc": "2026-10-06T20:02:55.247521+00:00", + "implementation_note": "Early helper-oracle negative controls pass; full runtime red-team matrix remains planned." } ], "goal": { diff --git a/experiments/agent_cast/src/authority.zig b/experiments/agent_cast/src/authority.zig new file mode 100644 index 0000000000..efa16fa0e5 --- /dev/null +++ b/experiments/agent_cast/src/authority.zig @@ -0,0 +1,727 @@ +const std = @import("std"); +const contracts = @import("contracts.zig"); + +const hard_records = 1024; +const hard_owned_bytes = 16 * 1024 * 1024; +const hard_key_bytes = 64 * 1024; + +pub const Limits = struct { + max_records: usize = 256, + max_owned_bytes: usize = 1024 * 1024, + max_key_bytes: usize = 4096, +}; + +pub const ContextHandle = struct { bytes: [32]u8 }; +pub const AgentHandle = struct { bytes: [32]u8 }; +pub const Permit = struct { bytes: [32]u8 }; + +pub const TaskDefinition = struct { + task_id: []const u8, + principal_domain: []const u8, + snapshot: contracts.SnapshotIdentity, + authority_partition: []const u8, + policy_generation: u64 = 1, + resource_generation: u64 = 1, + implementation_generation: u64 = 1, +}; + +pub const Generations = struct { + policy: u64, + resource: u64, + implementation: u64, +}; + +pub const PermitState = enum { active, revoked, expired, stale_generations, reclaimed }; +pub const PermitStatus = struct { + state: PermitState, + logical_id: u64, + deadline_ns: u64, + generations: Generations, +}; + +pub const Stats = struct { records: usize, owned_bytes: usize, metadata_bytes: usize }; + +const AgentRecord = struct { + context: [32]u8, + agent_id: []const u8, + revoked: bool = false, +}; + +const ActionRecord = struct { + context: [32]u8, + agent: [32]u8, + request: contracts.CallBinding, + generations: Generations, + issued_ns: u64, + deadline_ns: u64, + revoked: bool = false, + reclaimed: bool = false, +}; + +const Kind = enum { task, agent, action }; +const Record = union(Kind) { task: TaskDefinition, agent: AgentRecord, action: ActionRecord }; +const Slot = struct { + generation: u64 = 1, + token: [32]u8 = @splat(0), + storage: []u8 = &.{}, + record: ?Record = null, +}; + +// Single-owner registry. The parent generates a fresh 32-byte CSPRNG secret and +// fresh nonzero runtime epoch for each runtime, keeps the secret host-only, and +// exposes host_* methods exclusively through its inherited trusted control FD. +// No caller-provided current policy or implementation state is accepted by the +// agent methods. now_ns always comes from the parent's trusted monotonic clock. +// Methods use the same explicit allocator. The nominal owned-byte limit charges +// the full allocated slot table and all record strings; allocator bookkeeping +// and this caller-owned Registry value are outside that limit. +pub const Registry = struct { + limits: Limits, + secret: [32]u8, + runtime_epoch: u64, + issuance_counter: u64 = 0, + slots: []Slot, + state: Stats, + + pub fn init(alloc: std.mem.Allocator, limits: Limits, host_secret: [32]u8, runtime_epoch: u64) !Registry { + if (limits.max_records > hard_records or limits.max_owned_bytes > hard_owned_bytes or + limits.max_key_bytes == 0 or limits.max_key_bytes > hard_key_bytes) return error.InvalidLimits; + if (runtime_epoch == 0) return error.InvalidEpoch; + if (same_token(host_secret, @splat(0))) return error.InvalidSecret; + const capacity = @min(limits.max_records, limits.max_owned_bytes / @sizeOf(Slot)); + const slots = try alloc.alloc(Slot, capacity); + for (slots) |*slot| slot.* = .{}; + const metadata_bytes = capacity * @sizeOf(Slot); + return .{ + .limits = limits, + .secret = host_secret, + .runtime_epoch = runtime_epoch, + .slots = slots, + .state = .{ .records = 0, .owned_bytes = metadata_bytes, .metadata_bytes = metadata_bytes }, + }; + } + + pub fn deinit(self: *Registry, alloc: std.mem.Allocator) void { + for (self.slots) |slot| if (slot.record != null) alloc.free(slot.storage); + alloc.free(self.slots); + self.secret = @splat(0); + self.* = undefined; + } + + pub fn stats(self: *const Registry) Stats { + return self.state; + } + + // Host issuer surface. Task identity is (task_id, principal_domain). + pub fn host_register_task(self: *Registry, alloc: std.mem.Allocator, definition: TaskDefinition) !ContextHandle { + if (definition.task_id.len == 0 or definition.principal_domain.len == 0 or + definition.snapshot.id.len == 0 or definition.snapshot.version.len == 0 or + definition.authority_partition.len == 0 or definition.policy_generation == 0 or + definition.resource_generation == 0 or definition.implementation_generation == 0) return error.InvalidTask; + const bytes = try self.string_bytes(&.{ definition.task_id, definition.principal_domain, definition.snapshot.id, definition.snapshot.version, definition.authority_partition }); + for (self.slots) |slot| { + if (slot.record) |record| switch (record) { + .task => |existing| if (equal(existing.task_id, definition.task_id) and equal(existing.principal_domain, definition.principal_domain)) { + if (!same_task(existing, definition)) return error.TaskConflict; + return .{ .bytes = slot.token }; + }, + else => {}, + }; + } + const index = try self.reserve_slot(bytes); + const storage = try alloc.alloc(u8, bytes); + errdefer alloc.free(storage); + var remaining = storage; + var owned = definition; + owned.task_id = copy_string(&remaining, definition.task_id); + owned.principal_domain = copy_string(&remaining, definition.principal_domain); + owned.snapshot = .{ .id = copy_string(&remaining, definition.snapshot.id), .version = copy_string(&remaining, definition.snapshot.version) }; + owned.authority_partition = copy_string(&remaining, definition.authority_partition); + const token = try self.new_token(.task, index); + self.install(index, token, storage, .{ .task = owned }); + return .{ .bytes = token }; + } + + pub fn host_enroll_agent(self: *Registry, alloc: std.mem.Allocator, context: ContextHandle, agent_id: []const u8) !AgentHandle { + _ = self.find(.task, context.bytes) orelse return error.UnknownContext; + if (agent_id.len == 0) return error.InvalidAgent; + const bytes = try self.string_bytes(&.{agent_id}); + for (self.slots) |slot| { + if (slot.record) |record| switch (record) { + .agent => |agent| if (same_token(agent.context, context.bytes) and equal(agent.agent_id, agent_id)) { + if (agent.revoked) return error.AgentRevoked; + return .{ .bytes = slot.token }; + }, + else => {}, + }; + } + const index = try self.reserve_slot(bytes); + const storage = try alloc.dupe(u8, agent_id); + errdefer alloc.free(storage); + const token = try self.new_token(.agent, index); + self.install(index, token, storage, .{ .agent = .{ .context = context.bytes, .agent_id = storage } }); + return .{ .bytes = token }; + } + + // Exact duplicates return the original permit and deadline. Retained IDs are + // unique for this task context's lifetime, including reclaimed tombstones. + pub fn host_issue(self: *Registry, alloc: std.mem.Allocator, agent_handle: AgentHandle, request: contracts.CallBinding, implementation_generation: u64, deadline_ns: u64, now_ns: u64) !Permit { + const agent_index = self.find(.agent, agent_handle.bytes) orelse return error.UnknownAgent; + const agent = self.slots[agent_index].record.?.agent; + if (agent.revoked) return error.AgentRevoked; + const context_index = self.find(.task, agent.context) orelse return error.UnknownContext; + const task = self.slots[context_index].record.?.task; + const bytes = try self.binding_bytes(request); + if (request.effect != .snapshot_read or !equal(request.tool_id, "embedded-content-reader") or + !equal(request.args.options, "raw") or !equal(request.result.schema_id, "content") or + request.result.version != 1 or request.result.encoding != .bytes) return error.UnsupportedAction; + if (!equal(request.agent_id, agent.agent_id) or !equal(request.task_id, task.task_id) or + !equal(request.principal_domain, task.principal_domain) or !same_snapshot(request.snapshot, task.snapshot)) return error.BindingOutsideTask; + if (!equal(request.authority.partition, task.authority_partition) or request.authority.epoch != self.runtime_epoch or + request.authority.generation != task.policy_generation) return error.IssuerAuthorityMismatch; + if (implementation_generation != task.implementation_generation) return error.ImplementationMismatch; + for (self.slots) |slot| { + if (slot.record) |record| switch (record) { + .action => |action| if (same_token(action.context, agent.context) and action.request.id == request.id) { + if (!same_binding(action.request, request) or action.generations.implementation != implementation_generation or + !same_token(action.agent, agent_handle.bytes)) return error.DuplicateLogicalId; + if (action.reclaimed) return error.ActionReclaimed; + return .{ .bytes = slot.token }; + }, + else => {}, + }; + } + if (deadline_ns <= now_ns) return error.InvalidDeadline; + const index = try self.reserve_slot(bytes); + const storage = try alloc.alloc(u8, bytes); + errdefer alloc.free(storage); + const owned = copy_binding(request, storage); + const token = try self.new_token(.action, index); + self.install(index, token, storage, .{ .action = .{ + .context = agent.context, + .agent = agent_handle.bytes, + .request = owned, + .generations = generations(task), + .issued_ns = now_ns, + .deadline_ns = deadline_ns, + } }); + return .{ .bytes = token }; + } + + pub fn host_revoke(self: *Registry, permit: Permit) !void { + const index = self.find(.action, permit.bytes) orelse return error.UnknownPermit; + self.slots[index].record.?.action.revoked = true; + } + + pub fn host_revoke_agent(self: *Registry, agent: AgentHandle) !void { + const index = self.find(.agent, agent.bytes) orelse return error.UnknownAgent; + self.slots[index].record.?.agent.revoked = true; + } + + pub fn host_update_generations(self: *Registry, context: ContextHandle, current: Generations) !void { + const index = self.find(.task, context.bytes) orelse return error.UnknownContext; + const task = &self.slots[index].record.?.task; + if (current.policy < task.policy_generation or current.resource < task.resource_generation or + current.implementation < task.implementation_generation) return error.GenerationRollback; + task.policy_generation = current.policy; + task.resource_generation = current.resource; + task.implementation_generation = current.implementation; + } + + // Reclamation retains the exact binding tombstone and consumes its quota. + // It fences all future starts and cannot make a logical ID reusable. + pub fn host_reclaim(self: *Registry, permit: Permit) !void { + const index = self.find(.action, permit.bytes) orelse return error.UnknownPermit; + self.slots[index].record.?.action.reclaimed = true; + } + + // Whole-task teardown frees tombstones and invalidates every task enrollment + // and action. Reused slots receive new generations and opaque tokens. + pub fn host_destroy_task(self: *Registry, alloc: std.mem.Allocator, context: ContextHandle) !void { + const context_index = self.find(.task, context.bytes) orelse return error.UnknownContext; + for (self.slots, 0..) |slot, index| { + const belongs = if (slot.record) |record| switch (record) { + .task => index == context_index, + .agent => |agent| same_token(agent.context, context.bytes), + .action => |action| same_token(action.context, context.bytes), + } else false; + if (belongs) self.release_slot(alloc, index); + } + } + + // Agent surface: the parent authenticates this enrolled handle on its agent + // connection. Proposed calls never supply current policy or resource state. + // Query preserves the exact action/deadline and performs no issuance. + pub fn query(self: *const Registry, authenticated_agent: AgentHandle, permit: Permit, proposed: contracts.CallBinding, now_ns: u64) !PermitStatus { + const indices = try self.authenticate(authenticated_agent, permit, proposed); + const action = self.slots[indices.action].record.?.action; + const task = self.slots[indices.task].record.?.task; + if (now_ns < action.issued_ns) return error.TimeBeforeIssue; + const state: PermitState = if (action.reclaimed) .reclaimed else if (action.revoked) .revoked else if (now_ns >= action.deadline_ns) .expired else if (!same_generations(action.generations, generations(task))) .stale_generations else .active; + return .{ .state = state, .logical_id = action.request.id, .deadline_ns = action.deadline_ns, .generations = action.generations }; + } + + // Invoke at physical start. Revocation fences later validations but cannot + // retract an already released effect. The broker owns execution deduplication + // and outcomes for this one logical action; this registry owns authorization. + // Returned strings borrow registry storage and must not survive any registry + // mutation/reclaim/deinit. The parent copies preparation before async dispatch. + pub fn validate_start(self: *const Registry, authenticated_agent: AgentHandle, permit: Permit, proposed: contracts.CallBinding, now_ns: u64) !contracts.LogicalCall { + const status = try self.query(authenticated_agent, permit, proposed, now_ns); + switch (status.state) { + .active => {}, + .revoked => return error.PermitRevoked, + .expired => return error.PermitExpired, + .stale_generations => return error.GenerationsChanged, + .reclaimed => return error.ActionReclaimed, + } + const indices = try self.authenticate(authenticated_agent, permit, proposed); + const request = self.slots[indices.action].record.?.action.request; + const task = self.slots[indices.task].record.?.task; + return .{ .request = request, .admission = .{ .admitted = .{ + .binding = request, + .current_authority = .{ .partition = task.authority_partition, .epoch = self.runtime_epoch, .generation = task.policy_generation }, + .immutable_snapshot = task.snapshot, + } } }; + } + + const Indices = struct { action: usize, task: usize }; + + fn authenticate(self: *const Registry, authenticated_agent: AgentHandle, permit: Permit, proposed: contracts.CallBinding) !Indices { + const agent_index = self.find(.agent, authenticated_agent.bytes) orelse return error.UnknownAgent; + const agent = self.slots[agent_index].record.?.agent; + if (agent.revoked) return error.AgentRevoked; + const action_index = self.find(.action, permit.bytes) orelse return error.UnknownPermit; + const action = self.slots[action_index].record.?.action; + if (!same_token(action.agent, authenticated_agent.bytes) or !same_token(action.context, agent.context)) return error.WrongAgent; + if (!same_binding(action.request, proposed)) return error.BindingMismatch; + const task_index = self.find(.task, agent.context) orelse return error.UnknownContext; + const task = self.slots[task_index].record.?.task; + if (!equal(action.request.agent_id, agent.agent_id) or !equal(action.request.task_id, task.task_id) or + !equal(action.request.principal_domain, task.principal_domain) or !same_snapshot(action.request.snapshot, task.snapshot)) return error.BindingOutsideTask; + return .{ .action = action_index, .task = task_index }; + } + + fn find(self: *const Registry, kind: Kind, token: [32]u8) ?usize { + for (self.slots, 0..) |slot, index| { + if (slot.record) |record| { + if (std.meta.activeTag(record) == kind and same_token(slot.token, token)) return index; + } + } + return null; + } + + fn string_bytes(self: *const Registry, values: []const []const u8) !usize { + var total: usize = 0; + for (values) |value| { + if (value.len > self.limits.max_key_bytes) return error.KeyTooLarge; + total = std.math.add(usize, total, value.len) catch return error.OwnedByteLimit; + } + if (total > self.limits.max_owned_bytes) return error.OwnedByteLimit; + return total; + } + + fn binding_bytes(self: *const Registry, request: contracts.CallBinding) !usize { + const snapshot = request.snapshot orelse return error.BindingOutsideTask; + return self.string_bytes(&.{ request.agent_id, request.principal_domain, request.task_id, request.tool_id, snapshot.id, snapshot.version, request.args.path, request.args.options, request.result.schema_id, request.authority.partition }); + } + + fn reserve_slot(self: *const Registry, bytes: usize) !usize { + if (bytes > self.limits.max_owned_bytes - self.state.owned_bytes) return error.OwnedByteLimit; + for (self.slots, 0..) |slot, index| if (slot.record == null and slot.generation != std.math.maxInt(u64)) return index; + return error.RecordCapacity; + } + + fn new_token(self: *Registry, kind: Kind, index: usize) ![32]u8 { + const counter = std.math.add(u64, self.issuance_counter, 1) catch return error.CounterExhausted; + var message: [32]u8 = undefined; + std.mem.writeInt(u64, message[0..8], self.runtime_epoch, .big); + std.mem.writeInt(u64, message[8..16], @intCast(index), .big); + std.mem.writeInt(u64, message[16..24], self.slots[index].generation, .big); + std.mem.writeInt(u64, message[24..32], counter, .big); + var hmac = std.crypto.auth.hmac.sha2.HmacSha256.init(&self.secret); + hmac.update("agent-cast-authority/v1"); + hmac.update(@tagName(kind)); + hmac.update(&message); + var token: [32]u8 = undefined; + hmac.final(&token); + self.issuance_counter = counter; + return token; + } + + fn install(self: *Registry, index: usize, token: [32]u8, storage: []u8, record: Record) void { + self.slots[index].token = token; + self.slots[index].storage = storage; + self.slots[index].record = record; + self.state.records += 1; + self.state.owned_bytes += storage.len; + } + + fn release_slot(self: *Registry, alloc: std.mem.Allocator, index: usize) void { + const slot = &self.slots[index]; + self.state.owned_bytes -= slot.storage.len; + self.state.records -= 1; + alloc.free(slot.storage); + slot.storage = &.{}; + slot.record = null; + slot.token = @splat(0); + // An exhausted slot is permanently unavailable; generations never wrap. + slot.generation = std.math.add(u64, slot.generation, 1) catch std.math.maxInt(u64); + } +}; + +fn equal(a: []const u8, b: []const u8) bool { + return std.mem.eql(u8, a, b); +} + +fn same_token(a: [32]u8, b: [32]u8) bool { + return std.crypto.timing_safe.eql([32]u8, a, b); +} + +fn same_snapshot(optional: ?contracts.SnapshotIdentity, b: contracts.SnapshotIdentity) bool { + const a = optional orelse return false; + return equal(a.id, b.id) and equal(a.version, b.version); +} + +fn generations(task: TaskDefinition) Generations { + return .{ .policy = task.policy_generation, .resource = task.resource_generation, .implementation = task.implementation_generation }; +} + +fn same_generations(a: Generations, b: Generations) bool { + return a.policy == b.policy and a.resource == b.resource and a.implementation == b.implementation; +} + +fn same_task(a: TaskDefinition, b: TaskDefinition) bool { + return equal(a.task_id, b.task_id) and equal(a.principal_domain, b.principal_domain) and + same_snapshot(a.snapshot, b.snapshot) and equal(a.authority_partition, b.authority_partition) and same_generations(generations(a), generations(b)); +} + +fn same_binding(a: contracts.CallBinding, b: contracts.CallBinding) bool { + const snapshots_equal = if (a.snapshot) |snapshot| same_snapshot(b.snapshot, snapshot) else b.snapshot == null; + return a.id == b.id and equal(a.agent_id, b.agent_id) and equal(a.principal_domain, b.principal_domain) and + equal(a.task_id, b.task_id) and equal(a.tool_id, b.tool_id) and a.effect == b.effect and snapshots_equal and + equal(a.args.path, b.args.path) and a.args.window.offset == b.args.window.offset and a.args.window.length == b.args.window.length and + equal(a.args.options, b.args.options) and equal(a.result.schema_id, b.result.schema_id) and a.result.version == b.result.version and + a.result.encoding == b.result.encoding and a.output_budget == b.output_budget and equal(a.authority.partition, b.authority.partition) and + a.authority.epoch == b.authority.epoch and a.authority.generation == b.authority.generation; +} + +fn copy_string(remaining: *[]u8, input: []const u8) []const u8 { + const output = remaining.*[0..input.len]; + @memcpy(output, input); + remaining.* = remaining.*[input.len..]; + return output; +} + +fn copy_binding(request: contracts.CallBinding, storage: []u8) contracts.CallBinding { + var remaining = storage; + var owned = request; + owned.agent_id = copy_string(&remaining, request.agent_id); + owned.principal_domain = copy_string(&remaining, request.principal_domain); + owned.task_id = copy_string(&remaining, request.task_id); + owned.tool_id = copy_string(&remaining, request.tool_id); + owned.snapshot = .{ .id = copy_string(&remaining, request.snapshot.?.id), .version = copy_string(&remaining, request.snapshot.?.version) }; + owned.args.path = copy_string(&remaining, request.args.path); + owned.args.options = copy_string(&remaining, request.args.options); + owned.result.schema_id = copy_string(&remaining, request.result.schema_id); + owned.authority.partition = copy_string(&remaining, request.authority.partition); + std.debug.assert(remaining.len == 0); + return owned; +} + +fn fixture_definition() TaskDefinition { + return .{ .task_id = "task", .principal_domain = "user", .snapshot = .{ .id = "snapshot", .version = "sha256-fixture" }, .authority_partition = "read" }; +} + +fn fixture_request(id: u64, agent: []const u8) contracts.CallBinding { + return .{ + .id = id, + .agent_id = agent, + .principal_domain = "user", + .task_id = "task", + .tool_id = "embedded-content-reader", + .effect = .snapshot_read, + .snapshot = .{ .id = "snapshot", .version = "sha256-fixture" }, + .args = .{ .path = "file", .window = .{ .offset = 0, .length = 3 }, .options = "raw" }, + .result = .{ .schema_id = "content", .version = 1, .encoding = .bytes }, + .output_budget = 64, + .authority = .{ .partition = "read", .epoch = 7, .generation = 1 }, + }; +} + +fn fixture_registry(alloc: std.mem.Allocator) !Registry { + // Deterministic test key only; runtime secrets must come from host CSPRNG. + return Registry.init(alloc, .{ .max_records = 16, .max_owned_bytes = 65536 }, @splat(0xa5), 7); +} + +test "exact current task authority yields a registry owned immutable admission" { + var registry = try fixture_registry(std.testing.allocator); + defer registry.deinit(std.testing.allocator); + const context = try registry.host_register_task(std.testing.allocator, fixture_definition()); + const agent = try registry.host_enroll_agent(std.testing.allocator, context, "a"); + const request = fixture_request(1, "a"); + const permit = try registry.host_issue(std.testing.allocator, agent, request, 1, 100, 10); + const call = try registry.validate_start(agent, permit, request, 99); + try std.testing.expect(same_binding(request, call.request)); + try std.testing.expectEqual(@as(u64, 7), call.admission.admitted.current_authority.epoch); + try std.testing.expectEqual(@as(u64, 1), call.admission.admitted.current_authority.generation); + try std.testing.expectEqualStrings("read", call.admission.admitted.current_authority.partition); + try std.testing.expectEqualStrings("sha256-fixture", call.admission.admitted.immutable_snapshot.?.version); + try std.testing.expect(call.request.args.path.ptr != request.args.path.ptr); +} + +test "changing any exact logical binding field rejects validation" { + var registry = try fixture_registry(std.testing.allocator); + defer registry.deinit(std.testing.allocator); + const context = try registry.host_register_task(std.testing.allocator, fixture_definition()); + const agent = try registry.host_enroll_agent(std.testing.allocator, context, "a"); + const original = fixture_request(1, "a"); + const permit = try registry.host_issue(std.testing.allocator, agent, original, 1, 100, 0); + inline for (0..21) |change| { + var changed = original; + switch (change) { + 0 => changed.id = 2, + 1 => changed.agent_id = "b", + 2 => changed.principal_domain = "other-domain", + 3 => changed.task_id = "other-task", + 4 => changed.tool_id = "other-tool", + 5 => changed.effect = .mutation, + 6 => changed.snapshot = null, + 7 => changed.snapshot.?.id = "other-snapshot", + 8 => changed.snapshot.?.version = "other-version", + 9 => changed.args.path = "other-file", + 10 => changed.args.window.offset = 1, + 11 => changed.args.window.length = 4, + 12 => changed.args.options = "other-options", + 13 => changed.result.schema_id = "other-schema", + 14 => changed.result.version = 2, + 15 => changed.result.encoding = .utf8, + 16 => changed.output_budget = 63, + 17 => changed.authority.partition = "other-authority", + 18 => changed.authority.epoch = 8, + 19 => changed.authority.generation = 2, + 20 => changed.effect = .opaque_exec, + else => unreachable, + } + try std.testing.expectError(error.BindingMismatch, registry.validate_start(agent, permit, changed, 1)); + } +} + +test "host issuance rejects caller authority and implementation state inconsistent with registry" { + var registry = try fixture_registry(std.testing.allocator); + defer registry.deinit(std.testing.allocator); + const context = try registry.host_register_task(std.testing.allocator, fixture_definition()); + const agent = try registry.host_enroll_agent(std.testing.allocator, context, "a"); + inline for (0..3) |change| { + var request = fixture_request(1, "a"); + switch (change) { + 0 => request.authority.partition = "unregistered", + 1 => request.authority.epoch = 8, + 2 => request.authority.generation = 2, + else => unreachable, + } + try std.testing.expectError(error.IssuerAuthorityMismatch, registry.host_issue(std.testing.allocator, agent, request, 1, 100, 0)); + } + try std.testing.expectError(error.ImplementationMismatch, registry.host_issue(std.testing.allocator, agent, fixture_request(1, "a"), 2, 100, 0)); + var mutation = fixture_request(1, "a"); + mutation.effect = .mutation; + try std.testing.expectError(error.UnsupportedAction, registry.host_issue(std.testing.allocator, agent, mutation, 1, 100, 0)); + try std.testing.expectEqual(@as(usize, 2), registry.stats().records); +} + +test "forged tokens and permits belonging to other enrolled agents domains or tasks reject" { + var registry = try fixture_registry(std.testing.allocator); + defer registry.deinit(std.testing.allocator); + const context = try registry.host_register_task(std.testing.allocator, fixture_definition()); + const a = try registry.host_enroll_agent(std.testing.allocator, context, "a"); + const b = try registry.host_enroll_agent(std.testing.allocator, context, "b"); + const request = fixture_request(1, "a"); + const permit = try registry.host_issue(std.testing.allocator, a, request, 1, 100, 0); + var forged_permit = permit; + forged_permit.bytes[0] ^= 1; + var forged_agent = a; + forged_agent.bytes[0] ^= 1; + try std.testing.expectError(error.UnknownPermit, registry.query(a, forged_permit, request, 1)); + try std.testing.expectError(error.UnknownAgent, registry.query(forged_agent, permit, request, 1)); + try std.testing.expectError(error.WrongAgent, registry.query(b, permit, request, 1)); + inline for (0..2) |change| { + var definition = fixture_definition(); + if (change == 0) definition.task_id = "other-task" else definition.principal_domain = "other-domain"; + const other_context = try registry.host_register_task(std.testing.allocator, definition); + const other_agent = try registry.host_enroll_agent(std.testing.allocator, other_context, "a"); + try std.testing.expectError(error.WrongAgent, registry.query(other_agent, permit, request, 1)); + try std.testing.expectError(error.BindingOutsideTask, registry.host_issue(std.testing.allocator, other_agent, request, 1, 100, 0)); + } +} + +test "absolute deadline boundary and duplicate issuance never extend the original action" { + var registry = try fixture_registry(std.testing.allocator); + defer registry.deinit(std.testing.allocator); + const context = try registry.host_register_task(std.testing.allocator, fixture_definition()); + const agent = try registry.host_enroll_agent(std.testing.allocator, context, "a"); + const request = fixture_request(1, "a"); + try std.testing.expectError(error.InvalidDeadline, registry.host_issue(std.testing.allocator, agent, request, 1, 10, 10)); + const permit = try registry.host_issue(std.testing.allocator, agent, request, 1, 100, 10); + try std.testing.expectError(error.TimeBeforeIssue, registry.query(agent, permit, request, 9)); + try std.testing.expectEqual(PermitState.active, (try registry.query(agent, permit, request, 99)).state); + try std.testing.expectError(error.PermitExpired, registry.validate_start(agent, permit, request, 100)); + const duplicate = try registry.host_issue(std.testing.allocator, agent, request, 1, 1000, 100); + try std.testing.expect(same_token(permit.bytes, duplicate.bytes)); + const at_expiry = try registry.host_issue(std.testing.allocator, agent, request, 1, 100, 100); + const after_expiry = try registry.host_issue(std.testing.allocator, agent, request, 1, 100, 101); + try std.testing.expect(same_token(permit.bytes, at_expiry.bytes)); + try std.testing.expect(same_token(permit.bytes, after_expiry.bytes)); + try std.testing.expectError(error.InvalidDeadline, registry.host_issue(std.testing.allocator, agent, fixture_request(2, "a"), 1, 100, 101)); + var conflicting = request; + conflicting.args.window.length = 4; + try std.testing.expectError(error.DuplicateLogicalId, registry.host_issue(std.testing.allocator, agent, conflicting, 1, 100, 101)); + const status = try registry.query(agent, duplicate, request, 101); + try std.testing.expectEqual(@as(u64, 100), status.deadline_ns); + try std.testing.expectEqual(PermitState.expired, status.state); + try std.testing.expectEqual(@as(usize, 3), registry.stats().records); + try registry.host_reclaim(permit); + try std.testing.expectError(error.ActionReclaimed, registry.host_issue(std.testing.allocator, agent, request, 1, 100, 101)); +} + +test "revocation fences later starts and independent policy resource implementation updates invalidate actions" { + inline for (0..3) |change| { + var registry = try fixture_registry(std.testing.allocator); + defer registry.deinit(std.testing.allocator); + const context = try registry.host_register_task(std.testing.allocator, fixture_definition()); + const agent = try registry.host_enroll_agent(std.testing.allocator, context, "a"); + const request = fixture_request(1, "a"); + const permit = try registry.host_issue(std.testing.allocator, agent, request, 1, 100, 0); + _ = try registry.validate_start(agent, permit, request, 1); + var current: Generations = .{ .policy = 1, .resource = 1, .implementation = 1 }; + switch (change) { + 0 => current.policy = 2, + 1 => current.resource = 2, + 2 => current.implementation = 2, + else => unreachable, + } + try registry.host_update_generations(context, current); + try std.testing.expectEqual(PermitState.stale_generations, (try registry.query(agent, permit, request, 2)).state); + try std.testing.expectError(error.GenerationsChanged, registry.validate_start(agent, permit, request, 2)); + try std.testing.expectError(error.GenerationRollback, registry.host_update_generations(context, .{ .policy = 1, .resource = 1, .implementation = 1 })); + } + var registry = try fixture_registry(std.testing.allocator); + defer registry.deinit(std.testing.allocator); + const context = try registry.host_register_task(std.testing.allocator, fixture_definition()); + const agent = try registry.host_enroll_agent(std.testing.allocator, context, "a"); + const request = fixture_request(1, "a"); + const permit = try registry.host_issue(std.testing.allocator, agent, request, 1, 100, 0); + _ = try registry.validate_start(agent, permit, request, 1); + try registry.host_revoke(permit); + try std.testing.expectError(error.PermitRevoked, registry.validate_start(agent, permit, request, 2)); + try std.testing.expectEqual(PermitState.revoked, (try registry.query(agent, permit, request, 2)).state); + const duplicate = try registry.host_issue(std.testing.allocator, agent, request, 1, 1000, 2); + try std.testing.expect(same_token(permit.bytes, duplicate.bytes)); + try registry.host_revoke_agent(agent); + try std.testing.expectError(error.AgentRevoked, registry.query(agent, permit, request, 3)); +} + +test "conflicting IDs reject and reclaimed tombstones prevent replay until whole task teardown" { + var registry = try Registry.init(std.testing.allocator, .{ .max_records = 3, .max_owned_bytes = 8192 }, @splat(0xa5), 7); + defer registry.deinit(std.testing.allocator); + const context = try registry.host_register_task(std.testing.allocator, fixture_definition()); + const agent = try registry.host_enroll_agent(std.testing.allocator, context, "a"); + const request = fixture_request(1, "a"); + const permit = try registry.host_issue(std.testing.allocator, agent, request, 1, 100, 0); + var conflicting = request; + conflicting.args.window.length = 4; + try std.testing.expectError(error.DuplicateLogicalId, registry.host_issue(std.testing.allocator, agent, conflicting, 1, 100, 0)); + const before = registry.stats(); + try registry.host_reclaim(permit); + try std.testing.expectEqual(before.records, registry.stats().records); + try std.testing.expectEqual(before.owned_bytes, registry.stats().owned_bytes); + try std.testing.expectError(error.ActionReclaimed, registry.validate_start(agent, permit, request, 1)); + try std.testing.expectError(error.ActionReclaimed, registry.host_issue(std.testing.allocator, agent, request, 1, 1000, 1)); + try std.testing.expectError(error.DuplicateLogicalId, registry.host_issue(std.testing.allocator, agent, conflicting, 1, 1000, 1)); + try std.testing.expectError(error.RecordCapacity, registry.host_issue(std.testing.allocator, agent, fixture_request(2, "a"), 1, 100, 0)); + try registry.host_destroy_task(std.testing.allocator, context); + try std.testing.expectEqual(@as(usize, 0), registry.stats().records); + try std.testing.expectEqual(registry.stats().metadata_bytes, registry.stats().owned_bytes); + const new_context = try registry.host_register_task(std.testing.allocator, fixture_definition()); + const new_agent = try registry.host_enroll_agent(std.testing.allocator, new_context, "a"); + const new_permit = try registry.host_issue(std.testing.allocator, new_agent, conflicting, 1, 100, 0); + try std.testing.expect(!same_token(context.bytes, new_context.bytes)); + try std.testing.expect(!same_token(agent.bytes, new_agent.bytes)); + try std.testing.expect(!same_token(permit.bytes, new_permit.bytes)); + try std.testing.expectError(error.UnknownPermit, registry.query(new_agent, permit, request, 1)); + try std.testing.expectError(error.UnknownAgent, registry.query(agent, new_permit, conflicting, 1)); + try std.testing.expectError(error.UnknownContext, registry.host_enroll_agent(std.testing.allocator, context, "b")); + _ = try registry.validate_start(new_agent, new_permit, conflicting, 1); +} + +test "runtime epoch changes invalidate every old handle even with identical fixture secret" { + var first = try fixture_registry(std.testing.allocator); + defer first.deinit(std.testing.allocator); + var second = try Registry.init(std.testing.allocator, .{ .max_records = 16, .max_owned_bytes = 65536 }, @splat(0xa5), 8); + defer second.deinit(std.testing.allocator); + const first_context = try first.host_register_task(std.testing.allocator, fixture_definition()); + const first_agent = try first.host_enroll_agent(std.testing.allocator, first_context, "a"); + const first_request = fixture_request(1, "a"); + const first_permit = try first.host_issue(std.testing.allocator, first_agent, first_request, 1, 100, 0); + const second_context = try second.host_register_task(std.testing.allocator, fixture_definition()); + const second_agent = try second.host_enroll_agent(std.testing.allocator, second_context, "a"); + var second_request = first_request; + second_request.authority.epoch = 8; + const second_permit = try second.host_issue(std.testing.allocator, second_agent, second_request, 1, 100, 0); + try std.testing.expect(!same_token(first_permit.bytes, second_permit.bytes)); + try std.testing.expectError(error.UnknownPermit, second.query(second_agent, first_permit, second_request, 1)); + try std.testing.expectError(error.UnknownAgent, second.query(first_agent, second_permit, second_request, 1)); +} + +test "owned frozen task agent and action bindings survive mutable input reuse" { + var registry = try fixture_registry(std.testing.allocator); + defer registry.deinit(std.testing.allocator); + var task = "task".*; + var definition = fixture_definition(); + definition.task_id = &task; + const context = try registry.host_register_task(std.testing.allocator, definition); + @memset(&task, 'X'); + var agent_name = "a".*; + const agent = try registry.host_enroll_agent(std.testing.allocator, context, &agent_name); + agent_name[0] = 'X'; + var path = "file".*; + var request = fixture_request(1, "a"); + request.args.path = &path; + const permit = try registry.host_issue(std.testing.allocator, agent, request, 1, 100, 0); + @memset(&path, 'X'); + const original = fixture_request(1, "a"); + const validated = try registry.validate_start(agent, permit, original, 1); + try std.testing.expectEqualStrings("task", validated.request.task_id); + try std.testing.expectEqualStrings("a", validated.request.agent_id); + try std.testing.expectEqualStrings("file", validated.request.args.path); + try std.testing.expect(validated.request.args.path.ptr != path[0..].ptr); +} + +test "key and record byte quotas reject before owned allocation and failures clean up" { + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); + const alloc = failing.allocator(); + var registry = try fixture_registry(alloc); + defer registry.deinit(alloc); + const huge: [4097]u8 = @splat('x'); + var definition = fixture_definition(); + definition.task_id = &huge; + failing.fail_index = failing.alloc_index; + try std.testing.expectError(error.KeyTooLarge, registry.host_register_task(alloc, definition)); + try std.testing.expect(!failing.has_induced_failure); + try std.testing.expectEqual(@as(usize, 0), registry.stats().records); + failing.fail_index = std.math.maxInt(usize); + const context = try registry.host_register_task(alloc, fixture_definition()); + const agent = try registry.host_enroll_agent(alloc, context, "a"); + const before = registry.stats(); + failing.fail_index = failing.alloc_index; + try std.testing.expectError(error.OutOfMemory, registry.host_issue(alloc, agent, fixture_request(1, "a"), 1, 100, 0)); + try std.testing.expectEqual(before.records, registry.stats().records); + try std.testing.expectEqual(before.owned_bytes, registry.stats().owned_bytes); + try std.testing.expect(registry.stats().owned_bytes <= registry.limits.max_owned_bytes); + try std.testing.expectEqual(registry.stats().owned_bytes, failing.allocated_bytes - failing.freed_bytes); + var tiny = try Registry.init(std.testing.allocator, .{ .max_records = 1, .max_owned_bytes = @sizeOf(Slot) }, @splat(0xa5), 7); + defer tiny.deinit(std.testing.allocator); + try std.testing.expectError(error.OwnedByteLimit, tiny.host_register_task(std.testing.allocator, fixture_definition())); + try std.testing.expectError(error.InvalidLimits, Registry.init(std.testing.allocator, .{ .max_records = 1025 }, @splat(0xa5), 7)); + try std.testing.expectError(error.InvalidSecret, Registry.init(std.testing.allocator, .{}, @splat(0), 7)); + try std.testing.expectError(error.InvalidEpoch, Registry.init(std.testing.allocator, .{}, @splat(0xa5), 0)); +} diff --git a/experiments/agent_cast/src/tests.zig b/experiments/agent_cast/src/tests.zig index 1273ea0845..52701cda6e 100644 --- a/experiments/agent_cast/src/tests.zig +++ b/experiments/agent_cast/src/tests.zig @@ -8,4 +8,5 @@ test { _ = @import("cache_demo.zig"); _ = @import("subscribers.zig"); _ = @import("wire.zig"); + _ = @import("authority.zig"); } diff --git a/scripts/qualify_agent_cast.py b/scripts/qualify_agent_cast.py index 820d771da8..4e1529e3ef 100644 --- a/scripts/qualify_agent_cast.py +++ b/scripts/qualify_agent_cast.py @@ -46,6 +46,8 @@ def verify_report(report, content, mode): for index, receipt in enumerate(receipts): if receipt["logical_id"] != index + 1: raise ValueError("logical identity changed") + if receipt["principal_domain"] != "fixture-operator" or receipt["task_id"] != "fixture-task": + raise ValueError("admitted principal or task changed") if index < 8: expected = content[(index // 2) * 64:((index // 2) + 1) * 64] agent = "agent-a" if index % 2 == 0 else "agent-b" @@ -53,11 +55,14 @@ def verify_report(report, content, mode): raise ValueError("agent identity changed") if receipt["admission_status"] != "admitted" or receipt["status"] != "success": raise ValueError("allowed read failed") - if receipt["physical_group"] is None: - raise ValueError("successful read has no backing work") + expected_group = index // 2 if run["mode"] == "enabled" else index + if receipt["physical_group"] != expected_group: + raise ValueError("successful read has the wrong backing work identity") else: expected = b"" admission = "denied" if index == 8 else "stale_authority" + if receipt["agent_id"] != ("denied-agent" if index == 8 else "stale-agent"): + raise ValueError("rejected agent identity changed") if receipt["admission_status"] != admission or receipt["status"] != "admission_rejected": raise ValueError("rejected call executed") if receipt["physical_group"] is not None: @@ -83,14 +88,18 @@ def verify_cache_report(report, content): if report["immutable_input_sha256"] != hashlib.sha256(content).hexdigest() or report["immutable_input_bytes"] != len(content): raise ValueError("cache demo did not capture the actual supplied fixture") limits, stats = report["limits"], report["cache"] + # These caps are independently specified by this qualification workload. + # A binary cannot authorize a larger budget by reporting matching counters. + if limits != {"max_entries": 8, "max_bytes": 32768}: + raise ValueError("cache changed the independent workload limits") if not 0 < stats["metadata_bytes"] <= stats["retained_bytes"] <= limits["max_bytes"]: raise ValueError("cache retained byte accounting exceeds its stated limits") if stats["entries"] != 4 or stats["entries"] > limits["max_entries"]: raise ValueError("cache did not retain four independent actual values") if (stats["hits"], stats["misses"], stats["backing_reads"], stats["rejected"]) != (4, 4, 4, 3): raise ValueError("cache reported incorrect physical work or rejected consumers") - if not 0 < report["batch_limits"]["max_result_bytes"] <= 16 * 1024 * 1024: - raise ValueError("cache demo has no bounded aggregate output reservation") + if report["batch_limits"] != {"max_result_bytes": 4096}: + raise ValueError("cache changed the independent aggregate output reservation") names = ["cold-agent-a", "completed-values-agent-b", "rejected-consumers"] if [batch["name"] for batch in report["batches"]] != names: raise ValueError("wrong independent cache batches") diff --git a/scripts/tests/test_agent_cast_qualification.py b/scripts/tests/test_agent_cast_qualification.py new file mode 100644 index 0000000000..01aee94b12 --- /dev/null +++ b/scripts/tests/test_agent_cast_qualification.py @@ -0,0 +1,136 @@ +"""Negative controls for the agent-cast receipt oracles.""" + +import copy +import hashlib +import importlib.util +import json +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +EVIDENCE = ROOT / "docs" / "agent-cast" / "evidence" / "m2-2026-10-06" +SPEC = importlib.util.spec_from_file_location("qualify_agent_cast", ROOT / "scripts" / "qualify_agent_cast.py") +QUALIFIER = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(QUALIFIER) + + +def read_report(name): + return json.loads((EVIDENCE / name).read_text(encoding="utf-8")) + + +def receipt_hash(content): + return hashlib.sha256(content).hexdigest() + + +def replace_output(receipt, content): + receipt["output_hex"] = content.hex() + receipt["output_bytes"] = len(content) + receipt["output_sha256"] = receipt_hash(content) + + +class AgentCastQualificationTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.content = (EVIDENCE / "fixture-0.bin").read_bytes() + cls.compare = read_report("compare-0.stdout") + cls.cache = read_report("cache-0.stdout") + + def assert_compare_rejected(self, report, content=None): + with self.assertRaises((ValueError, KeyError, TypeError)): + QUALIFIER.verify_report(report, self.content if content is None else content, "compare") + + def assert_cache_rejected(self, report, content=None): + with self.assertRaises((ValueError, KeyError, TypeError)): + QUALIFIER.verify_cache_report(report, self.content if content is None else content) + + def test_receipt_backed_positive_controls(self): + QUALIFIER.verify_report(copy.deepcopy(self.compare), self.content, "compare") + QUALIFIER.verify_cache_report(copy.deepcopy(self.cache), self.content) + + def test_compare_rejects_skipped_receipt_and_empty_or_pinned_outputs(self): + skipped = copy.deepcopy(self.compare) + skipped["runs"][0]["logical_call_count"] = 9 + self.assert_compare_rejected(skipped) + + empty = copy.deepcopy(self.compare) + for run in empty["runs"]: + for receipt in run["receipts"][:8]: + replace_output(receipt, b"") + self.assert_compare_rejected(empty) + + pinned = copy.deepcopy(self.compare) + for run in pinned["runs"]: + for receipt in run["receipts"][:8]: + replace_output(receipt, b"P" * 64) + self.assert_compare_rejected(pinned) + + def test_compare_rejects_wrong_fixture_digest_length_and_logical_mismatch(self): + wrong_digest = copy.deepcopy(self.compare) + wrong_digest["immutable_input_sha256"] = "0" * 64 + self.assert_compare_rejected(wrong_digest) + + wrong_length = copy.deepcopy(self.compare) + wrong_length["immutable_input_bytes"] += 1 + self.assert_compare_rejected(wrong_length) + + mismatch = copy.deepcopy(self.compare) + mismatch["runs"][1]["receipts"][0]["task_id"] = "other-task" + self.assert_compare_rejected(mismatch) + + def test_compare_rejects_consistent_but_wrong_authority_and_identity(self): + changed = copy.deepcopy(self.compare) + for run in changed["runs"]: + for receipt in run["receipts"]: + receipt["principal_domain"] = "unauthorized-principal" + receipt["task_id"] = "wrong-task" + self.assert_compare_rejected(changed) + + def test_compare_rejects_changed_logical_identity(self): + changed_id = copy.deepcopy(self.compare) + for run in changed_id["runs"]: + run["receipts"][0]["logical_id"] = 99 + self.assert_compare_rejected(changed_id) + + def test_cache_rejects_wrong_input_and_changed_principal_or_task(self): + wrong_digest = copy.deepcopy(self.cache) + wrong_digest["immutable_input_sha256"] = "0" * 64 + self.assert_cache_rejected(wrong_digest) + wrong_length = copy.deepcopy(self.cache) + wrong_length["immutable_input_bytes"] += 1 + self.assert_cache_rejected(wrong_length) + + changed = copy.deepcopy(self.cache) + for batch in changed["batches"]: + for receipt in batch["receipts"]: + receipt["principal_domain"] = "unauthorized-principal" + receipt["task_id"] = "wrong-task" + self.assert_cache_rejected(changed) + + def test_cache_rejects_unauthorized_result_and_invented_counters(self): + unauthorized = copy.deepcopy(self.cache) + receipt = unauthorized["batches"][2]["receipts"][0] + receipt["source"] = "cache" + receipt["cache_hit"] = True + receipt["cache_miss"] = False + receipt["backing_reads"] = 0 + replace_output(receipt, self.content[:64]) + self.assert_cache_rejected(unauthorized) + + invented_hits = copy.deepcopy(self.cache) + invented_hits["cache"]["hits"] += 1 + self.assert_cache_rejected(invented_hits) + invented_reads = copy.deepcopy(self.cache) + invented_reads["batches"][0]["backing_reads"] += 1 + self.assert_cache_rejected(invented_reads) + + def test_cache_rejects_unbounded_accounting(self): + unbounded = copy.deepcopy(self.cache) + unbounded["limits"]["max_bytes"] = 10**12 + unbounded["cache"]["retained_bytes"] = 10**12 + unbounded["cache"]["metadata_bytes"] = 5 * 10**11 + self.assert_cache_rejected(unbounded) + + +if __name__ == "__main__": + unittest.main()