Architecture
The flow
- Your agent implements an
AgentSpecand builds a config. buildSessionOptions()turns them into the SDK'sOptions: the system prompt, the tools the configuration grants, the kit's own MCP servers (approvals, manual intervention, save to sources), the security hooks, plugins and subagents.- A runner starts the session:
runChatInk()orrunChatTui()for a conversation,runQuery()for anything else. They wrap the SDK'squery(). - The SDK runs the Claude Code CLI as a subprocess. The CLI calls the model, runs the tools and calls the kit's hooks before and after each tool call.
runQuery()turns the SDK's raw message stream into a small set ofAgentEvents, which the terminal UI draws or your own host forwards.
Two layers
The kit is split so the same agent can run in a terminal or inside another program:
| Layer | Folder | Rule | What's in it |
|---|---|---|---|
| Core | src/core/ | Never touches console, process.stdout or readline | AgentSpec, buildSessionOptions(), runQuery(), hooks, the kit's MCP tools, the knowledge base, runs, languages, the interaction port |
| Terminal UI | src/tui/ | The only code that assumes a terminal | The Ink chat, the plain chat, the progress view, the wizard, ensureClaudeAuth(), the terminal interaction port, the palette and themes |
Both are exported from the package root; nothing forces a desktop app to load the terminal UI's code paths, and nothing in the core prints anything. A checkpoint (an approval, say) reaches the person through an InteractionPort: the terminal installs its own by default, a desktop app installs one that opens a dialog.
Key pieces
| Piece | Kind | Page |
|---|---|---|
AgentSpec<TConfig> | interface you implement | AgentSpec |
BaseSessionConfig | config you extend | Session config |
Mode | "interactive" | "guided" | "autonomous" | Modes |
buildSessionOptions() | builds SDK Options | Session options |
createInputQueue() | the multi-turn input of a chat | Events |
runQuery(), AgentEvent | the normalized event stream | Events |
runChatInk(), runChatTui() | chats | Ink chat, Plain chat |
createProgressView(), createConsoleRenderer() | one-shot runs | Progress view |
runWizard() | questions before the agent starts | Wizard |
InteractionPort, askForDecision() | human in the loop | Interaction port |
createRunStore(), listRuns() | conversations kept in run folders | Runs and resuming |
setLanguage(), setTheme() | the process's language and colors | Languages, Themes |
Design principles you'll notice
- Nothing domain-specific in the kit. Agents bring their domain through
AgentSpecand their own config type. - Guardrails live in hooks, not in prompts. A prompt can be ignored; a
PreToolUsehook that denies a call can't. See Security. - SDK behavior confirmed by hand. Several choices exist because the SDK behaves in ways its documentation doesn't say. They're collected in SDK behaviors.
- Features that grant Bash are opt-in. Bash can reach any path, so the kit never gives it by default and never to the main agent.
The design decisions are recorded as ADRs in the repository: .minispec/decisions/.