KohakuTerrarium - examples agent apps conversational system
1446 characters
# Conversational Agent Controller
You are a router/orchestrator. You do NOT talk to the user directly.
**CRITICAL: All output goes to TTS (text-to-speech). NEVER use markdown, lists, or formatting.**
## Your Job
1. Gather context (memory, recent conversation)
2. Route to output sub-agent with full context
## Output Format
When user speaks, dispatch to output agent with context:
[/output]
Recent conversation:
{summary of recent exchanges if any}
Memory context:
{relevant facts from memory if any}
User said:
{the user's message}
[output/]
## Rules
1. **NEVER respond directly** - Always use [/output]...[output/]
2. **NEVER use markdown** - No **, ##, *, -, or any formatting
3. **Provide context** - Help output agent understand the situation
4. **Be fast** - Don't overthink, gather context and route
## Example
User says: "What did we talk about yesterday?"
You output:
[/output]
Recent conversation:
User greeted, asked about weather, discussed Python basics
Memory context:
User prefers concise answers, interested in programming
User said:
What did we talk about yesterday?
[output/]
## Memory Management
To save important information:
[/memory_writer]
Save: {fact to remember}
[memory_writer/]
To retrieve memory:
[/memory_read]
Query: {what to look up}
[memory_read/]
## Fallback
If you must output directly (not via sub-agent):
- Plain text only, no formatting
- Short and natural
- Will be spoken aloud via TTS
KohakuTerrarium - docs en concepts impl notes prompt aggregation
5464 characters
---
title: Prompt aggregation
summary: How the system prompt is assembled from personality, tool list, framework hints, and on-demand skills.
tags:
- concepts
- impl-notes
- prompt
---
# Prompt aggregation
## The problem this solves
An agent's "system prompt" is not one string. It is a composition of:
- the creature's personality / role,
- a list of available tools (names + descriptions),
- how to actually call tools in this creature's chosen format,
- any channel topology (in a terrarium),
- a description of named outputs (so the LLM knows when to route to
Discord vs stdout),
- tool-contributed guidance paragraphs (for tools that want to teach the
model how to use them well),
- plugin-contributed sections (project rules, environment info, etc.),
- optional full documentation for every tool (if in `static` skill
mode) — or none of it (if in `dynamic` mode),
- a procedural-skill index and on-demand skill bodies.
If you leave this to hand-written prompts, you ship bugs: stale tool
lists, wrong call syntax, duplicated sections. The framework
assembles the whole thing deterministically.
## Options considered
- **Hand-written prompts.** Fragile. Breaks whenever you add a tool.
- **Always-full static prompts.** Complete but huge — tool docs alone
can be tens of kilotokens.
- **Load-on-demand docs.** Ship names only; let the agent pull full
docs via the `info` framework command when needed.
- **Procedural skills as full inline bodies.** Powerful but too expensive
when a creature discovers many local/user/project skills.
- **Configurable.** Each creature picks the trade-off: `skill_mode:
dynamic` or `skill_mode: static`; procedural skills get a separate
byte-budgeted index.
## What we actually do
`prompt/aggregator.py:aggregate_system_prompt(...)` concatenates
sections in this order:
1. **Base prompt.** Rendered with Jinja2 (safe-undefined fallback);
contains the creature's personality and any project context files
declared under `prompt_context_files`.
2. **Tool section.**
- `skill_mode: dynamic` → tool *index*: name + one-line description
per tool. Agent loads full docs on demand via the `info` framework command.
- `skill_mode: static` → full documentation for every tool inline.
3. **Tool guidance section.** Deterministic aggregation of every tool's
`prompt_contribution()` output, ordered by bucket (`first`, `normal`,
`last`) and then alphabetically within each bucket.
4. **Procedural-skill index.** A byte-budgeted `## Skills` section built
from discovered skills. Only enabled, model-invocable skills are listed.
Overflow skills remain reachable through `##skill <name>##` or
`##info <name>##`.
5. **Channel topology section** (terrarium creatures only). Describes
"you listen on X, Y; you can send on Z; here is who sits on the
other side." Emitted by
`terrarium/config.py:build_channel_topology_prompt`.
6. **Framework hints.** How to call tools in this creature's format
(bracket / XML / native), how to use the inline framework commands
(`read_job`, `info`, `jobs`, `wait`, and `skill` when present), and
what the output protocol looks like.
7. **Named outputs section.** For each `named_outputs.<name>`, a short
description of when to route text there.
8. **Prompt plugin sections.** Each registered prompt plugin (priority
sorted, low→high) contributes one section. Built-ins:
`ToolListPlugin`, `FrameworkHintsPlugin`, `EnvInfoPlugin`,
`ProjectInstructionsPlugin`.
Framework-hint prose itself is now overrideable. The aggregator merges
package-level `framework_hints:` overrides from `kohaku.yaml` with any
creature-level `framework_hint_overrides`, then resolves four canonical
blocks: output model, dynamic execution model, static execution model,
and native execution model.
MCP tools, when connected, are injected as an extra section under
"Available MCP Tools" with per-server bullet lists.
## Invariants preserved
- **Deterministic.** Given the same config + registry + plugin set,
the prompt is byte-stable.
- **Auto sections never duplicate hand-written ones.** If you put a
tool list in your `system.md`, the aggregator's tool list is still
added; the framework does not deduplicate by content.
- **Skill mode is a knob, not a policy.** Nothing else in the system
changes based on `skill_mode` — it is exclusively a prompt-size
trade-off.
- **Skill index is budgeted, not all-or-nothing.** Procedural skills
are indexed up to `skill_index_budget_bytes`; missing ones are still
callable explicitly.
- **Tool guidance is cache-stable.** Bucket ordering plus alphabetical
sort keeps prompt prefixes stable for provider-side prompt caching.
- **Plugin order is explicit.** Priority sorted. Same priority → stable
insertion order.
## Where it lives in the code
- `src/kohakuterrarium/prompt/aggregator.py` — the composition function.
- `src/kohakuterrarium/prompt/plugins.py` — built-in prompt plugins.
- `src/kohakuterrarium/prompt/templates.py` — Jinja safe rendering.
- `src/kohakuterrarium/terrarium/config.py` — channel topology block.
- `src/kohakuterrarium/core/agent.py` — `_init_controller()` calls the
aggregator once on start.
## See also
- [Plugin](../modules/plugin.md) — writing prompt plugins.
- [Tool](../modules/tool.md) — how tool documentation is registered.
- [reference/configuration.md — skill_mode, tool_format, include_*](../../reference/configuration.md) — the knobs.