If an agent feels unusually disciplined, the explanation is usually not the model. It is the runtime around it. A source-level breakdown of how Forge engineers communication quality, planning continuity, and failure recovery through layered prompt composition, persistent task state, and runtime control hooks -- not through a better system prompt.
Forge caught my attention for reasons that had nothing to do with tool access. Plenty of systems can read files, patch code, and run commands. What stood out was the working style: direct answers, low status noise, explicit task structure, strong continuity after interruption. A noticeable resistance to the usual assistant habits of filler, hedging, and premature closure.
Once I read the source, the mechanism was obvious. Forge composes its behavior from layers, and those layers are visible in the code.
There is a base agent identity in forge.md. A wrapper template that injects local doctrine and non-negotiable runtime rules in forge-custom-agent-template.md. An instruction discovery service that searches for AGENTS.md in the base path, git root, and current working directory (instructions.rs). And a prompt builder that composes those layers into the active system context in system_prompt.rs.
This post traces each layer through the source code.
Prompt composition
Most systems treat communication quality as a model-quality question. Forge treats it as a composition problem.
The base agent definition in forge.md encodes target behavior directly:
1. **Solution-Oriented**: Focus on providing effective solutions rather than apologiating.2. **Professional Tone**: Maintain a professional yet conversational tone.3. **Clarity**: Be concise and avoid repetition....7. **Grounded in Reality**: ALWAYS verify information about the codebase using tools before answering....You have access to the {{tool_names.todo_write}} tool to help you manage and plan tasks.Use this tool VERY frequently...It is critical that you mark todos as completed as soon as you are done with a task.Each line maps to a specific failure mode: apology spam, vague confidence, hidden planning, the generic assistant voice that makes technical collaboration slower.
Forge does not stop at one prompt file. In system_prompt.rs, the system prompt builder merges agent-level rules with externally loaded instructions, then renders both the base system prompt and a custom wrapper template:
let mut custom_rules = Vec::new();
agent.custom_rules.iter().for_each(|rule| { custom_rules.push(rule.as_str());});
self.custom_instructions.iter().for_each(|rule| { custom_rules.push(rule.as_str());});...let static_block = TemplateEngine::default() .render_template(Template::new(&system_prompt.template), &ctx)?;let non_static_block = TemplateEngine::default() .render_template(Template::new("{{> forge-custom-agent-template.md }}"), &ctx)?;The behavior is composed, not monolithic. A base identity, plus a wrapper that injects local doctrine and runtime rules around that identity.
That wrapper lives in forge-custom-agent-template.md:
<project_guidelines>{{custom_rules}}</project_guidelines>
<non_negotiable_rules>- ALWAYS use tools to investigate the codebase before answering questions about how it works.- ALWAYS present the result of your work in a neatly structured format...- Do what has been asked; nothing more, nothing less.</non_negotiable_rules>Two things are happening here. First, the wrapper separates project-specific rules from hard runtime rules. Second, it marks part of the wrapper as non-negotiable. The model does not get to trade these off against other instructions.
The local doctrine is discovered automatically. In instructions.rs, the instructions service walks three locations:
/// This service looks for AGENTS.md files in three locations in order of/// priority:/// 1. Base path (environment.base_path)/// 2. Git root directory (if available)/// 3. Current working directory (environment.cwd)...let base_agent_md = environment.base_path.join("AGENTS.md");...if let Some(git_root_path) = self.get_git_root().await { let git_agent_md = git_root_path.join("AGENTS.md"); ...}...let cwd_agent_md = environment.cwd.join("AGENTS.md");If a repository says “be evidence-first” or “challenge weak framing,” the harness pulls that doctrine into the active prompt stack at session start. The tone you see in a good Forge session is locally adapted to the repo you are working in.
State persistence and rehydration
Many systems tell the model to make a plan. Fewer systems make that plan survive interruption, tool use, and resumption.
Forge handles planning continuity as runtime state. In session_metrics.rs, session metrics keep a persistent list of todos. New task updates merge with completed historical tasks instead of replacing them:
pub fn update_todos(&mut self, mut new_todos: Vec<Todo>) -> anyhow::Result<Vec<Todo>> { ... let mut merged = new_todos; for todo in &self.todos { if todo.status == TodoStatus::Completed && !merged.iter().any(|candidate| candidate.id == todo.id) { merged.push(todo.clone()); } }
merged.sort_by(|left, right| left.id.cmp(&right.id)); self.todos = merged;
Ok(self.get_active_todos())}Completed work is preserved outside the model and carried forward by the runtime. The model does not need to remember its own project history unassisted.
Resume behavior is explicit. In user_prompt.rs, existing todos are formatted as a markdown checklist and injected back into the conversation as a droppable user message:
if !todos.is_empty() { let todo_content = self.format_todos_as_markdown(&todos); let todo_message = TextMessage { role: Role::User, content: todo_content, ... droppable: true, }; context = context.add_message(ContextMessage::Text(todo_message));}The formatting is plain on purpose:
let mut content = String::from("**Current task list:**\n\n");...writeln!(content, "- {} {}", checkbox, todo.content)If you have used an agent that felt sharp at first and went vague after a pause, this is usually why. Planning quality depends on whether the runtime stores state and rehydrates it at the right moment, not on whether you told the model to “stay organized.”
Failure detection as a runtime hook
The doom-loop detector is the mechanism that makes Forge behave like a control system rather than a prompt wrapper.
Instead of hoping the model notices when it is repeating itself, Forge inspects the recent conversation for repetitive tool-call patterns and injects a reminder when it detects a loop.
In doom_loop.rs:
if let Some(consecutive_calls) = self.detect_from_conversation(conversation) { ... let reminder = TemplateEngine::default().render( "forge-doom-loop-reminder.md", &serde_json::json!({"consecutive_calls": consecutive_calls}), )?; let content = Element::new("system_reminder").cdata(reminder); context .messages .push(ContextMessage::user(content, None).into());}The harness reads the trajectory of the session, decides the agent is stuck, and intervenes before the next request goes out.
That hook is wired into the orchestrator’s request lifecycle in app.rs:
let hook = Hook::default() .on_start(tracing_handler.clone().and(title_handler)) .on_request(tracing_handler.clone().and(DoomLoopDetector::default())) .on_response( tracing_handler .clone() .and(CompactionHandler::new(agent.clone(), environment.clone())), ) .on_toolcall_start(tracing_handler.clone()) .on_toolcall_end(tracing_handler) .on_end(on_end_hook);The difference between “we could detect loops” and “the runtime has an active intervention point on every request” is structural. The hook is in the hot path. It runs whether or not the model is looping, because the model cannot opt out of being checked.
Forge also enforces a stricter definition of completion than most agents. In orch.rs, a turn is only complete if the model stopped and there are no remaining tool calls:
is_complete = message.finish_reason == Some(FinishReason::Stop) && message.tool_calls.is_empty();Polished prose does not count as done. If tool execution is still pending, the turn keeps going. Most agents treat completion as rhetorically legible. Forge defines it in code and enforces it structurally.
Execution boundaries in the runtime, not the prompt
The same pattern appears in execution safety.
A common mistake in agent design is solving runtime boundaries in natural language: describe what the model should not do, hope it remembers, call that a control system.
Forge does something more concrete. In executor.rs, the command executor swaps to rbash when restricted mode is enabled:
let shell = if self.restricted && !is_windows { "rbash"} else { self.env.shell.as_str()};let mut command = Command::new(shell);If a boundary can be enforced by the runtime instead of described in prose, that is the stronger place to put it. Forge improves communication quality, continuity, and safety with the same move: stop asking the model to carry the full burden alone.
What is worth copying
The reusable pattern across all of these mechanisms is a separation of responsibilities.
Keep the global persona narrow. Encode the communication traits you actually want (directness, concision, evidence-first behavior) in the base agent layer.
Inject local doctrine separately. If a repository has an AGENTS.md or style guide, load it dynamically and treat it as a system input. Forge’s prompt assembly (system_prompt.rs) and instruction discovery (instructions.rs) are a workable starting pattern.
Persist task state outside the model. Forge’s session metrics merge logic (session_metrics.rs) shows how to keep completed work visible without relying on model memory.
Rehydrate on resume. State in storage helps nobody unless the live session gets it back. Forge formats todos as plain markdown and injects them as a user message (user_prompt.rs).
Wire failure hooks into the hot path. The doom-loop detector runs on every request (app.rs), not just when the model seems stuck. That is the difference between an idea and an intervention.
Define completion structurally. Forge’s is_complete check (orch.rs) requires both a stop reason and no pending tool calls. Polished prose alone does not close a turn.
Push boundaries to the OS layer. The restricted-shell path (executor.rs) delegates a safety boundary to rbash instead of trying to enforce it in the prompt.
Where this led
Reading Forge’s source code changed how I thought about agent behavior. The layered composition pattern in particular, base identity, wrapper rules, repo-discovered doctrine, all merged into one prompt stack, became the starting point for a series of experiments.
The first question was direct: if Forge gets better behavior by layering instructions at specific lifecycle moments, can I reproduce that pattern with a parameterized personality system? I built a render pipeline with composable layers and tested it across multiple models.
That work is documented in two posts. The first, Stronger Models Regress Under Heavier Personality, covers what happened when I ran an auto-research loop and discovered that the heaviest personality form dragged a stronger model below its own baseline. The second, How 861 Characters Made an AI Agent 41% Better, expands the dataset to 154 runs across five model families and shows that compressed rules at 861 characters beat detailed explanations at 2,318.
The connecting thread: Forge’s compositional approach works because it separates concerns. Base identity, runtime rules, and local doctrine are distinct layers merged at session start. The personality work confirmed that stacking those concerns into one blob produces instruction conflicts the model resolves unpredictably. Composition matters. Monolithic prompts do not.
The next post in this series, Delegate Security, Don’t Parse It, goes into OS boundaries versus prompt-based safety rules: what rbash, execve(2) interception, and syscall filtering look like in actual system code.
What is the weakest part of your current agent stack: safety, context, output structure, planning, or evaluation?
Key Takeaways
- Communication quality is a composition problem, not a model quality problem. Forge layers base persona, wrapper rules, and repo-discovered doctrine into one prompt stack.
- Planning continuity requires runtime state. Forge persists task history outside the model and rehydrates it on resume -- it does not rely on model memory.
- Failure modes need runtime hooks. The doom-loop detector inspects conversation trajectory and intervenes before the model burns more tokens on a stuck pattern.
- Completion should be structural, not rhetorical. Forge only considers a turn done when the model stops AND there are no pending tool calls.
- Push boundaries downward when possible. If a control belongs in the shell or OS, put it there instead of describing it in prose.