Projects and containers#
A project is a durable workspace, the persistent provider state declared by registered project-capable agent modules, and an LXD container that supplies their processes and tools. The durable directories survive container rebuilds; the container can be replaced.
Project creation#
The slug becomes the container name and is used in IDE and preview hostnames. Display names are unique case-insensitively after trimming whitespace; create and rename reject a duplicate with 409 Conflict. Distinct display names that normalize to the same slug receive -2, -3, and later suffixes.
Durable and replaceable parts#
Files in /workspace and every registered provider-state mount survive stop, restart, container deletion during upgrades, and image replacement. Provider homes preserve most provider-owned configuration, authentication, and session state. Antigravity persists only /root/.gemini/antigravity-cli, not the rest of /root/.gemini. Claude also uses /root/.claude.json outside its mounted home and relies on host credential synchronization to restore it. Ad-hoc packages or files elsewhere in the container root filesystem do not survive container replacement.
Lifecycle#
At backend startup, reconciliation compares stored status with actual LXD state and reapplies the default and per-project resource envelope.
Container launch contents#
Project container policy is module-driven. Only modules declaring the project execution scope contribute a provisioning profile. Each provider's local NewFactory() passes its Profile() separately from the public module descriptor; the shared catalog and provisioning services never reconstruct provider policy. The factory retains a defensive profile snapshot and passes independent exact validated clones to its shared project preparer and provider build callback. That profile owns the CLI install/repair spec, credential synchronization, persistent-state mounts, shared instructions, workspace-skill compatibility, non-secret runtime assets, and optional browser MCP templates. A project-capable module without a complete matching profile is rejected when the module catalog is built. Shared workspace provisioners converge all catalog-configured instruction and skill targets rather than only the currently selected provider's target.
Profiles may also publish non-secret runtime assets for the selected agent. MiniMax uses this path for its Codex model catalog; its Token Plan subscription key remains a project secret and is never written into that template.
The reusable Ubuntu 24.04 base image contains:
- Node.js 22, Git, SSH client,
jq, build tools, Python, and GitHub CLI. - Claude Code, Codex, Kimi Code, and Antigravity at pinned versions. MiniMax reuses the pinned Codex CLI with an isolated provider configuration.
- The Agent Browser stack and Chromium.
code-serverwith on-demand startup.
Launch-time provisioning then:
- Seeds or synchronizes registered agent credentials into project credential locations, primarily the durable provider homes.
- Links agent skill directories into the workspace.
- Publishes current browser scripts and browser skill.
- Applies browser process limits.
- Configures the project IDE.
When a prompt selects Scheduled Tasks, Remote also publishes the provider-neutral remote-schedule CLI and skill under /workspace before starting the provider.
These launch steps are best-effort so one optional capability does not prevent the container from starting.
Before each selected-provider run, shared preparation verifies that provider's CLI, converges instructions and its runtime assets, refreshes skill links, and prepares any requested Browser or Scheduled Tasks capability. Required steps fail the run with a specific preparation error.
Start and restart behavior#
Restart is host-driven and can recover a container whose internal processes are wedged. If the container is missing, restart delegates to the full launch path. A legacy container missing provider-home mounts may be started briefly to copy old provider state out, then stopped and reattached to the converged layout.
Secrets#
Project secrets are validated environment keys with an authoritative host-side store. They are also mirrored into the managed workspace .env file for project tools.
Adding or updating a secret commits the authoritative store, then attempts to write the managed .env file and update the container configuration. Multiline values are deliberately omitted from LXD environment configuration but are still passed to agent executions and written to .env. Deleting a secret removes the authoritative entry and attempts to remove each managed copy. Those propagation steps are best-effort and log rather than roll back on failure, so a stale copy can remain. Already-running processes retain their old environment until restarted.
Sharing#
- Project creators are added to the membership list automatically.
- Any current member can list members and add a registered user.
- Any current member can remove a user, but a non-admin cannot remove the final member.
- Administrators bypass membership checks and can always recover access.
Resources and inspection#
The project workspace page provides:
| Control or data | Details |
|---|---|
| Lifecycle | Start, stop, restart, and admin-only delete |
| Resource limits | CPU, memory, and root-filesystem disk overrides; admin-only changes |
| Runtime resources | Processes, CPU time, current/peak memory, swap, and disk usage |
| Container identity | Image, type, architecture, PID, creation, and last use |
| Network | Interfaces, addresses, traffic, MAC, and MTU |
| Operating system | Distribution, kernel, uptime, CPU count, and hostname |
| Agent state | Installed versions, instructions, and credential-bundle freshness |
| Recovery | Manual network repair and automatic IPv4 repair timer |
Fleet CPU, memory, and process defaults are reapplied best-effort: an application failure is currently ignored. Explicit project overrides fail the launch when they cannot be applied. The optional disk override limits the container root disk only; the host bind mounts for /workspace and provider homes have no project quota.
Port discovery#
The backend runs ss inside a running container and returns non-loopback TCP listeners on allowed ports from 1024 through 65535. The browser drawer turns a selected listener into a slug--port.dev URL.
Deletion#
The current backend deletion path removes the LXD container, project metadata, the host project root containing both workspace and agent homes, project secrets, and project access list. Chat records are stored separately; the project service does not currently cascade-delete chats that reference the deleted project.
Code map#
- Project policy:
backend/internal/service/project/service.go - Container lifecycle:
backend/internal/service/container/lifecycle/service.go - Project handler:
backend/internal/transport/http/handlers/project_handler.go - Project UI:
frontend/src/ui/projects/ProjectContainersPage.tsx