The Workbench standard
Outcomes contract
A run produces a conversation and, when it applies, a durable outcome: the changesets, artifacts, and links it returned, described independently of the runner, model provider, and runtime. The conversation is not a substitute for returned files or workspace changes.
The contract has three schemas:
/schemas/outcomes/v1/outbox.schema.json: the optional declaration an agent writes./schemas/outcomes/v1/outcome.schema.json: the immutable outcome manifest the engine commits./schemas/outcomes/v1/application.schema.json: the mutable application receipt.
The outcomes contract is a reference contract. It is not covered by the 1.0 stability promise.
Three kinds of result#
- Changesets: workspace edits, with an explicit apply boundary.
- Artifacts: reports, screenshots, images, archives, and other files.
- Links: external results such as pull requests and previews.
By runtime#
| Runtime | Workspace execution | Initial application state |
|---|---|---|
| Local | Runs against host directories | present |
| Docker | Runs against mounted host directories | present |
| E2B | Runs against selected copies in a fresh sandbox | pending |
| Daytona | Runs against selected copies in a fresh sandbox | pending |
Local and Docker keep their in-place editing. Their outcomes record the run's changes; the engine does not undo edits when a run fails or a client disconnects. E2B and Daytona never silently apply collected edits to the host. Returning remote results and accepting them are separate operations.
The outbox#
Every durable execution gets an engine-owned outbox directory, given to the agent as WORKBENCH_OUTPUT_DIR and in its runtime context. On the local runtime it is ~/.workbench/files/work/<run-id>/outbox. Docker, E2B, and Daytona expose /outbox.
The agent writes requested deliverables there with its ordinary file tools. No tool is injected. Every regular file under the outbox is collected as an artifact, except the reserved top-level outcome.json. Users ask for results in plain language; they never need to know about the outbox.
The declaration#
outcome.json is optional. It adds a summary, artifact metadata, and links:
{
"version": 1,
"summary": "Prepared a report and implementation changes for review",
"artifacts": [
{
"path": "reports/findings.html",
"name": "Findings.html",
"description": "The collected research report",
"media_type": "text/html"
}
],
"links": [
{
"label": "Implementation PR",
"uri": "https://github.com/example/project/pull/42",
"kind": "pull_request"
}
]
}| Field | Rules |
|---|---|
version |
Required. 1. |
summary |
1 to 16,384 characters. |
artifacts[].path |
Required. A safe outbox-relative path, up to 4,096 characters. The file must exist. |
artifacts[].name |
1 to 512 characters. Affects presentation only. |
artifacts[].description |
1 to 4,096 characters. |
artifacts[].media_type |
1 to 255 characters. |
links[].label |
Required. 1 to 512 characters. |
links[].uri |
Required. http:// or https://, up to 8,192 characters. |
links[].kind |
Optional: pull_request, preview, or external. |
Artifact paths are unique and cannot escape the outbox. Undeclared regular files are still collected. Symlinks, special files, invalid JSON, invalid metadata, and missing declared files fail collection visibly. The declaration is limited to 1 MiB.
Recording a pull request link does not create a pull request, verify its contents, or grant Git credentials. Publishing, pushing, and opening pull requests stay explicit operations of an authorized agent or host.
The outcome manifest#
The engine validates and commits an immutable version 1 manifest for each completed turn snapshot and for the final collection of an execution attempt.
| Field | Meaning |
|---|---|
version |
1. |
id |
The engine-generated wbo_... outcome ID. |
run_id |
The wb_... run that produced it. |
created_at |
Collection time, ISO 8601. |
completeness |
complete or partial. |
turn_index |
Optional positive turn index, for a live snapshot. |
summary |
Optional, from the declaration. |
changesets |
Collected changes, one per writable workspace. |
artifacts |
Each with id, name, content, and optional path and description. |
links |
Each with id, label, uri, and optional kind. |
warnings |
Each with code and message. |
complete describes an orderly collection, not a guarantee that the work is correct. A failed or cancelled run can return a partial outcome; the run's terminal status remains the authority on whether the work succeeded.
File contents are stored by SHA-256 digest. A content descriptor is digest (sha256: and 64 hex characters), media_type, and size_bytes. Bytes are verified when captured and when read. The engine never resizes images, transcodes files, or substitutes a preview for an original.
Changesets#
A changeset has an id, a workspace ({"kind":"primary"} or {"kind":"named","name":...}), a base with snapshot_digest and optional git_revision, ordered entries, file-count stats, and an optional review diff.
Entries are add, modify, or delete. Existing paths have a before fingerprint: kind, permission mode, and either file digest and size or a symlink target. Added and modified paths have an after state. Deletes have no after. Renames are an add and a delete. The entries and stored contents are the apply authority; the review diff is for reading.
The baseline is the workspace before execution, including dirty files. Changes are not inferred from runner text or file.changed events. Selection omits repository metadata, dependency trees, common credential paths, and secret-bearing filenames. This protection is path-based, not a content classifier.
Application receipts#
A receipt is separate, mutable metadata with version, outcome_id, state, updated_at, and, when applied, applied_at.
pending: isolated results not yet accepted into a host workspace.present: execution already edited the host workspace through Local or Docker.applied: a pending changeset was explicitly applied.
Inspecting, opening, or exporting never changes a receipt or a manifest.
Apply and export#
Before applying, the engine checks every target path and expected fingerprint across all involved workspaces. A conflict rejects the whole operation before any path changes. Escaping paths, symlink parents, and escaping symlink targets are rejected. A retry never overwrites later unrelated host edits. Apply is not an operating-system transaction: an abrupt host crash can interrupt it.
Export writes outcome.json, the artifact files, and a directory per changeset with its after-files and optional changes.diff. The destination must not exist. Neither export nor inspect runs returned HTML, links, scripts, or binaries.
The CLI commands are on Outcomes.
Turn snapshots and final collection#
Multi-turn sessions snapshot finished outbox files and links after each completed, non-cancelled turn. Snapshots have a positive turn_index, partial completeness, no changesets, and a present receipt. Unchanged results do not produce new snapshots. Revising a file produces a new snapshot; earlier links still open the earlier bytes. Producers must finish writing before a turn ends, using atomic rename for background writers.
The final outcome collects workspace changes and the outbox after execution stops. The engine commits it before emitting outcome.available and before destroying disposable runtime resources. A failed collection is never disguised as an empty successful outcome; a failed turn snapshot emits outcome.failed.
A resumed session gets a new outbox, seeded with working copies of the latest retained revision of each file in that session.
Recovery#
For E2B, a failed collection keeps a private checkpoint linked to the original sandbox and pauses it when possible. wb outcome <run> --recover reconnects only to that sandbox and returns a partial outcome with a recovery warning; --discard-recovery abandons it. Daytona keeps no recovery checkpoint: its sandbox is deleted at cleanup even when collection failed.
Storage limits#
The reference engine stores outcomes under $WORKBENCH_HOME/outcomes/<outcome-id>/ and shared content under $WORKBENCH_HOME/blobs/, with 0700 directories and 0600 files.
| Limit | Default |
|---|---|
| One content blob | 256 MiB |
| Unique content per outcome | 512 MiB |
| One outcome manifest | 16 MiB |
| All engine-owned result storage | 5 GiB |
| Workspace baselines, and E2B and Daytona transfers each way | 512 MiB |
A quota failure never silently evicts retained outcomes. An outcome is a data contract, not permission to execute what it returns.