Skip to content

Workspace content layout

The workspace content volume holds one directory tree per workspace namespace. This page lists every path in that tree, the component that writes and reads it, the mount modes that decide how pods address it, and the retention rules that apply to it. For the reasons behind the layout, see Workspace content layout.

The operator mounts the whole volume at --workspace-content-path (default /workspace-content) and addresses every workspace as:

/workspace-content/{workspace}/{namespace}/

All paths below are relative to {workspace}/{namespace}/.

The operator and the arena controller take --workspace-content-scoped, set by the chart value workspaceContent.scoped (default false). Both must agree.

ModeChart valueVolume root of a workspace namespace’s PVCsubPath a pod uses for {rel}
Shared (share-root)workspaceContent.scoped: falsethe whole share{workspace}/{namespace}/{rel}
ScopedworkspaceContent.scoped: truethe {workspace}/{namespace} subtree{rel}

A recorded contentPath (for example ArenaRun.status.source.contentPath) is relative to {workspace}/{namespace} in both modes. The subPath a pod mounts, and SandboxClaim.status.volumeSubPath, carry the {workspace}/{namespace} prefix in shared mode only.

A pod mounts the workspace’s own PVC, workspace-{namespace}-content.

{workspace}/{namespace}/
├── arena/
│ ├── repos/{project}.git/ bare repository
│ ├── workspaces/{project}/{user}/ a user's working clone
│ ├── sources/{project}/.arena/versions/{sha}/ project snapshot
│ ├── template-sources/{source}/.arena/versions/{version}/
│ └── template-indexes/{source}.json
├── skills/{source}/.arena/versions/{version}/
├── manifests/{promptpack}.json
├── sandboxes/{claim}/ scratch sandbox filesystem
└── sessions/{runtime}/{key}/
├── session.json
└── handles/{sandbox}/{instance}/ session sandbox filesystem
PathContentsWritten byRead byAuthoritative
arena/repos/{project}.git/Bare git repository of an ArenaProject, plus an omnia-owner file recording the project’s UIDArena controller (seed, fetch of an external remote); operator content API (push and sync on a hosted project)Operator, Arena controllerHosted project: yes. External project: no, the remote is
arena/workspaces/{project}/{user}/One user’s working clone; {user} is the pseudonym (vuid) from the caller’s verified tokenOperator content API on the user’s behalf; a builder sandbox bound to it; the Arena controller deletes it when idleOperator content API; builder sandbox (read-write); dev agent runtime (read-only)Until pushed
arena/sources/{project}/.arena/versions/{sha}/One commit of the project, exported; {sha} is the full 40-character commit SHAArena controller (project head, run snapshot); operator (run start, deploy)Arena worker pods (read-only); content API (read-only)No
arena/template-sources/{source}/.arena/versions/{version}/Synced ArenaTemplateSource content, with .arena/HEAD naming the served versionArena controllerArena controller, content APINo
arena/template-indexes/{source}.jsonGenerated index of a template source’s templatesArena controllerOperator content APINo
skills/{source}/.arena/versions/{version}/Synced SkillSource content, with .arena/HEAD naming the served version; spec.targetPath overrides skills/{source}OperatorAgent runtime containersNo
manifests/{promptpack}.jsonSkill manifest of a PromptPack, named after the PromptPack objectOperator (PromptPack reconciler)Agent runtime containersNo
sandboxes/{claim}/Filesystem of a scratch sandboxSandbox pod; the operator removes it once no SandboxClaim of that name existsSandbox podNo
sessions/{runtime}/{key}/session.json (the operator’s record of the session) and handles/{sandbox}/{instance}/, a sandbox’s /workspace for the session; {key} is the hex of the first 16 bytes of the SHA-256 of the session IDOperator (session.json); sandbox pods (handle directories)Sandbox podsNo

Synced sources and sandbox sync-back share one writer, which stores {targetPath}/.arena/versions/{version}/ and {targetPath}/.arena/HEAD. {version} is the first 12 characters of the content SHA-256.

The operator’s content API serves a workspace’s content to the dashboard. It resolves paths as follows.

Request pathResult
arena/projects/{project}/…Alias for the caller’s own arena/workspaces/{project}/{user}/…. The clone is created on first use. Requires a verified user
arena/projectsRefused (403)
Anything under arena/workspaces/, arena/repos/, sandboxes/ or sessions/Refused (403)
.git inside a project pathRefused (403); also omitted from listings
Writes under arena/sources/Refused (403)
PUTReplaces the file atomically; an existing file keeps its permission bits
PUT onto a symlinkRefused (400)
PUT with neither If-Match nor If-None-MatchWrites unconditionally

Conditional writes use the file’s SHA-256 hex digest.

MechanismBehaviour
ETag: "{hash}" header and hash fieldReturned on file reads and PUT responses
?hashes=1 on a directoryAdds hash to each file entry
If-Match: "{hash}" on PUTWrites only if the current hash matches
If-None-Match: * on PUTWrites only if the file does not exist
Precondition failure412 with {"error": "precondition failed", "currentHash": "{hash}"}

