Go sidecar service managing per-business bare Git repos for versioned artifact storage. Multiple Ghost agents for the same business all commit to one shared repo. BizApp reads history, diffs, and file trees via the Artifact System HTTP API.
Ghost Engine (pure control plane)
|
| HTTP tool calls
|
Toolbox-Devtools (repo-git: clone/commit/push via file://)
|
| file:// protocol
|
Shared PVC (/mnt/repos/)
|
Artifact System Sidecar (Go, :2369)
|
| HTTP API
|
BizApp Backend → Frontend
Three containers participate:
| Container | /mnt/workspace |
/mnt/repos |
Role |
|---|---|---|---|
| Ghost | -- | -- | Pure control plane; delegates all workspace/repo ops to devtools via HTTP |
| Toolbox-Devtools | rw (PVC) | rw (PVC) | Runs repo-git and coding agents; owns workspace and repo access |
| Artifact System | -- | rw (PVC) | Manages bare repos, serves read API, optional GCS backup |
The devtools toolbox handles the full git lifecycle: clone, edit, commit, push. Ghost never touches /mnt/workspace or /mnt/repos directly — it sends tool calls to the toolbox-server over HTTP.
Bare repos live on a PVC mounted at /mnt/repos/, one directory per business:
/mnt/repos/
├── templates/
│ └── default-business.git/
├── hysp/ ← business_slug = "hysp"
├── acme-corp/
├── megastore/
└── ...
repo_id = business_slug. Multiple agents for the same business all clone and push to /mnt/repos/{repo_id}/. Each agent's working copy is at /mnt/workspace/{agent_id}/artifact-repo.
Bare repos are created by the artifact-system (running as root) but pushed to by per-agent sandbox users in the devtools container. Both containers share the agents group with a fixed GID of 1500.
When the artifact-system creates or clones a repo, it:
- Sets
core.sharedRepository = groupin the bare repo's git config — this tells git to create new objects with group-write permissions - Recursively sets group ownership to
agents(GID 1500) and group-write + setgid permissions on all directories
On startup, the artifact-system also fixes permissions on all existing repos to handle repos created before the permission model was introduced.
The matching agents group exists in both Dockerfiles:
toolbox/artifact-server/Dockerfile:groupadd --gid 1500 agentstoolbox/devtools/Dockerfile:groupadd --gid 1500 agents
Per-agent users created by the toolbox-server belong to the agents group, so they can push objects into any bare repo.
- 10k repos (~5 MB avg): ~50 GB
- 100k repos: ~500 GB (SSD storage class recommended)
Each business repo follows a template-defined structure:
site/
├── header.md
├── homepage.md
├── about.md
├── footer.md
├── logo.png
└── favicon.png
branding/
├── guidelines.md
└── colors.json
legal/
├── privacy_policy.md
└── terms_of_service.md
metadata/
└── business.yaml
Templates live in architect/templates/. On startup the Artifact System commits each template directory into a bare repo at /mnt/repos/templates/{name}.git. New business repos are cloned from the template.
- Init: BizApp calls
POST /api/v1/reposwithrepo_id(= business_slug) → Artifact System clones template into/mnt/repos/{repo_id}/. - Checkout: Agent calls
repo-git checkout <repo_id>→ clonesfile:///mnt/repos/{repo_id}/into$AGENT_WORKSPACE/artifact-repo(i.e./workspace/artifact-repoinside the sandbox). Multiple agents for the same business all clone the same bare repo. - Work: Agent delegates to
cursor-agentorclaude-agentpointing at the working copy. Tools write files normally. - Commit: Agent calls
repo-git commit-push --message "..." --author "..."→ stages all, commits, pushes. If another agent pushed first, it rebases before retrying. - Read: BizApp queries the Artifact System API for tree, log, diff, file content.
repo-git is a bash script at toolbox/devtools/bin/repo-git that wraps git operations for the sandboxed environment. It runs inside the devtools sandbox under the per-agent user.
| Command | Description |
|---|---|
repo-git checkout <repo_id> |
Clone or pull bare repo into $AGENT_WORKSPACE/artifact-repo |
repo-git commit-push --message <msg> --author <name> |
Stage all, commit, push (rebase on conflict) |
repo-git status |
Show working copy status |
repo-git init <repo_id> [--template <name>] |
Create repo via artifact API (rarely used by agents) |
Agent workspaces and bare repos are on PVCs. Bare repos at /mnt/repos/ are owned by root but the sandbox runs commands under per-agent UIDs, triggering git's safe.directory ownership check. Pushing also requires write access to the bare repo's objects/ directory, handled by the agents group permissions described in Bare Repo Permissions.
Three mitigations work together to make git reliable in the sandbox:
The sandbox sets core.fileMode=false and core.sharedRepository=umask via GIT_CONFIG_COUNT environment variables. This prevents git from attempting to write core.filemode to .git/config during clone — the env value already matches what git detects, so the config write (and its lockfile fchmod) is skipped entirely.
The Dockerfile runs git config --system --add safe.directory '*' to write the setting into /etc/gitconfig. Git intentionally ignores safe.directory from environment variables and -c flags for security reasons — it can only be set in system or global gitconfig. This allows per-agent users to access bare repos owned by root at /mnt/repos/.
Bare repos are created by the artifact-system as root, but agents push as per-agent UIDs. The artifact-system sets core.sharedRepository=group and makes repos group-writable for the agents group (GID 1500). Without this, git push fails with remote unpack failed: unable to create temporary object directory.
The test script at scripts/tests/test-repo-git.sh validates the full repo lifecycle — create repo via artifact API, checkout, mutate, commit-push, verify push — against running toolbox-devtools and artifact-system containers. The toolbox --remote URL and artifact --artifact-api URL must point at containers that share the same /mnt/repos volume (e.g. make test-tool passes --remote http://localhost:9091 with --artifact-api http://localhost:2370 for docker/ci-tool-test.yml).
scripts/tests/test-repo-reseed.sh checks that the bare template templates/default-business.git matches the source tree under architect/templates/default-business (use RESEED_TEMPLATES=true so startup rebuilds the template from the mount). Run via make test-tool after docker/ci-tool-test.yml is up.
Agents learn which repo_id to use via shared knowledge (injected by BizApp when creating the agent). The Artifact System itself is repo_id-agnostic — it just stores and serves bare repos.
Base URL: http://artifact-system:2369/api/v1
| Method | Path | Description |
|---|---|---|
| POST | /repos |
Init repo from template (repo_id + optional template) |
| DELETE | /repos/{repoID} |
Delete repo |
| GET | /repos/{repoID}/status |
HEAD sha, last commit date, file count |
| Method | Path | Description |
|---|---|---|
| GET | /repos/{repoID}/tree |
File tree at ref (default HEAD) |
| GET | /repos/{repoID}/files/{path} |
Raw file content |
| GET | /repos/{repoID}/log |
Commit history (paginated) |
| GET | /repos/{repoID}/diff |
Diff between two commits |
| GET | /repos/{repoID}/commits/{sha} |
Single commit detail |
| Method | Path | Description |
|---|---|---|
| POST | /repos/{repoID}/rollback |
Revert to a target commit |
| POST | /repos/{repoID}/commit |
Programmatic commit (for UI edits) |
| Env var | Default | Description |
|---|---|---|
REPOS_DIR |
/mnt/repos |
Base directory for bare repos |
TEMPLATES_DIR |
/app/templates |
Source template files |
RESEED_TEMPLATES |
(off) | If true/1/yes/on, on each startup remove each templates/{name}.git under REPOS_DIR and recreate it from TEMPLATES_DIR/{name} so the bare template tracks the mounted source (local dev or GCS). Default keeps the one-time seed behavior. |
LISTEN_ADDR |
:2369 |
HTTP listen address |
GCS_BACKUP_BUCKET |
(disabled) | GCS bucket for periodic backups |
GCS_BACKUP_INTERVAL |
1h |
Backup frequency |
For working with external GitHub repositories (public or private), the github-repo toolbox tool clones repos directly from GitHub into $AGENT_WORKSPACE/repos/<name>/ over HTTPS or SSH. This is an alternative to the artifact system for use cases like open-source contributions, multi-repo projects, or any code not hosted on the platform. Both repo-git (artifact) and github-repo (external) can be enabled simultaneously via the archetype config.md tools list. See Tools — GitHub Repos for details.
- Agent Workspace — mount namespace isolation,
/workspacevirtual mount point - Toolbox Containers — toolbox-server API, multi-agent isolation
cmd/artifact-server/main.go
internal/artifact/
├── server.go # HTTP server + router
├── handler.go # API handlers
├── repo.go # Git operations (go-git)
├── storage.go # Flat directory management (per repo_id)
├── template.go # Template loading + repo init
├── gitops.go # Ghost working-copy helpers (clone/push via git CLI)
└── backup.go # Optional GCS backup