Skip to content

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 repl invocation creates an ephemeral session for the life of the loop: start, many turns, close on exit.
  • Persisted / named — given a name and recorded so you can leave and come back. resume and fork work 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.

claude-coder list
(no sessions)

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):

[{ "name": "claude-code-expert", "transport": "sdk" }]

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 idle and 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 claude version 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.