YAML Spec Reference (v3)
Container + session knobs nest under the engine that interprets them
(spec.apptainer.*, spec.claude.*). Cross-cutting knobs (workdir,
a2a, health, restart, autonomous, listen, skills, telegram, hooks)
stay at the top level. Every curated block has a raw_* escape hatch
— the full underlying surface is always reachable.
The agent name is the parent directory of spec.yaml (dir-as-SSoT —
no metadata.name field).
Quick links
Annotated full example:
examples/agents/full-agent/spec.yaml— every supported field with inline commentsMinimal example:
examples/agents/minimal-agent/spec.yamlQuickstart with
startup_prompts:examples/agents/hello-agent/spec.yaml
Top-level shape
apiVersion: scitex-agent-container/v3 # REQUIRED — v1/v2 raise loud validation errors
kind: Agent # REQUIRED — Agent | AgentProxy
# (AgentProxy → HTTP forwarder, no SDK;
# see spec.proxy + examples/agents/proxy-agent)
metadata:
labels: # drives `sac fleet` filters AND the AgentCard
role: ecosystem-auditor
team: lab-a
description: ... # → AgentCard.description
function: audit, git status, ... # → AgentCard.skills[0].description
capabilities: audit,health-check # CSV → AgentCard.skills[0].tags
cardinality: singleton # → AgentCard.x-scitex-agent-container.cardinality
spec:
runtime: apptainer # REQUIRED — only value accepted since 2026-05-13
workdir: ~/proj # mounted rw at /work
dot_claude: ./dot_claude # merged into <workdir>/.claude/ at start
python-venv: auto # string or list — fallback chain
env-file: .env # string or list of dotenv paths
multiplexer: tmux # tmux | screen
apptainer: { ... }
claude: { ... }
mcp_servers: { ... }
health: { ... }
restart: { ... }
autonomous: { ... }
a2a: { port: 7901 }
proxy: { upstream: https://peer/, trust: untrusted } # kind: AgentProxy only
listen: { port: 7878 }
skills: { required: [...] }
telegram: { ... }
hooks: { pre_start: [...], post_start: [...], pre_stop: [...] }
extensions: { ... } # opaque per-deployment dict
startup_commands: [...] # SHELL before claude starts
startup_prompts: [...] # TEXT fed to claude as first user msg
host: gpu-box # mutually exclusive: singleton on one peer
hosts: [laptop, gpu-box, nas] # OR multi-instance, one per peer
Field reference
metadata.labels → AgentCard fields
Note on naming — two “skills” concepts. A2A’s AgentCard has a standard top-level
skills[]array used to advertise capabilities to peers (id / name / description / tags / examples). Anthropic’s Claude Code separately uses “skills” for prompt-fragment markdown files under<HOME>/.claude/skills/<name>/that the SDK loads into the agent’s own context. Both share the English word but live at orthogonal layers:
Layer
Drives
Effect
metadata.labels.skills(CSV)A2A
skills[0].tags+x-scitex-agent-container.required_skillsAdvertises capabilities on the card; no behaviour change inside the agent
spec.dot_claude/skills/<name>/SKILL.md(files)Materialised at
runtime/<name>/home/.claude/skills/(ADR-0003) and surfaced viaspec.skills.required[]@-imports in the auto-generated CLAUDE.mdLoaded into the agent’s prompt by the Claude SDK
Also note A2A’s separate top-level
capabilitiesfield is for transport properties (streaming,pushNotifications, etc.) — not a synonym for “what the agent can do”. The “can do” surface is alwaysskills[].
The AgentCard at GET /.well-known/agent-card.json (per-agent sidecar
when spec.a2a.port is set) and GET /agents/<name>/card
(host-level sac listen) is built entirely from spec.yaml:
AgentCard field |
spec.yaml source |
|---|---|
|
parent directory of |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
derived from |
|
|
|
|
|
|
|
|
spec — top-level
Field |
Type |
Description |
|---|---|---|
|
|
Only value accepted; docker/podman were dropped 2026-05-13 |
|
path |
Mounted rw at |
|
path |
Materialized into |
|
string | list |
Pre-activated for startup_commands; |
|
string | list |
dotenv paths sourced at start |
|
string |
Container user override |
|
|
Long-lived session host |
|
string / list of strings |
Singleton on one peer / multi-instance one-per-peer (mutually exclusive) |
|
list of shell commands |
Run before Claude starts |
|
list of strings |
Fed to Claude as first user message(s) |
spec.apptainer — engine knobs
Field |
Type |
Description |
|---|---|---|
|
path to |
|
|
path |
Writable rw layer above the SIF |
|
|
Bind mounts. Source side supports |
|
key-value dict |
Env vars exported into the container |
|
bool |
Forward host NVIDIA / AMD ROCm libs (mutually exclusive) |
|
list of strings |
Escape hatch — appended verbatim to |
|
bool (default |
Opt OUT of hardened-by-default isolation. When |
|
bool (default |
Apptainer |
spec.claude — SDK knobs
Field |
Type |
Description |
|---|---|---|
|
|
Claude model |
|
|
Session strategy (default |
|
string |
Explicit session UUID for |
|
int |
Only resume if session.jsonl is newer than N minutes |
|
list of strings |
Extra flags appended to |
|
|
MCP push channels (passed as |
|
bool |
Auto-confirm permission prompts in the TUI |
|
dict |
Escape hatch — splatted into |
spec.health / spec.restart / spec.watchdog / spec.autonomous
Field |
Description |
|---|---|
|
bool — enable periodic liveness probe |
|
seconds between probes |
|
per-probe timeout |
|
|
|
|
|
int |
|
seconds before first retry |
|
cap on backoff |
|
exponential factor |
|
parsed for back-compat; lifecycle managed via hooks |
|
drive turns until |
|
string token Claude prints when done (default |
|
int |
|
nudge sent when Claude pauses |
spec.a2a / spec.listen — network endpoints
Field |
Description |
|---|---|
|
|
|
Override for the host-level |
The per-agent sidecar binds the same URL shape as sac listen
(/agents/<name>/{turn,send,card}, /v1/a2a/agents/<name>/...,
/.well-known/agent-card.json, /health), so the same client code
works against either transport. Per-agent ports are an internal IPC
mechanism between sac listen and the runner (different processes);
clients reach every agent through the one stable host port at
sac listen (default :7878).
The AgentCard’s url field advertises the sac listen URL
(http://127.0.0.1:7878/agents/<name>) regardless of which
endpoint served the card, so external A2A clients caching the card
get a URL that survives per-agent port churn.
~/.scitex/agent-container/config.yaml
Host-wide sac configuration. All keys optional; defaults shown.
listen:
host: 127.0.0.1 # bind interface for sac listen (loopback only)
port: 7878 # host control-plane port
a2a:
port_range: [19000, 19999] # range the auto-allocator picks from
Skills
spec.skills was removed in v3 — skills now live under
dot_claude/skills/ (a sibling directory next to spec.yaml,
materialized into the workspace at start).
For AgentCard publication, declare the skill IDs via
metadata.labels.skills as a CSV (e.g. skills: "scitex-dev, gh-cli, git").
The list ends up in the card’s skills[0].tags (unioned with
metadata.labels.capabilities) and x-scitex-agent-container.required_skills.
spec.mcp_servers
A dict-of-dicts merged into <workdir>/.mcp.json at start. Mirrors
the .mcp.json shape directly. Use this OR drop a .mcp.json into
dot_claude/ — both are merged.
spec.telegram / spec.hooks / spec.extensions
Field |
Description |
|---|---|
|
bool — enable alerting bridge (consumed by claude-code-telegrammer) |
|
Telegram chat ID |
|
Shell commands before |
|
Shell commands after the runner reports ready |
|
Shell commands before SIGTERM |
|
Opaque dict — read by downstream tooling (priority, owner, etc.) |
Lifetime / session selection
Default = long-lived + safe-fallback session continue. The sac agents start CLI overrides at start time:
sac agents start <name> --one-shot # exits after first startup_prompt
sac agents start <name> --session continue # default (try continue, fall back to fresh)
sac agents start <name> --session new-session # force fresh
sac agents start <name> --resume <sid> # implies --session resume
CLI flags ALWAYS override the YAML — one-direction precedence so a per-invocation tweak doesn’t mutate the persistent default.
kind: AgentProxy — HTTP forwarder agents
A proxy agent forwards POST /v1/turn to an external A2A
endpoint instead of running a Claude SDK conversation in-process.
There is no SDK in the container; the runner is a thin Starlette
forwarder (image: sac-proxy.sif, lighter than sac-scitex.sif —
no Python ML stack).
Authoring contract:
kind: AgentProxy(instead ofkind: Agent).spec.proxyis REQUIRED.spec.claude,spec.startup_prompts,spec.startup_commandsare rejected at validation time (no SDK to configure / prompt).spec.a2a.portworks the same — that’s the port operators POST to.
spec.proxy reference
Field |
Type |
Default |
Notes |
|---|---|---|---|
|
string (REQUIRED) |
— |
Full URL to the upstream A2A endpoint (must start with |
|
enum |
|
|
|
list[str] |
|
Substring tokens; any inbound |
|
float > 0 |
|
Per-turn upstream HTTP timeout. Longer forwards return HTTP 504 to the caller. |
Security notes
Proxy is HTTP-only — no mTLS in the MVP (the
trustedlevel is reserved for future work).Default trust is
untrusted; operators must opt in to anything more permissive.Egress lockdown is application-layer: a 3xx redirect from upstream to a different host is rejected with HTTP 502. The MVP does not enforce an apptainer
--netpolicy.Runs in
sac-proxy.sif— seecontainers/sac-proxy.def.
See examples/agents/proxy-agent/spec.yaml
for a complete minimal example.
Examples
Copy from examples/agents/:
full-agent/— annotated spec exercising every supported field (plusdot_claude/layout)minimal-agent/— bare minimum, nodot_claudehello-agent/— quickstart withstartup_promptsproxy-agent/—kind: AgentProxyforwarder example