Skip to content
Documentation

Architecture

The layers of the harness and what each one owns.

Forge is a harness around a model, and the harness is the part that matters. The model proposes; the layers below decide what actually happens, and stay in charge when the model is wrong.

                        ┌─────────────────┐                        │    Developer    │                        └────────┬────────┘                                 │  one task, one sentence                        ┌─────────────────┐                        │    Forge CLI    │   flags · config · REPL                        └────────┬────────┘                        ┌─────────────────┐                        │  Agent runtime  │   the loop, until done                        └────────┬────────┘            ┌────────────────────┼────────────────────┐            ▼                    ▼                    ▼       ┌─────────┐         ┌──────────┐         ┌──────────┐       │ Context │         │  Tools   │         │ Provider │       └────┬────┘         └────┬─────┘         └────┬─────┘            │                   │ risk-gated         │            ▼                   ▼                    ▼      conversation        ┌──────────┐          model call      + compaction        │  Policy  │                          └────┬─────┘                    filesystem · shell · git                         Repository

What each layer owns

CLI

Parsing flags, resolving configuration from five sources, and choosing between a session and a one-shot run. Owns the exit code.

Knows nothing about tools or prompts.

Agent runtime

The loop. Sends the conversation to the provider, receives tool calls, dispatches them, appends results, and decides whether the task is done.

Never touches the filesystem itself.

Context

The conversation, the environment summary gathered at startup, and compaction when the window fills.

Holds no state between runs.

Tool registry

Names, typed schemas, risk levels, and handlers. One interface, whatever the tool does.

Does not decide whether a call is allowed.

Permission policy

Deny matching, escalation, allow-lists, and the mode lookup. Every call passes through it.

Cannot be bypassed by a tool or a prompt.

Provider

Turning a conversation into a model call and a reply into tool calls. Anthropic today, plus a fake for tests.

The only layer that makes a network request.

The loop

One iteration is: send the conversation, receive tool calls, run them through the policy, append the results, repeat. It ends when the model stops asking for tools, or when the iteration ceiling is reached.

01UnderstandThe task arrives as one sentence, resolved against the repo you started in.gather_environment()
02ExploreIt reads the layout, the toolchain, and the git state before touching anything.read_file · list_directory · search_files
03PlanIt states what it believes is wrong, then makes the smallest change that tests that.form a hypothesis before editing
04ImplementTargeted edits, whole files, or patches - every path confined to the workspace.edit_file · write_file · apply_patch
05TestDone means a command proved it works, not that an edit applied.shell
06InspectExit codes, stdout, and stderr come back as data, not as a crash.exit_code · stderr
07IterateIt reads the failure and adjusts, up to a hard iteration ceiling.max_iterations

The last stage feeds back into the middle. That single edge is the difference between an agent and a model call - a failing test is not the end of the run, it is the input to the next iteration.

Design decisions worth knowing

Tool results are data, never exceptions

A missing file, a non-zero exit, a patch that will not apply - each comes back as a result the model must read. Nothing about a failed tool call ends the run, which is precisely what allows the loop to correct itself.

The policy sits below the registry

Permission checks happen at dispatch, not inside individual handlers. A new tool cannot forget to ask, and a prompt cannot talk its way around the check because the check is not in the conversation. See Permissions.

Paths resolve against one root

Every filesystem tool resolves relative to the workspace root and refuses a path that escapes it. There is one place that logic lives, so there is one place to audit.

Compaction instead of failure

When the context approaches the window limit, earlier history is summarised rather than truncated blindly. A long run degrades in fidelity instead of dying at a hard boundary.

The provider is an interface

The loop depends on an interface, not a vendor. That is what makes the fake provider possible - the whole harness is testable with no network and no spend - and it is where additional providers attach.

Observability

  • Each tool call is logged as it happens, with its arguments, at the level you set.
  • FORGE_LOG_JSON switches the step log to structured JSON for ingestion.
  • Every run prints a summary: duration, model calls, tool calls, and token usage.
  • --json puts a machine-readable result on stdout while the log stays on stderr, so both are usable at once.

Reading the log is the fastest way to understand the agent's behaviour. When a run goes wrong, it usually went wrong several steps before the visible failure - and the log shows where.