Skip to content

feat(acp): compose agent identity into system prompt with fallback#1087

Open
tlongwell-block wants to merge 1 commit into
mainfrom
eva/system-prompt-fallback-core
Open

feat(acp): compose agent identity into system prompt with fallback#1087
tlongwell-block wants to merge 1 commit into
mainfrom
eva/system-prompt-fallback-core

Conversation

@tlongwell-block

Copy link
Copy Markdown
Collaborator

What & why

Agent identity (operator system prompt + NIP-AE core memory) is composed into a single identity section that rides the existing system-prompt routing, instead of core always being emitted as a standalone [Agent Memory — core] user-message section.

  • v2 agents (e.g. buzz-agent, protocol_version >= 2): the composed identity lands in the real system role via session/new.
  • v1 / stock ACP agents: the same composed string lands in the user message via the existing format_prompt legacy branch.

No new version fork in routing — the per-version split stays entirely in the existing !has_system_prompt_support gate, which kills double-render by construction.

Composition (core is additive, not exclusive-or)

operator prompt core fetch system role includes
present present base + operator + core
present confirmed empty base + operator
present failed base + operator
absent present base + core
absent confirmed empty base + fallback
absent failed base only

Fallback (the repurposed onboarding text → FALLBACK_SYSTEM_PROMPT) fires only when there's no operator prompt and core fetch confirmed-empty. A relay hiccup (Unavailable) never hands an established agent a newbie identity.

Key changes

  1. engram_fetch.rsbuild_core_section: Option<String> → tri-state fetch_core -> CoreFetch::{Present, ConfirmedEmpty, Unavailable}. The old Option flattened "empty" and "fetch failed" into one None; the enum keeps them distinct — the distinction the fallback rule depends on.
  2. pool.rs — two pure helpers (resolve_identity_section, compose_system_prompt) with the truth table in doc comments. Core fetch hoisted above session creation (is_new_session via a cheap map lookup), resolved section cached for the legacy path. SessionState.core_sectionsidentity_sections (it holds the resolved identity, not raw core).
  3. ACP compliance — top-level systemPrompt was a Buzz extension, not ACP. Moved to namespaced _meta: { "buzz.systemPrompt": ... } in both sender (acp.rs) and reader (wire.rs). Reader is crash-proof on malformed _meta and now ignores the bare top-level key; regression tests pin that.

Testing

  • Full suites green: 332 buzz-acp + 98 buzz-agent + integration, 0 failures. clippy clean.
  • Guardrail tests: test_format_prompt_modern_agent_suppresses_base_and_system (modern → no duplicate [Agent Memory] in user message) and test_format_prompt_legacy_agent_emits_base_and_system (legacy → composed identity via [System], delivery-neutral).
  • Pre-push hook ran the rust suite green.

Reviewer: please also run locally per TESTING.md.

Move NIP-AE core memory into the composed identity section that rides
the existing system-prompt routing, rather than always emitting it as a
standalone [Agent Memory — core] user section. v2 agents receive it in
the real system role via session/new; legacy v1 agents receive the same
composed string in the user message via the existing format_prompt gate.
No version fork in routing — the per-version split stays in the existing
`!has_system_prompt_support` branch, which kills double-render by
construction.

Core is additive to the operator prompt, not exclusive-or:
- operator prompt present + core present  -> base + operator + core
- operator prompt present + core empty     -> base + operator
- operator prompt absent  + core present   -> base + core
- operator prompt absent  + core empty      -> base + fallback
- core fetch failed (either case)           -> no core, no fallback

Replace the overloaded `build_core_section: Option<String>` with a
tri-state `fetch_core -> CoreFetch::{Present, ConfirmedEmpty,
Unavailable}` so "confirmed empty" and "fetch failed" stay distinct —
the distinction the fallback rule depends on. The old onboarding-nudge
const becomes FALLBACK_SYSTEM_PROMPT.

ACP compliance: top-level `systemPrompt` was a Buzz extension, not ACP.
Move it to the namespaced `_meta: { "buzz.systemPrompt": ... }` envelope
in both the sender (acp.rs) and reader (wire.rs). Reader is crash-proof
on malformed _meta and now ignores the bare top-level key; regression
tests pin that.

Rename SessionState.core_sections -> identity_sections to match what it
now holds (resolved identity, not raw core).

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant