Container

Packages: container/, groupfolder/, mountsec/.

Lifecycle

Each agent invocation follows this sequence:

  1. EnsureRunning โ€” verify Docker is reachable via the daemon socket
  2. CleanupOrphans โ€” stop any stale arizuko-* containers left from a previous crash
  3. Resolve group path โ€” groupfolder.Resolver maps the group folder name to an absolute host path
  4. BuildMounts โ€” assemble the volume mount list (see table below)
  5. mountsec.ValidateAdditionalMounts โ€” check extra mounts against the allowlist
  6. seedSettings โ€” write settings.json to the session's .claude/ directory: env vars, arizuko MCP server config via socat, sidecar MCP entries
  7. seedSkills โ€” copy container/skills/ to the session on first run (non-destructive; skips files that exist). Also seeds .claude.json if missing (required by Claude Code; keyed by folder for a stable userID hash)
  8. StartSidecars โ€” launch any per-group MCP sidecar containers (docker run -d)
  9. docker run -i --rm โ€” start the agent container, write input JSON to stdin
  10. Stream output โ€” read stdout, scan for delimiter markers, capture output JSON
  11. Timer-based timeout โ€” graceful docker stop then Process.Kill if exceeded
  12. StopSidecars โ€” stop and remove sidecar containers after agent exits

Container run logs are written to groups/<folder>/logs/container-<timestamp>.log.

Volume mounts

Host pathContainer pathModePurpose
groups/<folder>//workspace/rwGroup working directory ($HOME)
groups/<world>/share//workspace/share/rwCross-group shared state within a world
data/sessions/<folder>//root/rwAgent session state (.claude/ dir)
data/ipc/<folder>//var/run/pub/arizuko/rwMCP unix sockets
container/skills//skills/roSkill CLAUDE.md files (read by agent)
groups/<folder>/media//workspace/media/rwReceived media files
web/ (optional)/workspace/web/roWeb assets (when web channel active)
extra mounts/workspace/extra/<name>/rw or roAdditional mounts from container_config

Input JSON schema

Written to the container's stdin as a single JSON line:

{
  "sessionId":     "session ID or empty for new session",
  "messages":      [ { "role": "...", "content": "..." } ],
  "systemPrompt":  "prepended XML blocks (diary, episodes, grants, etc.)",
  "grants":        [ "rule1", "rule2" ],
  "folder":        "group folder name",
  "senderJid":     "JID of the originating sender"
}

Output protocol

The container writes all output to stdout. The gateway ignores all lines until it sees the start marker, then captures until the end marker:

---ARIZUKO_OUTPUT_START---
{"status":"ok","result":"agent reply text","newSessionId":"...","error":""}
---ARIZUKO_OUTPUT_END---

During the run, the gateway polls the IPC socket for tool call results and pipes responses back via stdin. A _close sentinel on the IPC input channel signals the container to flush and exit.

Status values: ok (success, cursor advances), error (failure with output, cursor advances), fatal (no output, cursor rolls back).

Container naming

seedSettings

seedSettings writes settings.json into the session's .claude/ directory before each invocation. It contains:

seedSkills

seedSkills runs once per group (non-destructive). It copies the bundled container/skills/ tree into the session's .claude/skills/ directory. Files are skipped if they already exist, so operator customizations are preserved.

Skills versioning: the bundled skills carry a MIGRATION_VERSION file. On startup, the gateway compares the installed version against the bundled one. If the bundled version is higher, it applies the numbered migration files (NNN-desc.md) in order, which may overwrite specific skill files.

Sidecars

Per-group MCP sidecars are defined in GroupConfig.Sidecars. For each sidecar:

  1. StartSidecars runs docker run -d with the sidecar image, socket volume at ipc/sidecars/<name>.sock, memory limit, and CPU limit
  2. The socket path is wired into settings.json as an additional mcpServers entry
  3. StopSidecars calls docker stop then docker rm -f after the agent container exits

Security flags

--cap-drop ALL
--security-opt no-new-privileges
--memory 1g
--cpus 2
--network none          (default; overridable per group)
--read-only             (root filesystem; writable mounts are explicit)

Mount security

Package: mountsec/. Additional mounts from container_config are validated against ~/.config/pub/arizuko/mount-allowlist.json before the container starts:

Docker-in-Docker path translation

When the gateway itself runs inside Docker, container.hp() translates local paths to host-side paths. HOST_DATA_DIR provides the host-side base; HOST_APP_DIR maps the application directory. Volume mounts passed to docker run use the translated paths so the inner Docker daemon can resolve them.