Skip to content

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.

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.

{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.

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 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.skills while the operator was started without it, the reconciler stops and says so, naming the fix: the chart value is workspaceContent.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}.json is 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.

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 resolvedVersion still identifies what it used, but the bytes may be gone.

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
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.

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.

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.