Skip to content

Boot-time TOML drift clobbers runtime-rendered prompt tails (Reference Knowledge, Your Team) — settings tail fixed in #3142, others still broken #3143

Description

@neo-wanderer

Summary

The boot-time TOML drift loop at crates/librefang-kernel/src/kernel/mod.rs:~2860 overwrites multiple runtime-rendered prompt tails on every restart, not just the ## User Configuration block. PR #3142 fixes the settings-tail case; this issue tracks the rest.

Mechanism (recap)

At hand activation / agent spawn, the kernel APPENDS several markdown blocks to manifest.model.system_prompt:

Block Source Rendered when
## User Configuration librefang_hands::resolve_settings() from [[settings]] hand activation
## Reference Knowledge skill prompt_context.md content from the agent's skills allowlist hand activation / agent spawn
## Your Team peer agent registry info hand activation / agent spawn

These are persisted to the agents DB.

On every subsequent restart, the boot-time drift detector compares the disk TOML manifest to the DB blob:

let changed = serde_json::to_value(&disk_manifest).ok()
    != serde_json::to_value(&entry.manifest).ok();
if changed {
    entry.manifest = disk_manifest;     // ← clobbers ALL rendered tails
    kernel.memory.save_agent(&entry)?;
}

The disk TOML never carries any of those tails (they're runtime artifacts, never written back to the source file), so the comparison ALWAYS triggers on a hand-with-any-rendered-tail restart, and all the rendered context is silently dropped from the prompt.

What's already fixed (PR #3142)

PR #3142 introduces apply_settings_block_to_manifest(), makes it idempotent via a USER_CONFIG_TAIL_MARKER, and re-runs it inside the drift block before save_agent. The activation site is also refactored to use the same helper. Result: settings tail survives restarts.

What's still broken

After #3142 lands, restarts will still:

  1. Strip ## Reference Knowledge. Agents lose access to their skill prompt_context.md content. The agent then falls back to skill_read_file({skill, path}) calls on demand if it knows what to ask for, but the discoverability summary is gone — affects which skills get considered on the first turn of each session, even when session_mode=new.

  2. Strip ## Your Team. Agents lose the peer registry list, so agent_send to peers becomes "I don't know who's running" guesswork. Symptom: agent calls agent_list redundantly at the top of every session because the static peer info is no longer in the prompt.

Both are silent: no error, no warning, just degraded agent context. The bug is most visible on multi-hand setups with peer routing or skill-heavy hands.

Suggested follow-up shape

Two reasonable approaches:

A. Replicate the #3142 pattern for each rendered tail. Helpers
apply_skill_section_to_manifest() and
apply_team_section_to_manifest(), each idempotent via marker, each
called from both activation and drift sites. Pros: minimal, surgical.
Cons: multiplies the helper count; future runtime-rendered tails
(there will be more) need the same treatment.

B. Compare on a sanitized projection of the manifest that strips ALL known runtime-rendered tails before the diff. The drift block then only fires when the raw TOML truly differs from the raw DB form, ignoring rendered synthesis entirely. Pros: solves the whole class of bug at once; new rendered blocks are automatically tolerated as long as they have markers. Cons: requires defining a manifest_for_diff() projection and keeping the marker registry up to date.

I lean toward B for long-term hygiene, but A is the cheap incremental path if maintainer prefers landing fixes one at a time.

Repro (post-#3142)

A multi-agent hand with multiple skills:

# example-hand/HAND.toml
id = "example"
name = "Example"
skills = ["alpha", "beta"]   # both have non-empty prompt_context.md

[agents.worker]
name = "example-worker"
skills = ["alpha", "beta"]   # workaround for #3135

[agents.worker.model]
provider = "default"
model = "default"
system_prompt = """You are example-worker."""
librefang hand activate example
# Inspect example-worker's stored manifest — system_prompt has
#   ## User Configuration   (post-#3142: survives)
#   ## Reference Knowledge  (still gets dropped)
#   ## Your Team            (still gets dropped — if peers exist)

just restart
# Re-inspect — Reference Knowledge and Your Team blocks GONE.

Why one issue

These two follow-up cases share the exact same diagnosis as #3142 — same code path, same fix shape. Filing as one issue rather than two so reviewers can decide between (A) and (B) holistically.

Out of scope

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/memorySQLite memory substratearea/runtimeAgent loop, LLM drivers, WASM sandbox

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions