Prompt Assembly¶
This page describes how Hive builds LLM context: system prompts, user messages, and what is deliberately excluded. Paths are relative to the repo root (src/hive/...).
Overview¶
flowchart TD
START["Daemon cycle: agent idle?"]
START -->|has active goal| P1["agent_cycle.py<br/>build Agent + pursuit_context"]
START -->|idle| G1["ExistenceLoop.generate_goal<br/>or GoalStrategy"]
P1 --> P2["Persona.build_system_prompt<br/>or profile.build_system_prompt"]
P2 --> P3["+ toolkit.instructions per bound toolkit"]
P3 --> P4{"Transcript exists?"}
P4 -->|no| P4a["bridge.py: user = goal + Context block"]
P4 -->|yes| P4b["Load transcript + system context note"]
P4a --> P5["agent.py: memory recall (PersistentMemory)<br/>ReAct loop"]
P4b --> P5
G1 --> G2["profile.build_system_prompt"]
G2 --> G3["existence._build_prompt user sections<br/>+ memory_snippets (semantic recall)"]
G3 --> G4["Single JSON response"]
P5 --> TOOLS["Tool schemas sent via<br/>provider API -- not prompt text"]
Two paths, one daemon:
| Path | Trigger | System prompt source | User message |
|---|---|---|---|
| Goal pursuit | Active goal in store | Persona or AgentProfile + toolkit instructions |
Goal objective + pursuit context |
| Goal generation | No active goal | AgentProfile.build_system_prompt |
Multi-section situation prompt |
Orchestration lives in daemon/agent_cycle.py. Runtime assembly is in runtime/agent.py, runtime/bridge.py, and agents/existence.py.
System Prompt Sources¶
The system prompt is built once when the runtime Agent is constructed (pursuit) or passed as the first message (generation).
AgentProfile (agents/profile.py)¶
Used when the profile has no persona block, and as the base for generation calls.
AgentProfile.build_system_prompt(economy_enabled) assembles:
- Agent name and role
- World framing (economy on vs off)
- JSON-format instruction
personality.traitsandpersonality.stylefrom YAML- Custom
system_promptfield from the profile - Skills -- markdown files from
skills/viaagents/skills.py(load_skills)
# agents/profile.py -- section order
"You are an autonomous agent named {name}."
"Role: {role}."
# economy or autonomous framing
"Always respond in the exact JSON format requested..."
# personality, system_prompt, skills
Persona (runtime/persona.py)¶
When a profile includes a persona: block, the daemon builds a Persona via Persona.from_profile (daemon/agent_context.py). Pursuit uses Persona.build_system_prompt instead of the raw profile builder.
Includes:
- Name, personality, values, fears, purpose, long-term goals
- Profile
system_promptmapped toPersona.context - Behavioral state lines (risk, concentration, social drive, autonomy, happiness) -- updated by
apply_suffering_effects()before each cycle - Optional
Instructionslist andbehavior_style
Toolkit instructions (tools/base.py)¶
Each Toolkit subclass may override the instructions property. The runtime collects non-empty instructions from all bound toolkits:
# runtime/agent.py
toolkit_instr = [tk.instructions for tk in self._toolkits if tk.instructions]
instruction_obj.build_system_prompt(toolkit_instr, response_model)
Examples: notepad presets inject journaling guidance; custom plugins can add domain rules here.
Structured output schema¶
When response_model is set, Instructions._response_schema_block appends a JSON Schema block to the system prompt (runtime/instructions.py).
Goal Pursuit Message Stack¶
When an agent has an active goal, AgentCycleRunner (daemon/agent_cycle.py) builds a runtime Agent and wraps it with DaemonAgentAdapter (runtime/bridge.py).
1. System prompt¶
# With persona (preferred when profile has persona: block)
Agent(..., persona=persona, toolkits=...)
# Without persona
Agent(..., system_prompt=profile.build_system_prompt(economy_enabled=...), toolkits=...)
Toolkit instructions are appended inside build_system_prompt.
2. Pursuit context (user message prefix)¶
On the first pursuit cycle for a goal, DaemonAgentAdapter.pursue_goal formats the task:
# runtime/bridge.py (first cycle only)
instruction = f"{goal}\n\nContext:\n{context}" # when context non-empty
On resume cycles (daemon.pursuit_resume: true), the adapter loads persisted messages from PursuitTranscriptStore and passes fresh daemon context as a system note instead of duplicating the user message:
The context / continuation_context string joins (agent_cycle.py):
| Block | Source | Notes |
|---|---|---|
| Identity preamble | agents/identity.py render_preamble |
Name, traits, chapters, narrative, worldview, opinions |
| Mood line | agents/mood.py MoodState.prompt_line |
Skipped during crisis |
| Suffering fragment | agents/suffering.py prompt_fragment |
Only when load >= threshold_prominent |
3. Conversation bootstrap¶
Agent._prepare_conversation (runtime/agent.py):
- Creates
ConversationMemorywithmax_messages = max_steps * 4 - Resume path: hydrates prior messages from the transcript store; optional continuation system note (see above)
- First cycle: if persistent memory is attached (daemon pursuit always wires
PersistentMemorybacked by the agent'sSemanticMemory), recalls up to 3 relevant entries and adds a system message (Relevant memories: ...) - First cycle: adds the task as a user message (the goal + pursuit context)
After each pursuit slice, the adapter persists the non-system message buffer back to SQLite. Transcripts are deleted when the goal completes or is abandoned.
4. ReAct loop¶
Each step sends conversation.get_messages() plus tool definitions via the provider API (Tool.to_schema()). Tool results append as tool-role messages. Guardrails (when enabled) run on input before the model and on output before return (runtime/guardrails.py).
Goal Generation (ExistenceLoop)¶
When idle, the daemon either calls a custom GoalStrategy or the default ExistenceLoop (agents/existence.py).
LLM call shape¶
messages=[
Message.system(profile.build_system_prompt(economy_enabled=...)),
Message.user(prompt), # from _build_prompt
]
Note: generation uses profile system prompt, not Persona.build_system_prompt, even when a persona exists. Persona behavioral fields are copied into the user prompt instead.
User prompt sections (existence._build_prompt)¶
Sections are joined in order:
| Section | Condition | Source |
|---|---|---|
| Identity intro | always | Agent name, role, autonomy framing |
| Economy framing | economy_enabled |
Inserted after role |
| Identity preamble | if present | IdentityManager.build_preamble |
| Notepad | if non-empty | Last 500 chars via NotepadManager.get_tail |
| Relevant memories | if any | Up to 3 semantic snippets from memory/recall.py |
| Economic status | economy on | WorldState.get_status |
| Agent stats | when stats available | Health, energy, happiness, reputation |
| Suffering | if prompt_fragment() non-empty |
Active stressors + thresholds |
| Behavioral state | if persona set |
Risk, social, concentration, autonomy, happiness, purpose, long-term goals |
| User nudges | if any | Pending nudges from store, sanitized via sanitize_operator_nudge (+ sanitized A2A pending) |
| Recent goals | if any | Last 5, status + objective (80 char trim) |
| Peer summaries | if any | Other agents' active goals (60 char trim) |
| Available tools | if any | Name + description list from toolkit factory |
| Available actions | always | Economy actions or messaging/memory |
| Task | always | JSON schema for {"goal": "...", "reasoning": "..."} |
Post-generation validation¶
_validate_goal rejects goals shorter than 10 chars, longer than 500, duplicates, or >80% overlap with recently abandoned goals.
GoalStrategy override¶
Pass HiveDaemon(goal_strategy=...) to replace ExistenceLoop entirely. The protocol receives a GoalContext dataclass with the same fields the default loop uses (agents/goal_strategy.py).
What Is NOT in Prompts¶
| Data | How agents access it |
|---|---|
| Workspace files | FileToolkit, ShellToolkit, GitToolkit under .hive/workspaces/{agent_id}/ -- contents are never pre-loaded |
| Full notepad | Only the last 500 characters in goal generation; full text via notepad_read tool |
| Tool JSON schemas in prompt text | Sent as native tool definitions to the model API |
| Raw SQLite rows | Goals, approvals, schedules accessed through tools and daemon logic |
| Other agents' inboxes | A2A/comms content arrives via tools; pending A2A subjects are summarized into nudges at generation time only |
| Plugin code | Executed when enabled; not injected unless the plugin toolkit sets instructions |
Workspace isolation is enforced in tools (tools/file/toolkit.py resolves paths under the workspace root). The LLM is told what tools exist; it must call tools to read files.
Limits and Truncation¶
| Context | Limit | File |
|---|---|---|
| Goal objective in peer summaries | 60 chars | daemon/agent_context.py |
| Recent goal objectives in generation | 80 chars | agents/existence.py |
| Recent goals listed | 5 | agents/existence.py |
| Generated goal length | 10--500 chars | agents/existence.py |
| Notepad tail in generation | 500 chars | tools/notepad/toolkit.py |
| Identity narrative in preamble | last 400 chars | agents/identity.py |
| Identity chapters in preamble | last 5 | agents/identity.py |
| Identity opinions in preamble | last 5 | agents/identity.py |
| Open questions in preamble | last 3 | agents/identity.py |
| Narrative storage before sealing | 800 chars (MAX_NARRATIVE) |
agents/identity.py |
| Pending A2A in generation | 3 messages | daemon/agent_cycle.py |
| Persistent memory recall (pursuit) | 3 entries | runtime/agent.py |
| Persistent memory recall (generation) | 3 entries | memory/recall.py via agent_cycle.py |
| Conversation buffer (pursuit) | max_steps * 4 messages |
runtime/memory.py |
| Pursuit outcome summary logged | 500 chars | runtime/bridge.py |
| Suffering in prompts | load >= 0.35 (threshold_prominent) |
agents/suffering.py |
| File read/write caps | 10 MB default | config.py tools.file_max_* |
Agent max_steps (standalone SDK) |
25 default | runtime/agent.py |
Agent max_steps (daemon pursuit) |
from profile (20 default) |
daemon/agent_cycle.py |
MAX_STEPS policy |
continue (keep goal active) |
config.py daemon.max_steps_policy |
Conversation truncation drops oldest message groups but keeps the first user message and preserves assistant+tool_result pairs (runtime/memory.py _truncate).
Related Pages¶
- System Overview -- surfaces and high-level diagram
- Daemon Mode -- six-phase cycle
- Persona System -- dynamic behavioral fields
- Suffering System -- stressors and prompt thresholds
- Architecture -- module map and config table