recursive-agent 0.8.2

A Rust coding-agent platform: kernel, tools, multi-agent orchestration, HTTP API, MCP, and TUI
Documentation
---
type: Architecture
title: Layer 0 — System Prompt Assembly
description: How the system prompt is assembled from multiple memory sources before each agent run. Injection order, size limits, and the role of each source.
tags: [layer0, system-prompt, injection, memory, config]
timestamp: 2026-06-30T12:15:00Z
---

# Layer 0 — System Prompt Assembly

`src/system_prompt.rs::assemble_system_prompt` is the single maintenance point
for the "common" system-prompt structure. Every agent-loop entry point — CLI
`run` / `do` (`cli/builder.rs::build_runtime`), CLI `loop`
(`main.rs::run_loop`), HTTP API (`src/http/handlers.rs`), and TUI
(`recursive-tui/src/runtime_builder.rs`) — calls it with a channel-prepared
base and gets back the full prompt. Channels only differ in how they source
the base (e.g. CLI `--append-system-prompt`, HTTP `append_system_prompt`
request field), which they fold into `base` before calling. MCP server mode
has no agent loop and uses no system prompt.

The six memory layers below are assembled inside `Config::from_env()` and
already live in `config.system_prompt` (the `base` passed to
`assemble_system_prompt`). The order is intentionally **stable → volatile**
to maximise prefix-cache hits.

## Injection Order

```
1. # User preferences       ← ~/.recursive/memory/user.md        (≤ 8 KB)
2. # Project memory         ← <workspace>/.recursive/memory/project.md (≤ 8 KB)
3. # Memory                 ← .recursive/memory.json notes (legacy)
4. # Scratchpad             ← working memory KV entries
5. # Facts                  ← facts.jsonl entries (global + workspace merged)
6. # Episodic recall        ← recent session summaries
   ─── config.system_prompt ends here (the "base") ───
7. # Project context        ← AGENTS.md + CLAUDE.md at workspace root (≤ 16 KB each)
8. # Available skills       ← skill_index() string
9. ## Coordinator workflow  ← coordinator_system_prompt(), only when sub-agent enabled
   + `agent` tool usage note
```

`assemble_system_prompt(base, workspace, skills, sub_agent_enabled)` lays down
7–9 on top of `base`. Project context is prepended (via
`prepend_project_context`) so a user-supplied `--system-prompt` / HTTP
`system_prompt` still gets the project context in front of it. The
coordinator workflow + `agent` tool note (9) appear only when
`config.subagent_enabled` is true, in lockstep with the `Agent` tool
registered by `multi::register_subagent_if_enabled` (also called by every
channel) — so the prompt never advertises the `agent` tool to a surface that
lacks the tool.

CLI `run` additionally appends goal-matched skill *bodies* after assembly
(the only channel with a `goal` at prompt-build time); this is a
channel-specific suffix, not part of the common assembly.

## Size Limits

| Source | Cap | Location |
|--------|-----|----------|
| `user.md` | 8 KB | `MAX_MEMORY_FILE_SIZE` in `src/config.rs` |
| `project.md` | 8 KB | `MAX_MEMORY_FILE_SIZE` |
| `AGENTS.md` | 16 KB | `MAX_PROJECT_CONTEXT_SIZE` |
| `CLAUDE.md` | 16 KB | `MAX_PROJECT_CONTEXT_SIZE` |
| Facts summary | token budget | `src/tools/facts.rs::facts_summary()` |

Files that exceed their cap are truncated with a `[…truncated]` marker.

## Key Functions

| Function | File | Purpose |
|----------|------|---------|
| `load_user_memory()` | `src/config.rs` | Read `~/.recursive/memory/user.md` |
| `load_project_memory()` | `src/config.rs` | Read workspace `project.md` |
| `load_project_context()` | `src/config.rs` | Read `AGENTS.md` + `CLAUDE.md`, merge under `## ` sub-headers |
| `prepend_project_context()` | `src/config.rs` | Prepend the project context block to a base prompt |
| `assemble_system_prompt()` | `src/system_prompt.rs` | Single common assembly entry point called by every channel |
| `register_subagent_if_enabled()` | `src/multi.rs` | Register the `Agent` tool when `config.subagent_enabled` (called by every channel) |
| `default_system_prompt()` | `src/config.rs` | Hardcoded working principles |
| `skill_index()` | `src/skills.rs` | One-line summary per available skill |
| `facts_summary()` | `src/tools/facts.rs` | Recent facts as bullet list |
| `episodic_recall_summary()` | `src/tools/episodic_recall.rs` | Last session summary |

## Adding a New Layer 0 Source

1. Write a `load_*()` function in `src/config.rs` that returns `Option<String>`.
2. If it is a base/memory source, append it inside `Config::from_env()`'s
   `layers` vec — it then reaches every channel automatically via
   `config.system_prompt`. If it is a post-base source (like project context
   or skill index), add it inside `assemble_system_prompt` in
   `src/system_prompt.rs` — again, every channel picks it up via the single
   call site.
3. Add a section header so the agent knows which section it's reading.

Do NOT add a new source by editing each channel separately — that recreates
the per-channel duplication this layout exists to avoid.

**Do not branch inside `AgentRuntime::run`** to add context — use the system
prompt builder (Invariant #1).

## Architecture Knowledge Bundle as Layer 0

This architecture bundle (`.dev/architecture/`) can itself be injected as
Layer 0 by referencing `index.md` in `project.md`:

```markdown
## Architecture Reference
See `.dev/architecture/index.md` for the full architecture knowledge bundle.
Load individual concept docs with `Read` on demand.
```

## Related Concepts

- [Memory Overview]memory/index.md — full four-layer memory system
- [Layer 0 — Injected Context]memory/layer0-injected-context.md — user.md and project.md details
- [Layer 1 — Scratchpad]memory/layer1-scratchpad.md — working memory injected at position 4
- [Layer 2 — Facts]memory/layer2-facts.md — facts injected at position 5
- [Layer 3 — Episodic]memory/layer3-episodic.md — episodic recall at position 6
- [Skills System]skills.md — skill_index() at position 9
- [Agent Loop]agent-loop.md — how the assembled system prompt is used