Concepts
Prompt Architecture
Brute’s default system prompt is not one string — it is a stack of small prompt modules selected per provider and rendered at turn time. Brute::Middleware::SystemPrompt prepends the result as the :system message (unless one already exists in the log).
Deferred building
Section titled “Deferred building”Brute::SystemPrompt.build stores a block; it runs only when prepare receives a runtime context:
sp = Brute::SystemPrompt.build do |prompt, ctx| prompt << Brute::Prompts::Identity.call(ctx) prompt << Brute::Prompts::Environment.call(ctx)end
sp.prepare(provider_name: "anthropic", model_name: "claude-...", cwd: Dir.pwd)# => #<SystemPrompt::Result> — .to_s (joined) or .sections (one per module)Deferral matters because modules adapt to the context: which model family is running, what the working directory holds, which skills were discovered. Nothing is frozen at agent-construction time.
Provider stacks
Section titled “Provider stacks”Brute::SystemPrompt.default picks an ordered module list from STACKS by provider — "anthropic", "openai", "google", with "default" as the fallback. Gateway providers (opencode_*) infer the upstream model family from the model name, so a claude-* model behind a gateway gets the Anthropic stack.
The Anthropic stack, for illustration: Identity, ToneAndStyle, Objectivity, TaskManagement, DoingTasks, ToolUsage, Conventions, GitSafety, CodeReferences, Environment, Skills, Instructions. Other providers get leaner variants; several modules also carry per-provider text templates under lib/brute/prompts/text/ (e.g. identity/anthropic.txt, identity/google.txt) so even shared sections speak each model’s dialect.
Conditional sections
Section titled “Conditional sections”After the base stack, the default builder appends reminders based on runtime state:
| Condition | Module |
|---|---|
ctx[:agent] == "plan" |
Prompts::PlanReminder |
ctx[:agent_switched] == "build" |
Prompts::BuildSwitch |
ctx[:max_steps_reached] |
Prompts::MaxSteps |
A module is just .call(ctx) -> String-or-nil
Section titled “A module is just .call(ctx) -> String-or-nil”Every prompt module answers one class method. Prompts::Skills is typical — its ERB template renders only each skill’s name, description, and location into <available_skills> (the body loads later via the skill tool), reading skills from ctx[:skills] or scanning ctx[:cwd].
Custom prompts
Section titled “Custom prompts”Pass your own to the middleware — built from any mix of Brute modules and your own text:
sp = Brute::SystemPrompt.build do |prompt, ctx| prompt << "You are a release engineer for #{ctx[:cwd]}." prompt << Brute::Prompts::GitSafety.call(ctx) prompt << Brute::Prompts::Skills.call(ctx) # scans cwd when no ctx[:skills]end
Brute.agent.use(Brute::Middleware::SystemPrompt, system_prompt: sp)