Talking to a Running Agent
A sac agent is a long-lived process: once started, it stays alive
and accepts new turns. Three transports reach into that process,
ordered from external-friendliest to most internal:
Transport |
When to use |
Auth |
|---|---|---|
A2A sidecar — |
External tools, browsers, curl, peer agents, A2A-spec consumers |
none (loopback) |
CLI — |
Scripted flows on the same host (the one running the agent) |
none |
|
Trusted orchestrators (e.g. orochi), cross-host via the existing SSH mesh |
bearer token |
All three end up dropping a TurnEnvelope on the runner’s shared
inbox, so the SDK conversation is identical regardless of which door
the prompt came through.
1. A2A sidecar — POST /v1/sac/agents/<name>/turn
Enable it by leaving spec.a2a.port at its default (auto) — sac
claims a free port from ~/.scitex/agent-container/config.yaml’s
a2a.port_range (default [19000, 19999]) at start time and persists
it in state.db. Operators see the assigned port via sac agents list.
Most agents need no a2a block at all:
spec:
# a2a:
# port: auto # the default; sac picks + persists
# port: 7901 # pin only when you need a stable external URL
# port: null # disable the sidecar entirely
Per-agent ports are an internal IPC detail. External clients reach
every agent through the one stable host port at sac listen
(default 127.0.0.1:7878). The AgentCard’s url field advertises
that stable URL — so the URL survives restarts even when the
per-agent port churns.
The per-agent sidecar then exposes the same URL shape as the
host-level sac listen, so the same client URL works whether it
routes through the host control plane or POSTs directly to the
agent’s port. Per the sac/orochi contract, two symmetric namespaces
serve identical handlers:
Method |
Path |
Purpose |
|---|---|---|
POST |
|
Canonical. Drop a prompt onto the SDK session |
POST |
|
Alias of |
GET |
|
This agent’s AgentCard |
POST |
|
A2A-protocol-compat mirror of |
POST |
|
A2A-protocol-compat mirror of |
GET |
|
A2A-protocol-compat mirror of |
POST |
|
Bare shortcut (port already pins the agent) |
GET |
|
A2A discovery card (built from this agent’s |
GET |
|
Alias of |
GET |
|
|
The {name} segment is informational — port routing has already pinned
the request to one agent, so a name mismatch returns 404 {"error": "this port serves agent 'X', not 'Y'"} as a sanity check.
Send a turn
curl -s --max-time 120 -X POST \
http://127.0.0.1:7901/v1/sac/agents/ecosystem-auditor/turn \
-H 'Content-Type: application/json' \
-d '{"text": "Which scitex packages currently have audit-all violations?"}'
Or, since the port already identifies the agent, the bare shortcut:
curl -s --max-time 120 -X POST http://127.0.0.1:7901/v1/turn \
-H 'Content-Type: application/json' \
-d '{"text": "Same question, via the shortcut."}'
Response shape (either path):
{ "reply": "scitex-stats has 2 PS-204 violations; rest are green.",
"exit_after": false }
The runner stays attached after — subsequent POSTs reach the same SDK session (the conversation accumulates).
Discover the agent
The AgentCard at /.well-known/agent-card.json is auto-generated from
the agent’s spec.yaml (see spec-reference.md
for the field-to-card mapping). Browsers and A2A-spec discovery clients
read it directly:
curl -s http://127.0.0.1:7901/.well-known/agent-card.json | python3 -m json.tool
The card’s url field advertises <base>/v1/sac/agents/<name> —
exactly the path the sidecar serves — so a client following the
advertised URL hits a working endpoint.
One-shot vs follow-up
The HTTP body accepts an "exit_after" flag:
{ "text": "Reply DONE and exit.", "exit_after": true }
When true, the runner shuts down after this turn. Default is false
(stay alive for more turns).
2. CLI — sac agents send / sac agents tail
The same-host, no-network path:
# Send a turn — output is just the ack; the reply lands on session.jsonl
sac agents send ecosystem-auditor "Which packages have uncommitted changes?"
# Read the latest assistant turns
sac agents tail ecosystem-auditor -n 5
# Or stream as structured JSON (one envelope per line)
sac agents tail ecosystem-auditor -n 5 --json
This is the right transport for shell scripts driving an agent locally — no port, no JSON, no token.
3. sac listen — host-level HTTP control plane
sac listen boots a host-wide bearer-auth HTTP server (default port
7878, loopback only). Cross-host orchestrators reach it through the
existing SSH mesh; same-host orchestrators speak to it directly.
# Start the listen server (one per host; sac respects existing instance)
sac listen &
TOKEN=$(cat ~/.scitex/agent-container/tokens/listen-$(hostname).token)
# Send a turn through the control plane
curl -s -X POST http://127.0.0.1:7878/v1/sac/agents/ecosystem-auditor/send \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"type":"prompt","prompt":"Same question, via sac listen."}'
The full host-level surface (mirrored at /v1/a2a/... for A2A-spec
consumers):
Method |
Path |
Purpose |
|---|---|---|
GET |
|
|
GET |
|
List all agents on this host |
GET |
|
Full agent state |
GET |
|
SSE stream of |
POST |
|
Send prompt or interrupt key |
GET |
|
AgentCard for this agent |
POST |
|
Create + start from an inline spec |
DELETE |
|
Stop the agent |
send accepts both turn types:
// prompt turn
{ "type": "prompt", "prompt": "Your question here", "options": { ... } }
// key / interrupt
{ "type": "key", "key": "ESC" }
Cross-host: sac --on <peer>
sac --on <peer> agents send ... dispatches the call across hosts via
the peer registry’s SSH mesh. The remote sac does the local send;
output streams back. Same prompt-text contract.
sac --on gpu-box agents send researcher "Resume training and tail the logs."
Forwarding to external A2A (kind: AgentProxy)
Sometimes the agent on the other end isn’t a sac agent — it’s a
peer A2A endpoint hosted somewhere else (a hosted service, a peer
fleet, a contracted vendor). Wrapping it in a kind: AgentProxy
agent lets the rest of sac (sac agents send, sac listen, the
AgentCard discovery surface) treat it the same as a local SDK agent.
The proxy agent has no Claude SDK; it just forwards POST /v1/turn
to its configured spec.proxy.upstream and re-projects the
upstream AgentCard at its own /.well-known/agent-card.json so
peers see one consistent skill list.
apiVersion: scitex-agent-container/v3
kind: AgentProxy
spec:
runtime: apptainer
apptainer: { image: ~/.scitex/agent-container/containers/sac-proxy.sif }
proxy:
upstream: https://peer.example.com
trust: local-mesh
redact: [ANTHROPIC_API_KEY, sk-]
timeout_s: 30.0
a2a: { port: 7905 }
See spec-reference.md § kind: AgentProxy
and examples/agents/proxy-agent/
for the full reference.
Picking a transport
A2A-spec client / browser / curl → POST to the URL the AgentCard advertises (
<base>/v1/sac/agents/<name>/turn). Loopback only; no auth.Shell script on the same host →
sac agents send+tail. No port, no JSON, no token.Another agent, on the same host → A2A sidecar; agents have
httpxin the SIF. Use the canonical URL or the/v1/turnshortcut — both work.Orchestrator (orochi, custom) →
sac listen(port 7878) with the bearer token; same/v1/sac/agents/<name>/...path shape, cross-host via the existing SSH mesh.External A2A peer (hosted service, vendor) → wrap as a
kind: AgentProxyagent so the rest of sac treats it the same.
Pick the most external transport that meets your needs — every layer
above the inbox is a thin wrapper, so there’s no functional difference
once the turn lands. The URL shape is identical on both the per-agent
sidecar and sac listen, so the same client code works against either.
See also
spec-reference.md— the YAML knobs (spec.a2a.port,spec.listen.port)how-sac-works.md— the architecture diagram showing where each transport hooks insac-and-orochi.md— how orochi consumessac listenacross hosts