Every write (PUT, DELETE and a move) holds a per-path lock across operator replicas, so a precondition holds whichever replica serves the request.

SituationBehaviour
Another write holds the path for more than about 2 seconds503 with Retry-After: 2
A write into a clone whose git index stays locked for about 2 seconds (a source-control verb, or git in the builder’s sandbox)409 workspace busy
The clone was removed as idle while the write waited409 workspace was removed; reopen the project
Lock files.content-locks/ at the root of the volume, outside every workspace; never listed. A lock older than 30 seconds is taken to be left by a replica that died, and is broken
ItemRule
Projects listThe ArenaProject objects in the namespace, not a directory walk
Display name, description, tagsRead from .project.json in the project’s head snapshot (status.repository.headContentPath) and overlaid on the ArenaProject object. A project with no head has none; a malformed file is ignored
Project deleted and recreated under the same nameStarts with an empty repository and no snapshots. The repository’s omnia-owner file and the snapshot tree each record the project’s UID, and a different UID discards what is there

A mount role’s path template is relative to {workspace}/{namespace}/.

RuleValue
Built-in rolesscratch, session
PlaceholdersExactly one whole segment {id}; at most one whole segment {user}
Forbidden to reach or containsandboxes/, sessions/, arena/sources/, arena/repos/, arena/snapshots/, arena/deployed/, arena/template-sources/, arena/template-indexes/, skills/, manifests/
OverlapThe paths of two roles may not overlap
SymlinksA mount directory, or a directory on the way to it, that is a symlink is refused
Chart defaultproject: arena/workspaces/{id}/{user}, mustExist: true, minRole: editor

See Workspace and SandboxClaim for the fields.

ContentRuleValue
Synced source versions (skills/, arena/template-sources/, sync-back targets)After each sync, versions beyond the most recent by modification time are removed. The version HEAD names is never removed, so a source may hold one directory more than the cap. If HEAD cannot be read, nothing is removed10; fixed in the operator and the Arena controller, no chart value
Project snapshots (arena/sources/{project}/)The Arena controller sweeps after each head export. It keeps the head snapshot, every snapshot pinned by an unfinished ArenaRun of the project, the newest others, and any snapshot younger than the grace period10 others; grace period 1 hour
Deployed packsA deploy compiles its snapshot at once, so a deployed pack pins no snapshotNone
User clones (arena/workspaces/)A clone idle past the idle period is deleted if it holds nothing its repository lacks, and flagged and kept otherwise14 days; --arena-workspace-idle-after, chart value enterprise.arena.controller.workspaceIdleAfter
Session directories (sessions/)Removed when session-api does not hold the session, or its AgentRuntime is deleted, and no claim of the session existsThree consecutive misses spanning at least 1 hour; checked every 10 minutes
Scratch directories (sandboxes/)Removed once no SandboxClaim of that name existsSame strikes and grace as sessions

The idle period restarts on status.workspaces[].lastActivity of the ArenaProject. The operator stamps it on a file write, move, create or delete through the project alias, on a source-control verb that changes the clone, and when a builder sandbox mounting the clone binds or is deleted while bound. Reads do not stamp it. See ArenaProject for the status fields.

ComponentAccessScope
Operatorread-writeThe whole share. Content API, SkillSource sync, PromptPack manifests, run and deploy snapshot export
Arena controllerread-writeThe whole share. Repositories, project heads, template sources, run snapshots, idle clones
Arena worker podsread-onlysubPath to one snapshot
Agent runtime containerread-onlyThe workspace subtree at /workspace-content, only when the PromptPack declares skills and the operator has a content path
Dev agent runtime (live PromptPack)read-onlyOne user’s clone at /workspace-source
Sandbox podsper the SandboxClass mount, ReadWrite by defaultOne claim’s scratch, session or mount-role directory at the class’s filesystem.workspace.mountPath

The dashboard does not mount the volume. It reads and writes through the operator’s content API.

ContentLocation
Session media and attachmentsThe media backend of spec.media.storage: for local, the PVC named by local.volumeClaim, mounted by the facade and the runtime; otherwise an object store
Arena run outputThe PVC named by output.pvc.claimName when output.type is pvc, or an S3 bucket
PromptPack source contentThe API server: a PromptPackSource creates an immutable PromptPack and a backing ConfigMap for each version
PathIf lost
arena/repos/Every commit of a hosted project is lost. An external project is fetched again from its remote
arena/workspaces/Each user loses what they had not pushed. A clone is created again from the repository on next use
arena/sources/Exported again by the next run or head export
arena/template-sources/, arena/template-indexes/Synced and indexed again
skills/Synced again from the SkillSource
manifests/Rewritten on the next PromptPack reconcile
sandboxes/, sessions/The files of the sandbox or session