Workspace content layout
Omnia keeps per-workspace files on a shared workspace content volume. Arena projects, skills, and the versioned bundles that runs and agents read all live there.
Most of it is re-derivable. A small part of it is not, and losing that part loses authored work. This page says which is which, because the difference is not visible from the directory names.
The root
Section titled “The root”Everything is namespaced by workspace and Kubernetes namespace:
/workspace-content/{workspace}/{namespace}/The prefix is not always present in paths you will see in Kubernetes objects. Two mount modes exist:
| Mode | Volume root | Path recorded in objects |
|---|---|---|
| Legacy (shared volume) | the whole share | includes {workspace}/{namespace} |
| Scoped (workspace-scoped volume) | the {workspace}/{namespace} subtree |
workspace-relative |
Scoped mode is storage-enforced isolation — an Azure Files NFS PV per workspace,
for example — and is the reason a contentPath sometimes looks like it is
missing its prefix. It is not; the volume root already is the prefix. See
Isolate workspace content.
Everything on the volume
Section titled “Everything on the volume”{workspace}/{namespace}/├── arena/│ ├── projects/{project-id}/ authored, mutable│ ├── snapshots/{project-id}/.arena/versions/{v}/│ ├── {source-name}/.arena/versions/{v}/│ ├── template-sources/{name}/.arena/versions/{v}/│ └── template-indexes/{source-name}.json generated index├── skills/{source-name}/.arena/versions/{v}/└── manifests/{promptpack-name}.json generated index| Path | Written by | Authoritative |
|---|---|---|
arena/projects/{id}/ |
the dashboard project editor | yes |
arena/snapshots/{id}/ |
creating a project version | no |
arena/{source-name}/ |
the ArenaSource controller | no |
arena/template-sources/{name}/ |
the ArenaTemplateSource controller | no |
arena/template-indexes/{name}.json |
the ArenaTemplateSource controller | no |
skills/{source-name}/ |
the SkillSource controller | no |
manifests/{pack}.json |
PromptPack skill reconciliation | no |
Exactly one of these is authored. Everything else is fetched, generated, or snapshotted, and rebuilds itself.
Two shapes
Section titled “Two shapes”Working copies — arena/projects/
Section titled “Working copies — arena/projects/”Plain directories. This is where the dashboard’s project editor reads and writes, and the only mutable content on the volume. Projects arrive three ways: created empty, rendered from a template, or copied from an existing artifact.
.project.json carries the project’s name, description, tags and creator. For a
project rendered from a template it also records which template produced it.
This is the directory to back up.
Versioned artifacts — .arena/versions/{version}/
Section titled “Versioned artifacts — .arena/versions/{version}/”Everything else that holds content uses one shape, because everything else goes
through the same syncer: numbered, read-only snapshots with a HEAD marker
naming the current one. The .arena directory name is used for skills too — it
is the syncer’s convention, not an Arena-specific one.
They are immutable on purpose. A run pins the version it executes against — the
worker pod mounts that specific version directory by subPath — so editing a
materialised bundle in place would change what a running job sees. Content that
needs editing belongs in arena/projects/.
Skills
Section titled “Skills”Skills are the one subtree read by agent pods rather than by Arena, so they are worth understanding separately.
A SkillSource fetches and syncs. Given a git, oci or configmap
source, the controller materialises it at skills/{source-name}/.arena/versions/{v}/
— the same versioned shape, the same retention. spec.targetPath overrides the
directory; it defaults to skills/{source-name}.
PromptPack skill reconciliation resolves and publishes. This is the step
that turns synced files into something an agent can load, and it both reads and
writes the volume. It is one part of the PromptPack reconciler — reconcileSkills
— not a separate controller, so it runs whenever the pack does.
A pack declares the skills it wants in spec.skills. On reconcile, the
controller resolves those names against what the SkillSources have synced under
skills/, then writes the result to manifests/{pack-name}.json — an index
naming each resolved skill and where its files are. The write is atomic (temp
file, then rename), so a reader never sees a half-written manifest.
Resolution sets three conditions on the PromptPack, and they are the diagnostic path when skills do not appear:
| Condition | False when | Reason |
|---|---|---|
SkillsResolved |
a declared skill is not in any synced source | LookupFailed |
SkillsValid |
two sources provide the same skill name | NameCollision |
SkillToolsResolved |
a skill allows a tool the pack does not declare | UnknownTool |
Two behaviours worth knowing:
- Skills need workspace content storage to be on. If a pack declares
spec.skillswhile the operator was started without it, the reconciler stops and says so, naming the fix: the chart value isworkspaceContent.enabled, and the operator needs a restart after changing it. - Writing the manifest is best-effort. A failed write is logged, not
fatal — the conditions above are still set from what resolution found. So a
pack can report all three conditions healthy while
manifests/{pack}.jsonis absent or stale, and the agent simply loads no skills until the next reconcile succeeds. Check the file exists, not just the conditions.
The agent’s runtime container reads the manifest. It mounts the workspace
content PVC read-only at /workspace-content and reads
manifests/{pack}.json to find its skills.
That mount is conditional, and deliberately so:
- the operator must have a workspace content path configured, and
- the referenced PromptPack must declare at least one skill
Otherwise the agent pod does not mount the volume at all. Clusters that do not use skills therefore run agents without needing a workspace content PVC to exist — which is why an agent can be perfectly healthy on a cluster where this volume is absent.
Version retention
Section titled “Version retention”The syncer garbage-collects old versions per source, keeping the most recent 10. This applies to every versioned subtree — Arena sources, template sources, project versions and skills alike. It is currently a compile-time constant, not a Helm value.
Two consequences worth planning for:
- Capacity. Each source can hold up to ten copies of its bundle. Ten versions of a large bundle across many sources is the term that grows.
- Old versions disappear. A run that pinned a version GC has since removed
cannot be re-executed against the same content. The run’s recorded
resolvedVersionstill identifies what it used, but the bytes may be gone.
What is not on this volume
Section titled “What is not on this volume”Worth knowing, because these are the things people look for here and do not find:
| Content | Where it actually lives |
|---|---|
| Session media and attachments | a separate media-backend PVC, mounted by facade and runtime |
| Arena run outputs | the output PVC named by the run’s output.pvc, if configured |
| PromptPack source content | not on any volume — PromptPackSource creates PromptPackVersion objects in the API server |
Who mounts it
Section titled “Who mounts it”| Component | Access | When |
|---|---|---|
| Operator — content API | read/write | serves every dashboard read and write |
| Operator — reconcilers | read/write, directly | SkillSource and ArenaSource sync here; PromptPack skill reconciliation writes manifests |
| Arena worker pods | read-only, subPath to one version |
per run |
| Agent runtime containers | read-only | only when the pack declares skills |
The dashboard does not mount this volume. It reads and writes through the operator’s authenticated content API, which resolves the workspace’s namespace and confines access to it.
What to back up
Section titled “What to back up”| Path | Authoritative? | If lost |
|---|---|---|
arena/projects/ |
yes | authored projects are gone |
arena/snapshots/ (or arena/deployed/) |
no | re-create a version from the project |
arena/{source-name}/ |
no | re-syncs from git / OCI / the ConfigMap |
arena/template-sources/, arena/template-indexes/ |
no | re-syncs and re-indexes |
skills/ |
no | re-syncs from the SkillSource |
manifests/ |
no | rewritten on the next PromptPack reconcile |
Backing up the whole volume is mostly backing up re-derivable snapshots. Backing
up arena/projects/ is the part that matters.
Troubleshooting
Section titled “Troubleshooting”A run used content I did not expect. Runs pin a version at pod creation.
Check the run’s resolvedVersion and compare it to HEAD under the source’s
.arena/versions/ — a version created after the pod started will not be picked
up, which is the intended behaviour.
A project is missing from the projects list. The list reads directories
under arena/projects/. Content fetched by an ArenaSource is not there and is
not editable; it is a materialised bundle, not a working copy.
An agent has no skills. Work down the chain. Does the PromptPack declare
spec.skills, and was the operator started with workspace content storage
enabled? Without both, the pod does not mount the volume at all. Then read the
pack’s SkillsResolved, SkillsValid and SkillToolsResolved conditions.
Finally check manifests/{pack}.json actually exists — the write is
best-effort, so healthy conditions do not guarantee a manifest.
A path looks like it is missing its workspace prefix. See the two mount modes above — under scoped volumes the prefix is the volume root.
Related
Section titled “Related”- ArenaSource —
targetPath, and thetype: workspacesource a project version creates - SkillSource — fetching and syncing skills
- Use skills — declaring skills on a PromptPack
- Isolate workspace content — scoped volumes and per-workspace enforcement
- Multi-tenancy architecture