How sac works
scitex-agent-container (sac) materializes a spec.yaml into a long-lived,
externally addressable agent process — whichever harness drives its turns.
Launch flow
spec.yaml ─┐
to_home/ ─┴─→ sac agents start ──→ apptainer instance
│
▼
long-lived harness session (TUI or SDK)
│
├── $HOME (= runtime/<name>/home/, bind-mounted)
│ e.g. CLAUDE.md / .mcp.json / .env / state.md ← from to_home/
│ e.g. .claude/{commands,skills,hooks,...} ← from to_home/.claude/
│
├── spec.mounts[] ← explicit host-path allowlist (ro/rw)
│
├── state-dir (host: ~/.scitex/agent-container/runtime/<name>/)
│ pid, heartbeat.json,
│ session.jsonl, session_id, quota.json
│
├─→ POST /v1/turn (per-agent A2A inbound)
│ ▲
│ │ live-runner route
│ │
sac listen :7878 ───────────┼───────┘
bearer-auth /v1/sac/{ \
health, agents, ─→ claude --resume <sid> -p
agents/<n>/{status,send,card}, (re-launch fallback when
... no live runner)
}
▲
sac channel send TO MSG ─────────────────────────────────┤
sac peer post-turn AGENT TEXT ────────────────────────┘
What each piece does
spec.yaml (SSoT)
The single file that fully defines an agent. The agent name is the name of its parent directory — no name field in the YAML. See spec-reference.md.
to_home/ (optional)
A directory next to spec.yaml. At start, sac mirrors its contents into the
agent’s container $HOME (= runtime/<name>/home/). Every path under
to_home/ lands at the same relative path under $HOME — the mirror itself is
harness-agnostic. AGENTS.md is the neutral instruction source. For Hermes,
SAC verifies the projected bytes and passes them through Hermes’
agent.system_prompt configuration. It never translates CLAUDE.md silently:
Source |
Destination |
Merge rule |
|---|---|---|
|
|
exact projection |
|
|
marker-protected projection |
|
|
full overwrite |
|
|
mode 0600, overwrite |
|
|
marker-protected append |
|
|
recursive copy |
Every launch writes $HOME/.scitex/agent-container/runtime/prompt-projections.json from the executable
materializer. It records SHA-256 provenance for the explicit startup prompts,
declared instruction/skill sources, and effective runtime projections. Source
changes during assembly or a projection that no longer matches the manifest
abort launch; documentation is not used as a runtime fallback.
Hermes launch also fails if the manifest has no verified root AGENTS.md, if
the home backings disagree, or if its recorded hash no longer matches.
Apptainer instance
sac agents start calls apptainer exec with:
the SIF at
spec.apptainer.image<workdir>bound rw at/workany extra binds from
spec.apptainer.binds[]env vars from
spec.apptainer.envoptional GPU passthrough (
--nv/--rocm)
Harness session
Inside the container, sac launches the harness — the agent program that
drives one turn. sac owns the process, its lifecycle, and its addressability;
the harness owns only the turn. Session state persists in the host-side state
dir so it survives container restarts.
The harness is selected by spec.harness (family) plus spec.runtime (launch
mode within that family). Four harnesses are registered; only the anthropic
ones can be started today. A registry entry is a declaration, not a working
launch path, so the table says which is which:
|
Registry entry |
Selected by |
Process shape |
|
|---|---|---|---|---|
|
|
|
external |
yes |
|
|
|
sac-hosted session runner |
yes |
|
|
the harness axis alone |
sac-hosted session runner |
no — refused |
|
|
the harness axis alone |
sac-hosted session runner |
no — refused |
spec.runtime only discriminates within the anthropic family; the openai
and codex families have one entry each, so the runtime axis selects nothing
for them.
Honest limit today: a non-anthropic harness loads, validates and resolves
to its registry entry, but every lifecycle launch path refuses it — loudly,
rather than silently starting a Claude harness under a spec that asked for
something else. The one working alternative is spec.a2a.handler: openai_session for the OpenAI SDK; there is no equivalent A2A executor for
codex yet.
spec.claude.session controls whether to start fresh (new-session), continue
the last session (continue), or resume a specific one (resume <sid>) for the
Claude-family harnesses.
A2A inbound (spec.a2a.port)
When spec.a2a.port is set, sac binds POST /v1/turn on that localhost port
for this agent. Any process on the host can send a prompt without knowing the
tmux pane — including other agents via sac peer post-turn.
sac listen (control plane)
A per-host REST API (bearer-auth, loopback-only) that exposes fleet-wide
operations: health checks, agent status, send, agent-card, and more.
sac channel send routes through it for local agent-to-agent messaging.
Restart / health
The runner supervisor checks spec.health probes and applies spec.restart
policy (never / on-failure / always) with exponential backoff.
Heartbeat state is written to runtime/<name>/heartbeat.json every tick.
For resident Hermes agents, heartbeat.json.authoritative_heartbeat is the
privacy-safe native projection: stable agent/spec/engine/model/session/boot
identities, monotonic sequence/time, progress checkpoint, bounded host lease,
and optional Cards developer/reviewer lease. It never carries prompts,
responses, tool payloads, account names, or credentials. The owning host also
advances the matching record in the explicitly shared PostgreSQL instances
store (SCITEX_STORE_DSN is mandatory); the append-only heartbeat diary remains
terse and per-host. Lease age uses the Store HLC observed by readers, not the
publisher’s wall-clock expiry. Fleet
readers reject future, duplicate, out-of-order,
spoofed, and overlapping cross-host leases before classifying a resident as
idle, active, blocked, stalled, disconnected, or dead.
See also
spec-reference.md — full field reference
directories.md — directory tree + config cascade
images.md — Apptainer image management