Architecture
A map of the code for people who want to change it. For what Circle does, read How Circle works first.
Circle is a TypeScript program for Node.js 24, under src/, compiled with strict checks. The agent loop is its own: there is no LangChain, LangGraph or deepagents. Model access goes through the openai and @anthropic-ai/sdk packages, MCP through @modelcontextprotocol/sdk, and sessions are stored with node:sqlite.
Entry points
| Module | What it does |
|---|---|
cli.ts |
Reads the command line (folder, @files, messages, options) and picks the full-screen interface, line mode, print mode, JSON events or RPC; also circle update. |
run_options.ts |
The options of one run that are not settings: tool limits, system prompt text, --models and the like. |
headless.ts |
Print mode, JSON events and line mode, and what they write to standard error. |
line_setup.ts |
Setup and the trust question asked line by line, for runs without the full-screen interface. |
rpc.ts, rpc_codec.ts |
RPC mode over standard input and output, and the messages and jobs as it sends them. |
exit_guard.ts |
Stops background jobs when the terminal closes or Circle is terminated. |
The agent
| Module | What it does |
|---|---|
runtime.ts |
Puts a session together: the model, tools, MCP servers, extensions, policy, system prompt and session store. Switches, forks and imports sessions, and reloads integrations. |
session_manager.ts, session_lock.ts |
Keep independent runtimes per conversation, coordinate deletion and shutdown, and hold heartbeat leases against concurrent writers in other Circle processes. UI selection does not own execution. |
harness.ts |
The loop: one model request, then the tool calls it asked for, until the model stops. Steering messages are read before the next request; follow-ups start a new turn when one ends. task calls next to each other run together, other calls one after another. |
system_prompt.ts |
Builds the system prompt from prompts/, the instruction files, skills, SYSTEM.md and APPEND_SYSTEM.md. |
memory_sources.ts |
Finds the instruction files (AGENTS.md, CLAUDE.md and the like) for a folder. |
skills.ts |
Finds skills in the built-in, user, project and .agents/skills folders and loads their text. The built-in ones are in builtin_skills/ beside the module itself, the lowest-priority source. |
commands.ts |
Custom commands: finds them and expands their templates. |
mentions.ts |
@file in a message (bare, quoted or with escaped spaces): attaches the file, completes paths for the input box, and reads a paste of dropped image files. |
events.ts |
The event bus. A subscriber that throws cannot stop the agent. |
types.ts |
Messages, tool calls, usage and the tool contract. |
testing.ts |
ScriptedModel, the model the tests drive. |
Models
| Module | What it does |
|---|---|
model.ts |
Talks to OpenAI-compatible and Anthropic-compatible endpoints and turns text, thinking, tool calls and usage into one form. |
tool_history.ts |
Keeps complete tool-result batches before internal mode transitions in outbound requests. Original checkpoints stay unchanged; missing or duplicate results and ordinary user input are not hidden or fabricated. |
model_guard.ts |
Retries by kind of failure, drops parameters an endpoint rejects, and catches stalled streams, repetition and a missing finish. |
text_repetition.ts |
Notices output that repeats itself, for model_guard.ts. |
tool_call_compat.ts |
Repairs tool names and arguments when the repair is unambiguous, and validates arguments before anything runs. |
model_media.ts, media.ts |
Images and PDFs: reading them, checking them and putting them into a request in each protocol’s form. |
image_fit.ts |
Scaling a large PNG down (pngjs, area averaging) before it is sent. |
screenshot.ts |
The screenshot tool’s capture: screencapture and a JXA window list on macOS, PowerShell on Windows, the first screenshot program that works on Linux. |
probe.ts |
Asks an endpoint for its models and works out its protocol and base URL, for setup and /models. |
model_catalog.ts |
Context windows, prices and whether a model reads images, from models.dev: the snapshot in data/models_dev.json.gz, refreshed into the data folder once a day. |
model_profiles.ts |
Thinking depths and other capabilities per model, from data/provider_profiles.json.gz. |
model_scope.ts |
Which models ctrl+p goes through. |
pricing.ts |
The cost of each call and the totals the footer shows. |
compaction.ts |
Shared operation snapshots, progress and persistent outcomes. |
request_budget.ts |
Model input/output reservations, complete provider-frame estimates and shared recovery allowances. |
compaction_engine.ts |
Content-only serialization, bounded parallel parts, complete coverage, cached nodes and atomic activation. |
compaction_store.ts |
Independent SQLite operation, attempt and summary-node records; branch-aware export and restore. |
context_middleware.ts |
What the model sees of a long session: large results kept in files, older messages summarized, older images left out, and the originals read back when needed. |
middleware/ |
Reminders added to a request: running and polled jobs (job_notice.ts), repeated tool calls (loop_guard.ts), the plan (plan_tail.ts), and pruning of old tool results (tool_result_prune.ts). |
Anthropic responses carry an opaque thinking_scope derived from the API URL,
key and requested model. When that identity changes, model.ts leaves historical
thinking and redacted_thinking blocks out of the outbound request, keeping
text, tool calls and results. Original checkpoint messages are unchanged. For
older or imported messages without a scope, an explicit HTTP 400 error rejecting
a thinking signature triggers one retry without historical thinking. The model instance
remembers fingerprints of the rejected signed payloads, so reused message ids do
not suppress different valid thinking. A
restart can need that recovery again for unscoped history. Other errors are not
handled as signature errors, and signature errors do not disable thinking.
Tools and safety
| Module | What it does |
|---|---|
tools.ts |
The built-in tools: files, execute, task, write_todos, jobs, plan mode, question, the web, skills and compaction. grep, apply_patch and lsp have modules of their own. |
grep_tool.ts |
The grep tool: literal search, its output modes and limits. |
apply_patch.ts |
The apply_patch tool. |
file_mutations.ts |
The file backend the tools may use: a file’s text and its revision, a batch of changes checked before any write, and a restore of all of it or none. |
file_mutations_fs.ts |
That backend on the host filesystem, which the terminal, line, print and RPC modes use; its read says what it cannot bring back byte for byte. |
file_rewind.ts |
The turn journal of the text tools: what a turn changed, the preview that reads every file again, and the checked restore. Its receipts live in circle.sqlite, so /rewind still knows a turn after a restart. |
file_read.ts |
Reads a window of lines without loading a whole file. |
lsp_tool.ts |
The lsp tool: language servers over standard input and output. |
websearch.ts |
webfetch, DuckDuckGo fallback requests and search-result HTML parsing. |
websearch_service.ts |
Runtime-owned native search capability cache, deadlines, normalized results and DuckDuckGo fallback. |
native_websearch.ts |
Provider search protocols, structured execution evidence, citations and auxiliary model usage. |
questions.ts |
The question tool and its answers. |
plan_file.ts |
Plan mode’s plan: where each conversation’s plan file is kept, and the choices plan_exit offers and how their answers read. |
secret_prompt.ts |
Secret requests: the files a task and Circle exchange, and writing the value into its target file. |
editor_bridge.ts |
The localhost MCP server an editor plugin talks to: what is open, what is selected, diff requests, and the write that follows an accept. Loopback only, token only, workspace only. |
memory_write.ts |
The dated lines Circle keeps for itself in MEMORY.md: write one, list them, take one back, under the length and read-size caps the prompt works with. |
approval_rules.ts |
The structured rules: {tool, pattern, effect} in your approvals.json and the project’s .circle/approvals.json, the layer that decided, and what a project may and may not grant. |
approvals.ts |
Sorts commands and tool calls, keeps the session’s rules and decides what needs your answer. |
subagent_files.ts |
The agent folders: <name>.md per subagent (keys name, description, tools, model, body the prompt), read from ~/.circle/agents, ~/.agents/agents, ~/.claude/agents and the project’s own, with what could not be used reported for /agents. |
hooks.ts |
Declarative hooks: hooks.json for you, a trusted project (re-approved when the file changes) and an optional skill, the shell protocol, and decisions that override the approval mode. |
sandbox.ts |
Resolves paths (including /conversation_history/, /large_tool_results/, /background_jobs/ and /session_plans/, which live in the data folder), refuses credential files, runs commands with secret variables stripped from their environment, and stops their process groups. It is a policy boundary, not an operating-system sandbox. |
redact.ts |
Hides passwords and tokens in command and job output. |
secrets.ts |
Variables and saved secrets written %NAME%: secrets.json, putting values into a call’s arguments after its approval, and turning a secret’s value back into %NAME% in what the model reads. |
browser/ |
The conversation browser. tools.ts has the browser_* tools, each action’s site for its approval and the handshake with the person. page.ts is the script that reads a page and moves the pointer. protocol.ts, format.ts, hosts.ts, certificates.ts and files.ts are what both browsers share. chrome.ts and cdp.ts drive Chrome, Edge or Chromium for the terminal; the desktop’s browser is apps/desktop/src/browser.ts. |
terminal/ |
The device terminals: SSH and Telnet consoles the model drives and the person can take over. tools.ts has the terminal_* tools and the bridge that carries them to the desktop’s main process; sessions.ts owns each conversation’s consoles (holder, serial queue, wait, the @xterm/headless screen). transport.ts is the connector seam, with ssh.ts and telnet.ts behind it: an unknown host key pauses the SSH handshake on its open wire and sessions.ts keeps that paused login for the person’s answer, so a “connect once” resumes the same handshake rather than paying for a second one (a wire that died or a wait that ran out falls back to logging in from scratch); protocol.ts fixes the wire shape, profiles.ts reads the person’s terminal-sessions.json and hostkeys.ts the trusted host keys. tui_screen.ts draws a device screen into the TUI’s rows (colours only from ink/theme.ts); keys.ts turns an attached keystroke into device bytes. The desktop hosts the same TerminalSessions in apps/desktop/src/terminal.ts. |
user_shell.ts |
Your own !command and !!command. |
Sessions and data
| Module | What it does |
|---|---|
checkpoint_store.ts |
circle.sqlite: sessions, their messages as a tree of checkpoints, branches, labels, context versions and one turn’s file changes for /rewind. Stored messages are never rewritten. |
session_graph.ts |
Checks that a session’s messages and branches fit together. |
session_export.ts |
/export and --export: Markdown, HTML and JSONL, and reading a JSONL export back. |
settings.ts |
settings.json and credentials.json: reading, project overrides in a trusted folder, and saving one change at a time. |
paths.ts |
The data folder and the paths inside it. |
keybindings.ts |
keybindings.json: the actions and their default keys. |
git_info.ts |
The folder’s git branch. |
Background work and integrations
| Module | What it does |
|---|---|
jobs.ts |
Background jobs: commands and subagents, their output files, their notices to the model, and stopping them. |
watch.ts |
api.Watch, which an extension tool returns to wait for something slow. |
schedule.ts |
Scheduled tasks: the store under schedules/, the clocks (every, cron, once), the run records and the per-schedule run lock. |
scheduler.ts |
Firing them: one headless circle -p in a child process per run, the tick loop, the daemon file. |
schedule_cli.ts |
The circle schedule command line. |
mcp_loader.ts |
Connects MCP servers and offers their tools under the same policy as built-in ones. |
extensions.ts |
Loads extensions and what they register: tools, commands, middleware, renderers and event handlers. |
net.ts |
The proxy and certificate settings for every request Circle makes itself. |
The terminal interface
src/ink/ draws: it reads keys and terminal reports, builds the palette, and turns state into rows. src/tui/ decides what is on screen and what a key does. The rules for both are in The TUI contract.
| Module | What it does |
|---|---|
ink/parse_keypress.ts |
Turns terminal input into keys, pastes, mouse events and colour reports. |
ink/theme.ts |
The palette, built from the terminal’s colours: text, tints, lamps and the rainbow. |
ink/theme_watch.ts |
Asks the terminal for its colours and follows a switch between dark and light. |
ink/screen.ts |
Writes only the rows that changed. |
ink/string_width.ts |
Display width, wrapping and cutting of text with colour codes. |
ink/selection.ts |
What a mouse selection covers on the painted screen. |
ink/clipboard.ts |
Copying: OSC 52, the system’s clipboard tool and tmux’s buffer. Pasting: a picture or copied image files from the system clipboard (ctrl+v). |
ink/components/dialog_card.ts |
The card in the input box’s frame: title with its lamp, body, options, input row. |
ink/components/approval_card.ts, question_card.ts, secret_card.ts |
The approval, question and secret cards and their keys. |
ink/components/loop_frame.ts |
The input box’s frame: rainbow, busy label and mode word. |
ink/components/picker.ts, popup.ts |
The lists above the input box, and the completion list’s rows. |
ink/components/plan_panel.ts |
The plan box. |
ink/components/markdown_renderer.ts |
Markdown in answers, thinking and records. |
ink/components/welcome.ts |
The welcome block and its logo. |
tui/session_app.ts |
The full-screen session: input dispatch, cards, lists, the terminal’s modes, and the commands that need its own state (/models, /login, /reload, /editor). |
tui/render.ts |
Lays out a frame: conversation, plan box, waiting messages, input box, footer and strip. |
tui/composer.ts |
The input box: editing, pastes, word keys, growth and the completion list. |
tui/input_history.ts |
The prompt history and ctrl+r. |
tui/slash_commands.ts |
The built-in commands, parsing, close matches, /help and /hotkeys text. |
tui/slash_handlers.ts |
What each built-in command does. |
tui/interaction_queue.ts |
One card at a time, the turn’s own before a background agent’s, none while you type. |
tui/approval_preview.ts |
What an approval card shows: the command, the file and its diff. |
tui/tool_rows.ts, tui/agent_rows.ts, tui/display_lexicon.ts |
Rows for tool calls, results, thinking and subagents, and the short names and phrases they use. |
tui/status_rows.ts |
The header, the busy label, the footer, the line under each turn, the compaction row and the window title. |
tui/strip_rows.ts |
The strip of subagents, jobs and live device terminals under the footer, and the band each of their pages wears. |
tui/turn_status.ts |
Per-turn time, tokens and thinking time, from the event bus. |
tui/welcome_state.ts, tui/folder_inventory.ts |
What the welcome shows: the folder’s own instruction files, skills, commands, extensions and settings, and its recent sessions. |
tui/subagents.ts |
Selecting a subagent in the strip and paging through records. |
tui/conversation_tree.ts |
The session as a tree, for /tree and /fork. |
tui/undo_history.ts |
What /undo and /redo put back on screen. |
tui/transcript_find.ts |
ctrl+f. |
tui/mouse_selection.ts |
Dragging, clicking and copying with the mouse. |
tui/external_editor.ts |
Which editor /editor runs. |
tui/exit_lines.ts |
What the terminal shows after Circle leaves: stopped jobs and the command that reopens the session. |
Install and update
| Module | What it does |
|---|---|
install_layout.ts |
Release archives and the install folder: versions/<version>/, current.ref, installation.json, checksums and pruning. |
install_manager.ts |
Installs a release folder; run by install.sh and install.ps1. |
install_processes.ts |
The processes running a program of an installation, which circle uninstall (uninstall.ts) looks for before it removes anything. Held Windows programs are matched by native process name and canonical path; an unidentified busy executable stops the removal. |
update.ts |
The daily update reminder and circle update. |
version.ts |
The version and version comparison. |
install.sh, install.ps1 and these modules share one layout; tests/install-layout.test.ts, tests/install-manager.test.ts and npm run release:smoke pin it. See Releasing.
Other folders
src/prompts/: the system prompts per model family, the tool descriptions, the subagent and summary prompts, and the/inittemplate.src/data/: the models.dev snapshot and the provider profiles, compressed.src/builtin_skills/: the skills that ship with Circle, one folder each (create-pr,pr-review).scripts/copy-assets.tscopies them todist/builtin_skills, and the desktop build (apps/desktop/scripts/runtime-assets.mjs) puts them beside its bundled runtime, insideapp.asaronce packaged. A release without them fails its check (install_layout.ts).scripts/: building and smoke-testing release packages, the release script, copying assets intodist/, andupdate-models-dev.ts(refreshessrc/data/models_dev.json.gz; run it before a release).tests/: one*.test.tsfile per area, run with Node’s test runner throughtsx. They driveScriptedModelor a local HTTP gateway and need no network.
This document is one of Circle’s design specifications, licensed under CC BY 4.0: quote or build on it with attribution to Circle and qingshanfeihu and a link to the original. See the design specifications index.