feat(desktop): two-layer agent prompt architecture + dynamic AGENTS.md regeneration#584
Open
wpfleger96 wants to merge 11 commits into
Open
feat(desktop): two-layer agent prompt architecture + dynamic AGENTS.md regeneration#584wpfleger96 wants to merge 11 commits into
wpfleger96 wants to merge 11 commits into
Conversation
wesbillman
reviewed
May 14, 2026
wesbillman
reviewed
May 14, 2026
999d81d to
05a6059
Compare
Agents running in Sprout have no platform awareness — they don't know about channels, MCP tools, @mention syntax, or the other agents in the workspace. This adds a base layer prompt (compiled into sprout-acp via include_str!) that gives every agent reliable Sprout context on every turn, regardless of persona or runtime. Base layer: platform identity, MCP tool reference, communication patterns, workspace layout, startup recovery. Persona layer: unchanged role-specific content (Solo/Kit/Scout behavioral protocols, worktree discipline, quality bar). Also adds default model (claude-sonnet-4-20250514) to all three built-in personas so users don't need to pick at agent creation, and fixes a merge_personas() bug where .is_some() checks would cause infinite reset loops once built-ins carry non-None model values.
Consolidated fixes from 5 independent reviewers (3 Claude specialists,
Codex/GPT-5.5, Gemini) plus PLAN author feedback.
Structural: refactor format_prompt() from 6 positional params to
FormatPromptArgs struct — eliminates positional confusion and makes
future additions zero-cost for existing call sites.
Correctness: replace get_channel_history() references in [Context]
hints with get_messages() (the actual MCP tool name), fix $AGENT_CWD
reference in base_prompt.md (env var doesn't exist), fix [Context]
description in PERSONA_PACK_SPEC to match reality.
Config: migrate SPROUT_ACP_BASE_PROMPT_DISABLED from bare env::var
check to proper Config field (no_base_prompt), add base_prompt_file
for runtime override. Change base_prompt type from Option<String> to
Option<&'static str> to reflect compile-time constant nature.
Personas: add provider: Some("claude") alongside model on all three
built-in personas so the UI/runtime agree on the backend. Add comment
documenting merge_personas() canonical-override semantics.
Coverage: prepend [Base] to heartbeat and initial_message paths that
previously bypassed format_prompt(). Add section-ordering test with
full context. Fix trailing-newline triple-gap via trim_end().
base_prompt.md now leads with the sprout CLI (12 subcommand groups from #585) rather than MCP tools, which are still available but secondary. Adds the @mention formatting rule (no bold/italic), get_feed type filtering, and a pointer to nest_agents.md for workspace conventions. Reverts the hardcoded model/provider on Solo, Kit, and Scout back to None. The agent binary's own default is the right choice here — pinning claude-sonnet-4-20250514 is opinionated and will go stale. Users who want a specific model can set it per-agent via the model picker.
23b4eb0 to
035d4c0
Compare
AGENTS.md in ~/.sprout is now dynamically regenerated whenever personas, agents, or workspace config changes. Agents discover their teammates on every fresh session via a managed section demarcated by HTML comment markers. User edits outside the markers are preserved across regenerations.
BackendKind and RespondTo are only used in test helper constructors.
Fixes identified by crossfire review (Codex + Gemini) and plan author: - Use tempfile::NamedTempFile for atomic writes instead of deterministic .tmp path that races under concurrent regeneration triggers - Enforce ordered BEGIN/END marker search with line-start anchoring to prevent inverted slicing when markers are out of order or mid-line - Strip orphan BEGIN markers before appending new managed section - Escape pipe and newline characters in agent/persona names to prevent Markdown table corruption - Rename "Role" column to "Persona" (display_name is a name, not a role) - Move regenerate_nest_context calls outside lock scope in all mutation hooks to reduce lock hold time and eliminate future deadlock risk - Add 7 adversarial unit tests: marker ordering, orphan cleanup, duplicates, code-block false positives, pipe/newline escaping, idempotency
nest.rs grew to 770 lines with regenerate_nest_context, marker helpers, and 19 unit tests. The 500-line default is too tight for this file — override to 800 following the established pattern.
…to wpfleger/agent-personas * origin/wpfleger/dynamic-nest-agents: fix(desktop): add nest.rs to file size check overrides style: apply rustfmt to review-fix commit fix(desktop): address review findings for nest AGENTS.md regeneration fix: gate test-only imports behind #[cfg(test)] in nest.rs feat(desktop): dynamic nest AGENTS.md regeneration
…uplication base_prompt.md (injected every turn via [Base]) already has a comprehensive CLI reference table covering all 11 command groups. The CLI_QUICK_REFERENCE constant in nest.rs was a less complete duplicate (4 commands). AGENTS.md's dynamic section now focuses on what's unique to it: active agents and workspace info. Also bumps personas.rs file size override from 900 to 950 to accommodate the merge_personas inequality checks added in the review-fix commit.
8f1b241 to
350aa8a
Compare
Review surfaced 10 findings across 5 independent sources (3 Claude specialists + Codex + Gemini). Key changes: - Fix incorrect CLI syntax in base_prompt.md (missing --channel flag, positional arg that should be named) and stale nest_agents.md reference - Apply line-start validation to END_MARKER in find_managed_markers, matching the BEGIN_MARKER contract documented in the function comment - Extract prepend_base_prompt helper to deduplicate [Base] injection across format_prompt, dispatch_heartbeat, and initial_message paths - Move base_prompt_file read from main.rs panic to Config::from_cli with proper ConfigError propagation and 1 MB size guard - Extract try_regenerate_nest helper to consolidate 11 identical error-handling blocks across command files - Sanitize relay_url before AGENTS.md injection (strip CR/LF) - Fix greedy newline strip in strip_orphan_begin_marker - Add doc comments for PromptContext.base_prompt lifetime constraint
350aa8a to
95515fd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two changes to how Sprout managed agents receive platform context, consolidated from PRs #583 and #584 because they're both sides of the same "agent awareness" story.
1. Two-layer prompt architecture (originally #584)
Introduces a shared
[Base]prompt layer injected by the ACP harness on every turn — Sprout platform identity, CLI command reference (all 11 subcommand groups from #585), communication patterns, startup recovery, and workspace layout. Every agent gets reliable Sprout orientation without each persona duplicating it.base_prompt.mdcompiled into the harness viainclude_str!()FormatPromptArgsstruct replaces 6 positional params onformat_prompt()[Base]section prepended before[System](persona) on every turn, including heartbeats and initial messagesSPROUT_ACP_NO_BASE_PROMPTdisables it;SPROUT_ACP_BASE_PROMPT_FILEoverrides the compiled-in defaultmodel/providerreverted toNonePERSONA_PACK_SPEC.mdupdated with two-layer architecture docs2. Dynamic AGENTS.md regeneration (originally #583)
Makes
~/.sprout/AGENTS.mddynamically reflect workspace state instead of being a static write-once file. A managed section (demarcated by<!-- BEGIN SPROUT MANAGED -->markers) is regenerated whenever agents, personas, or workspace config change.regenerate_nest_context()renders active agents table + workspace info into the managed sectiontry_regenerate_nest()helper in 10 Tauri command functions (personas, agents, workspace) — all outside lock scopeensure_nest()for currency on first launchtempfile::NamedTempFileto prevent racesConsolidation + review fixes
Consolidated #583 into #584 since both modify agent context. Then addressed 10 findings from crossfire code review (3 Claude specialists + Codex + Gemini):
base_prompt.md(--channel <UUID>flag, space-separated args) and stalenest_agents.mdfile reference →AGENTS.mdEND_MARKERinfind_managed_markers, matching theBEGIN_MARKERcontractprepend_base_prompt()helper to deduplicate[Base]injection acrossformat_prompt,dispatch_heartbeat, andinitial_messagepaths--base-prompt-fileread frommain.rspanic toConfig::from_cli()with properConfigErrorpropagation and 1 MB size guardtry_regenerate_nest()helper to consolidate 11 identical error-handling blocks across command filesrelay_urlbefore AGENTS.md injection (strip CR/LF)strip_orphan_begin_markerPromptContext.base_prompt'staticlifetime constraintCLI_QUICK_REFERENCEconstant fromnest.rs— the[Base]prompt's CLI table makes the 4-command duplicate unnecessaryFiles changed
ACP harness (base prompt):
crates/sprout-acp/src/base_prompt.md— CLI-first base layer contentcrates/sprout-acp/src/queue.rs—FormatPromptArgsstruct +[Base]injection +prepend_base_prompt()helpercrates/sprout-acp/src/pool.rs—base_promptonPromptContextwith lifetime doc commentcrates/sprout-acp/src/config.rs—SPROUT_ACP_NO_BASE_PROMPT+SPROUT_ACP_BASE_PROMPT_FILE+base_prompt_contentvalidated infrom_cli()crates/sprout-acp/src/main.rs—PromptContextconstruction + heartbeat[Base]via helpercrates/sprout-acp/README.md—get_channel_history→get_messagesDesktop (personas + dynamic AGENTS.md):
desktop/src-tauri/src/managed_agents/personas.rs— refactored built-ins,merge_personas()inequality fixdesktop/src-tauri/src/managed_agents/nest.rs—regenerate_nest_context()+try_regenerate_nest()+ rendering + marker parsing (line-start validation on both markers) + relay URL sanitization + 19 testsdesktop/src-tauri/src/managed_agents/nest_agents.md— managed section markers in templatedesktop/src-tauri/src/lib.rs— startup regeneration hookdesktop/src-tauri/src/commands/personas.rs— regeneration viatry_regenerate_nest()(6 call sites)desktop/src-tauri/src/commands/agents.rs— regeneration viatry_regenerate_nest()(2 call sites)desktop/src-tauri/src/commands/agent_models.rs— regeneration viatry_regenerate_nest()desktop/src-tauri/src/commands/workspace.rs—AppHandleparam + regeneration viatry_regenerate_nest()desktop/scripts/check-file-sizes.mjs—nest.rs(800) +personas.rs(950) overridesDocumentation:
crates/sprout-persona/PERSONA_PACK_SPEC.md— two-layer architecture section