Problem
LibreFang has no extension point in the system-prompt build path. Every piece of context that should be injected before the model sees the user turn — live memory recall, tool-usage guidance for newly enabled hands, dynamic state from runtime plugins (e.g. "phone-control is currently armed until 14:32") — has to be wired in by editing prompt_builder.rs directly.
This forces every cross-cutting feature to land as a kernel patch instead of as a hand / skill / extension. It also blocks the active-memory pattern (a memory sub-agent that runs before every eligible reply and injects its findings into system context), because the only way to invoke a memory agent today is for the main agent to know it should and call agent_send itself — which pollutes the system prompt and leaks the architecture to the LLM.
Reference: openclaw
OpenClaw plugins implement a before_prompt_build hook that calls prependSystemContext(...). Examples:
extensions/active-memory: runs a bounded blocking memory sub-agent and injects its result so the main agent never has to know memory exists.
extensions/diffs: prepends stable tool-usage guidance for the diffs tool every turn, kept out of user-prompt space.
extensions/phone-control: injects current arm/disarm state so the agent knows which high-risk tools are gated.
The hook contract is: "you get the prompt context as it is being built, you may prepend a labeled section, you may not mutate user / history / tool-result content."
Proposed approach
-
Define trait PromptContextProvider in librefang-runtime:
#[async_trait]
pub trait PromptContextProvider: Send + Sync {
fn name(&self) -> &str;
async fn before_prompt_build(
&self,
ctx: &PromptBuildContext<'_>,
) -> Option<PrependedSection>;
}
PromptBuildContext exposes read-only agent_id, session_id, last_user_message, chat_type, recent_history_summary. Returning None is a no-op.
-
Register providers on the kernel via KernelHandle::register_prompt_provider(Arc<dyn PromptContextProvider>). Section 15 (Live Context) in prompt_builder.rs already proves the slot is safe — extend it to fan out across all registered providers, in registration order.
-
Each provider's output goes into its own section (## Live Memory, ## Diffs Guidance, …) with the provider name as the header so the LLM (and a debugging human) can attribute content.
-
Hard caps: per-provider 8 KB, total injected 32 KB, 1 s wall budget for the whole fan-out. Time-out / over-budget providers drop with a WARN log, never block the turn.
-
Hands and skills can register providers at activation; the kernel deregisters them at deactivation. No new TOML schema needed initially — programmatic registration only.
Acceptance criteria
This is the prerequisite for any future active-memory, diffs, or runtime-state injection feature; do not implement those until this lands.
Problem
LibreFang has no extension point in the system-prompt build path. Every piece of context that should be injected before the model sees the user turn — live memory recall, tool-usage guidance for newly enabled hands, dynamic state from runtime plugins (e.g. "phone-control is currently armed until 14:32") — has to be wired in by editing
prompt_builder.rsdirectly.This forces every cross-cutting feature to land as a kernel patch instead of as a hand / skill / extension. It also blocks the
active-memorypattern (a memory sub-agent that runs before every eligible reply and injects its findings into system context), because the only way to invoke a memory agent today is for the main agent to know it should and callagent_senditself — which pollutes the system prompt and leaks the architecture to the LLM.Reference: openclaw
OpenClaw plugins implement a
before_prompt_buildhook that callsprependSystemContext(...). Examples:extensions/active-memory: runs a bounded blocking memory sub-agent and injects its result so the main agent never has to know memory exists.extensions/diffs: prepends stable tool-usage guidance for thediffstool every turn, kept out of user-prompt space.extensions/phone-control: injects current arm/disarm state so the agent knows which high-risk tools are gated.The hook contract is: "you get the prompt context as it is being built, you may prepend a labeled section, you may not mutate user / history / tool-result content."
Proposed approach
Define
trait PromptContextProviderinlibrefang-runtime:PromptBuildContextexposes read-onlyagent_id,session_id,last_user_message,chat_type,recent_history_summary. ReturningNoneis a no-op.Register providers on the kernel via
KernelHandle::register_prompt_provider(Arc<dyn PromptContextProvider>). Section 15 (Live Context) inprompt_builder.rsalready proves the slot is safe — extend it to fan out across all registered providers, in registration order.Each provider's output goes into its own section (
## Live Memory,## Diffs Guidance, …) with the provider name as the header so the LLM (and a debugging human) can attribute content.Hard caps: per-provider 8 KB, total injected 32 KB, 1 s wall budget for the whole fan-out. Time-out / over-budget providers drop with a
WARNlog, never block the turn.Hands and skills can register providers at activation; the kernel deregisters them at deactivation. No new TOML schema needed initially — programmatic registration only.
Acceptance criteria
PromptContextProvidertrait + registration API onKernelHandle.prompt_builder.rscalls registered providers concurrently with the 1 s deadline; results merge into the existing Section 15 area as labeled subsections.docs/architecture/describing the hook contract and the budget caps.This is the prerequisite for any future
active-memory,diffs, or runtime-state injection feature; do not implement those until this lands.