Internals
This will be a guide to how FreeCode is made — each component explained in a beginner-friendly way, complete enough to know how real-world production AI agent harnesses work.
This section is the architecture, not the usage. It follows one request all the way down: a prompt enters the loop, becomes a streamed provider call, comes back as tool calls, gets gated by permissions and hooks, executes, and folds back into the next turn — while the bus narrates the whole thing to your screen.
The thin-client boundary
TUI, VS Code, Web, and the desktop app are pure presentation layers — they
render and speak IPC, nothing else. Every frontend talks to apps/core over
the same JSON-RPC protocol (session.send, tools.call, …), so a bug fix or
a new capability written once in core reaches all four frontends without
touching any of them. It also means core has no UI to leak into: it can be
driven headlessly (CI, freecode eval, a script) with zero frontend code
loaded at all. See IPC protocol for the wire format.
Where each subsystem lives
Everything under this section maps to a directory in apps/core/src/:
| Section | Directory |
|---|---|
| IPC protocol | server.ts, packages/shared/src/ipc/ |
| Agent loop | agent/ |
| Provider layer | providers/ |
| Tool system | tools/ |
| Context engine | context/ |
| Event bus | bus/ |
| Lifecycle hooks | hooks/ |
| Permission engine | permission/ |
| Sub-agent runtime | agent/subagent.ts |
| Memory | memory/ |
| Knowledge graph | memory/graph/ |
| Sessions, store & rollout | session/, store/, rollout/ |
| Compaction | compaction/ |
| Effect runtime & DI | effect/ |
| Eval harness | eval/ |
| MCP client | mcp/ |
| Skills system | skills/ |
| CLI entrypoint & subcommands | cli/, cli.ts |
| Slash commands | commands/ |
| Usage accounting | usage/ |
| Repo map | repo-map/ |
| Claude Code session import | claude-sessions/ |
| Graph explorer addon | graph-explorer/ |
| Shared utilities (no page yet) | utils/ |
| Autonomous runs — Phase 0 only, nothing executes yet | autonomous/ |
Each page follows the request through its subsystem with references to the actual source files, not just a description of the concept — the goal is that you can read a page, then go straight to the file it names.
The invariants the codebase holds to
A handful of rules recur across every subsystem in this section, and are worth knowing before diving into any one page:
- Frontends have zero business logic. No provider calls, no file
reading, no tool execution outside
apps/core. - One egress, one bridge. The event bus has exactly one
subscribeAllwriter per process — adding a second silently double-prints every event. - Fail closed. An unclassified tool is mutating (permissions), an unconfigured eval judge closes the gate rather than passing quietly (eval), a denied tool call still leaves a trace (agent loop).
- DI over singletons. Services are resolved through Effect layers, not imported as module-level singletons, so tests can swap a whole dependency graph at once.
- A denial is not silence. A refused tool call is its own event type
(
function.denied), never absent — see Agent loop §“Denials” for why folding it into normal tool spans breaks several consumers downstream.