Under container delivery ([sandbox] container_delivery = true, shipped in #963) the host no longer drives the agent, so it no longer reads the model usage and the model name out of the ACP stream. It learns both from a file the orchestrator writes inside the container: outcome.json.
Inside that container the orchestrator and the job's agent run as the same uid. So the agent can write to any file the orchestrator can write, outcome.json included, at any point before the host reads it. Nothing structural stops a job from reporting numbers it chose.
What forging actually buys today, measured
I traced where the usage value goes. It reaches:
- the seller's exec-metadata block (
seller_exec.rs),
- the episode telemetry (
episode.rs),
- the roster's model name, which the seat advertises on kind-30340 and on a claim.
It does not reach budget.rs or authorize_pay.rs. per_job_budget_sats is the buyer's sat budget and has nothing to do with model consumption. Payment is the offer amount, agreed before the job runs.
So a forged report does not move sats. It does two things:
- It puts a false
harness_model into an event the seller signs, so the seller attests to something untrue about its own run.
- It skews telemetry.
That is why this is a hardening task and not an incident.
Why not meter at the credential proxy (the original Task B10)
The plan docs/superpowers/plans/2026-09-01-container-delivery-wiring-implementation.md specifies Task B10: move usage and model accounting to the host credential proxy, which forwards every model call and cannot be touched by the job.
That is the stronger answer and it stays the right long-term shape. It is proposed here as the follow-up rather than the first step, because:
- Nothing today depends on the number for a money decision, so the proxy's tamper-resistance buys correctness we are not yet spending.
- Metering at the proxy means teaching it to parse per-vendor API traffic for token counts and model names — one shape per upstream (Anthropic, Cursor, OpenAI), each of which can change under us.
- uid separation removes the practical forging path for a fraction of that work, and it is useful for its own sake: it also stops a job from touching anything else the orchestrator owns.
Proposal
Run the orchestrator and the agent as different uids inside the job container, with the orchestrator's output files owned by the orchestrator uid and not writable by the agent uid.
- The agent keeps write access to the workdir, which is the whole point of the job.
outcome.json, the delivery oid file, and the agent-done marker become writable only by the orchestrator.
- The inputs file is already deleted before the agent starts (C3) and the push token is already written only after the agent's process group is reaped, so this closes the remaining same-uid gap rather than opening a new design.
- After this, forging the report needs the agent to escape its uid, which is a container-escape-class problem rather than a shell command.
Acceptance
- A test proves the agent uid cannot write the orchestrator's output files.
- A live job still delivers and is paid, on both token modes.
- The residual risk is written down where the current same-uid note lives (
delivery_orchestrator.rs module docs).
Then, separately
Keep Task B10 open for the moment usage first gates a real decision — a guard on model spend, per-usage pricing, or anything reputational keyed on the model. At that point the proxy count is the only one worth trusting.
One limit worth stating plainly, for both approaches. Proxy metering and uid separation both protect the seller from the job. Neither does anything about a dishonest seller, who owns the host and the proxy. If the advertised model is meant as a signal buyers rely on, that is a different and harder problem — see #863.
Under container delivery (
[sandbox] container_delivery = true, shipped in #963) the host no longer drives the agent, so it no longer reads the model usage and the model name out of the ACP stream. It learns both from a file the orchestrator writes inside the container:outcome.json.Inside that container the orchestrator and the job's agent run as the same uid. So the agent can write to any file the orchestrator can write,
outcome.jsonincluded, at any point before the host reads it. Nothing structural stops a job from reporting numbers it chose.What forging actually buys today, measured
I traced where the usage value goes. It reaches:
seller_exec.rs),episode.rs),It does not reach
budget.rsorauthorize_pay.rs.per_job_budget_satsis the buyer's sat budget and has nothing to do with model consumption. Payment is the offer amount, agreed before the job runs.So a forged report does not move sats. It does two things:
harness_modelinto an event the seller signs, so the seller attests to something untrue about its own run.That is why this is a hardening task and not an incident.
Why not meter at the credential proxy (the original Task B10)
The plan
docs/superpowers/plans/2026-09-01-container-delivery-wiring-implementation.mdspecifies Task B10: move usage and model accounting to the host credential proxy, which forwards every model call and cannot be touched by the job.That is the stronger answer and it stays the right long-term shape. It is proposed here as the follow-up rather than the first step, because:
Proposal
Run the orchestrator and the agent as different uids inside the job container, with the orchestrator's output files owned by the orchestrator uid and not writable by the agent uid.
outcome.json, the delivery oid file, and theagent-donemarker become writable only by the orchestrator.Acceptance
delivery_orchestrator.rsmodule docs).Then, separately
Keep Task B10 open for the moment usage first gates a real decision — a guard on model spend, per-usage pricing, or anything reputational keyed on the model. At that point the proxy count is the only one worth trusting.
One limit worth stating plainly, for both approaches. Proxy metering and uid separation both protect the seller from the job. Neither does anything about a dishonest seller, who owns the host and the proxy. If the advertised model is meant as a signal buyers rely on, that is a different and harder problem — see #863.