Concepts¶
The mental model behind claude-coder: sessions, the fleet, transports, profiles, and the fail-closed posture.
claude-coder drives Claude Code the way a person would — your Claude (Max/Pro) subscription, never an API key. Everything below is built on a few small ideas.
Sessions¶
A session is one driven Claude Code conversation. You send it turns; it runs tools, streams text, and reaches a boundary. A session has a state, an id, and a transcript.
Sessions come in two flavors:
- Ephemeral — created, used, and closed in one process. A
replinvocation creates an ephemeral session for the life of the loop: start, many turns, close onexit. - Persisted / named — given a name and recorded so you can leave and come back.
resumeandforkwork with persisted sessions.
The catalog¶
The catalog is how persisted sessions are discovered and reattached. It is SDK-native: the session name (title) lives inside the Claude Code JSONL transcript, so there is no separate registry to drift. list reads it and history reads the transcript.
Note
Each session carries a state: starting, idle, busy, stalled (a recoverable "stuck busy"), closed, or error. idle means it is ready for the next turn.
The fleet¶
The fleet is named multi-session management. One manager runs several named sessions side by side under a concurrency ceiling, merges their events into one tagged stream, and persists a roster so sessions survive a restart.
The fleet is what the TUI and the in-process FleetApi drive. You address members by name (agent1, reviewer, …); fleet events are tagged with the session name so you can tell who said what.
Transports¶
A transport is the mechanism that actually drives Claude Code. There are two, and a session uses exactly one.
SDK (sdk) |
PTY (pty) |
|
|---|---|---|
| How it drives | Programmatic query() stream |
A real interactive TUI in a pseudo-terminal |
| Event fidelity | structured (exact usage, cost, tool calls) |
inferred (parsed from the screen) or raw bytes |
| Permissions | Same programmatic policy/modes, resolved in-protocol | Same policy/modes, resolved by keystroke-verified dialog injection |
| Pre-assign a session id | Yes | No (id is late-bound) |
Answer Claude's AskUserQuestion prompts programmatically (experimentalAskUserQuestionAnswering) |
Yes | No |
When to use which¶
| Use SDK when… | Use PTY when… |
|---|---|
| You want structured usage/cost data | You need exactly what the real TUI does |
| You drive permissions in code | You want the interactive dialog flow |
| Default. Most automation. | You are reproducing TUI-specific behavior |
SDK is the default. The only built-in profile, claude-code-expert, is transport: sdk.
Tip
Both transports strip credential env vars and pin MCP isolation identically. The choice is about fidelity and control surface, not safety — see Security.
Profiles¶
A profile is a named, reusable config recipe: which transport, working directory, model, permission mode, system-prompt append, env, and MCP servers a session starts with. You create a session from a profile by name. Profiles are defined in a JSON config file (or the single built-in, claude-code-expert):
Profiles support single-parent inheritance (extends) and { "secretRef": "..." } markers for env values resolved at session-create time. Full schema, inheritance, and secret injection live in Profiles and Secrets.
Turns, boundaries, and interrupts¶
You drive a session one turn at a time. A turn is one input and the work that follows it, up to a boundary.
- Turn — you send input; Claude thinks, runs tools, streams text. A blocking turn returns a
TurnResult; a non-blocking send returns a handle you can await. - Boundary — the turn ends: completed, interrupted, hit a limit, or errored. The session returns to
idleand is ready for the next turn. - Interrupt — you can stop a running turn mid-flight. The session settles at a boundary; optionally drain any queued input.
# In the repl, Ctrl-C interrupts the current turn; exit closes the session.
claude-coder repl claude-code-expert
This turn/boundary loop is the same whether you drive one session from the CLI, many from the TUI, or the library directly.
The fail-closed posture¶
claude-coder is fail-closed: when something is uncertain, it refuses rather than risks it. Five mechanisms enforce that posture:
- Auth invariant — the subscription-OAuth rule. A session driven on an explicit API key, base-URL override, or cloud-provider credential (
ANTHROPIC_API_KEY,ANTHROPIC_BASE_URL,AWS_BEARER_TOKEN_BEDROCK, and peers) is rejected at profile compile time and again at start. - Env strip — those same credentials are stripped from the child env as a last line of defense, even if one slips through.
- MCP isolation — driven sessions always get
--strict-mcp-config(PTY) /strictMcpConfig: true(SDK), so the child loads only the MCP servers claude-coder injects on purpose and ignores your machine's ambient servers. - PTY version band — the PTY transport gates the
claudeversion to[2.1.0, 2.2.0)and verifies each prompt's structure before any keystroke, so a drifted TUI gets no blind input. - Secret redaction — resolved secret values are redacted from every published event and diagnostic.
See Security for the full model.