Versioned workspaces for agent output

A workspace represents a multi-file tree as records and artifacts. Agents can create new versions, execute a version in a jail, serve it to a browser and export its history as a Git repository. Workspaces are an extension over Radia's public API.

Why workspaces exist

Code generation often produces a project rather than one source file. The files must persist between iterations, preserve their version history and materialize as a directory for tools that expect a filesystem.

Radia stores each tree version durably while using temporary directories only for execution. The record model remains the source of identity, provenance and authorization.

An extension over runtime primitives

The runtime has no workspace, file, path or directory abstraction.

The workspace extension combines a manifest record, artifact records for file contents and a latest-version projection. It uses the same public API as an external application.

application a chat agent, a code reviewer, your own worker examples/ extension: workspace, sandbox, git export imports the SDK only. Never the runtime extensions/ runtime: records, artifacts, leases, grants, taint knows nothing about files src/
Shared conventions live in extensions/. Application-specific behavior stays in the application, and the runtime remains limited to records, leases, grants and artifacts.

Structural tests prevent extensions from importing runtime internals. Commands such as Git export are therefore ordinary clients, not privileged server operations.

Tree digests and path validation are normative. Their exact algorithms cross a trust boundary and are covered by extension conformance tests.

Workspace representation

A manifest record contains a name, tree digest, base version and file list. Each file entry contains a validated path, mode, content digest and artifact id.

a workspace record
{ "name": "landing-page",
  "treeDigest": "t1:9f2c...ade1",
  "basedOn": "01JB2Q7X4KJ8ZN0R5V3M9WPHTC",
  "files": [
    { "path": "index.html", "mode": "100644", "digest": "sha256:4b1e...", "artifactId": "01JB2..." },
    { "path": "style.css",  "mode": "100644", "digest": "sha256:c70a...", "artifactId": "01JB2..." },
    { "path": "cat.png",    "mode": "100644", "digest": "sha256:8ee3...", "artifactId": "01JB2..." }
  ] }

Editing writes a new manifest linked to its base version. File bytes are content-addressed, so unchanged files are shared across versions. Concurrent edits from the same base create visible successor branches.

Existing artifacts can be attached without copying their bytes. Attached artifacts become data parents of the manifest, propagating their provenance labels.

radia workspaces
NAME          FILES  VERSIONS  TREE            OWNER
landing-page      3         7  t1:9f2cade1a4…  human:alice
scraper           9        22  t1:07b3e5c918…  human:alice

Paths are validated when a manifest is written and again during materialization. The materializer verifies that every resolved file remains within the destination directory.

Sandboxed execution

Radia ships a Deno permission jail for JavaScript and a bubblewrap backend for available host interpreters. Sandbox records describe the guarantees of each backend.

A worker probes a sandbox before advertising it. A backend is available only when the probe confirms the guarantees claimed by its record.

Successful probes determine which execution tools the worker publishes. A host that cannot provide the required isolation does not advertise that runner.

The untrusted process holds no Radia credential. The host performs permitted operations on its behalf and supplies authoritative metadata that the child cannot choose.

Deno permissions alone do not confine module loading. Filesystem confinement therefore uses a mount namespace on Linux and a Seatbelt profile on macOS. Sandbox records state whether this additional boundary is active.

A run writes files into a separate output directory. When execution finishes, the host captures that directory as a new workspace version. The input tree remains immutable and may be shared by concurrent runs.

A promoted workspace runs across a credential boundary A binding and a promotion grant must name the same workspace digest. A trusted host holds the agent run credential and launches untrusted code without a credential. The code writes to a separate directory, which the host captures as a successor workspace. binding agent:scraper → digest t1:07b3… promotion grant prod may claim with t1:07b3… digest agrees otherwise refuse trusted host holds the agent's run credential adds parents, labels and identity sandbox boundary untrusted code · no credential separate output tree successor workspace host captures the code tree is immutable; every run writes somewhere new
The binding chooses the code, the grant pins what may claim work, and the host requires the digests to agree. Only the host holds a credential; executed code receives a confined tree.

Promotion and agent bindings

A binding record associates an agent with a workspace digest. The generic host materializes that version, executes it in the selected jail and claims work through the agent's run.

Promotion rotates grants that are pinned to the workspace digest. The claim permission and executable code version are therefore checked together.

what is prod running, answered from the enforcement path
$ radia promote t1:07b3e5c918… --tier prod --pin agent:scraper:take,ack
$ radia pins agent:scraper --tier prod
agent:scraper on 'prod': t1:07b3e5c918…

A host refuses a binding whose digest disagrees with the active grant pin. Rollback promotes a previous digest through the same mechanism. The jail bounds host access, the binding selects code and the grant limits the records that code may process and produce.

Bindings declare which request fields identify input artifacts. The host fetches those artifacts under the agent's authority and places them in the run directory. Outputs name the inputs as parents so their labels propagate.

The analysis example uses promoted workspace stages through one generic host. View the analysis example →

Serving and exporting workspaces

Serve a tree to a browser

The extension converts a manifest into a path-to-artifact index and mints a short-lived capability over the complete tree. Scriptable content is served from an isolated origin.

Requests match entries in the fixed index and never resolve against a filesystem. Artifact access is authorized when the capability is minted, and the URL expires with that capability.

Export a Git repository

radia workspace-git
$ radia workspace-git landing-page --dir /tmp/landing.git
landing-page: 7 versions, 34 objects -> /tmp/landing.git
$ git clone /tmp/landing.git && cd landing && git log --oneline
9c1e4a2 attempt 7: fix the header overlap
04b7f30 attempt 6: add the illustration
…

The same projection can be served over read-only Git HTTP:

radia git-serve
$ radia git-serve
git server on http://127.0.0.1:7790  (reading http://127.0.0.1:7788)

$ git clone http://you:<token>@127.0.0.1:7790/landing-page.git

Git authentication uses a mint-only credential and applies the caller's Radia permissions. Revocation stops subsequent fetches. Push is explicitly unsupported.

Each manifest version becomes a commit, version links become commit parents and trailers refer back to the source record. The exporter writes Git objects directly without invoking Git.

Export is one-way. Git object identity and rewritable history are not accepted as workspace identity; Radia records and SHA-256 tree digests remain authoritative.

Capabilities and limits

Anyone who has purged a leaked credential from a repository knows what it costs: every downstream hash changes, every fork force-pushes, every clone is invalidated, signed tags die. Here a file is an artifact, so one payload can be destroyed while the tree digest still verifies and the history still reads. The blast radius is gone.

And the guarantee is narrower than it sounds; here it is in full. The manifest keeps the path and the hash of the erased file, in a record body that has no erasure path of its own. So credentials/prod-db.txt outlives its own contents, and anyone holding a candidate secret can hash it and confirm that exact value was in that tree. A destroyed build output or document is gone in every practical sense. A destroyed credential is unreadable and still confirmable, and its filename is plaintext forever. What this removes is the blast radius, not the disclosure.

Past that: every file version is attributable to a specific run, the dependency set is inside the record rather than claimed by a lockfile, a grant can scope which workspace an agent may touch at all, and labels track whether a file descends from something untrusted. Branch protection has been approximating that last set for years, and a git hook dies to --no-verify.

What it does not buy: incremental builds, editor feedback, merge, blame. Git does the storage model better and has thirty years of tooling around it. This is not a better git. It is a working tree whose every state is a record the runtime can authorize, attest and erase.

What the system can tell you about all this →