This page lists every place model-facing text is defined, assembled, or handed to a provider. Use it when you change agent behavior, and when you review anything that puts untrusted content in front of the model.
"Injection" has two senses here. The first is what we inject: the system prompt and ephemeral blocks. The second is what others could inject: fetched pages, comments, child results, and model-authored tool descriptions. Because of the second list, every framed block that carries foreign text is escaped.
What we inject
Shared assistant instructions
File: agents/protocol/src/index.ts
seedAssistantSystemPrompt({currentTime?, contextLines?}) (line 14) holds the shared Seed instructions: HM resource types, hm:// link formatting, the exact-block-ID rule, profile and :profile reads, :attributes, reading the activity feed with read activity:, and directory-then-children exploration.
It takes no tool-specific options. There is no title-tool instruction. A separate model call generates session titles, and the agent holds no tool for it.
Agent system prompt assembly
File: agents/src/api-service.ts, Service.#agentSystemPrompt() (line 4169). In order:
the user-configured AgentDefinition.systemPrompt, stored as Seed blocks and converted to resolved markdown;
seedAssistantSystemPrompt({currentTime});
the memory prompt (line 4184). It is always included, because memory is a read and write address and belongs to no tool group. It describes ~/memory/, the check-memory-first habit, whole-file rewrites, {fromUrl} downloads, read ipfs://, write ipfs:// publishing, and that chat attachments are session-private and read with read attachment:<id>;
the user-actions prompt (line 4190): "Your user holds the same verbs you do… entries tagged <user_action>/<user_action_result> are actions the user ran themselves — read their results as shared ground truth";
the <space> index (see below), present whenever the call passes a stateDir, which agent runs always do;
<available_signing_identities> JSON plus signing and publishing instructions, only when the agent has signing keys. This includes the publish recipe and the parent-must-exist rule.
GetSession returns the fully assembled prompt as systemPromptMarkdown (line 5042) for UI inspection. That dialog is the ground truth for what the agent is being told.
The Space index
File: agents/src/api-service.ts, buildSpaceIndex() (line 5900).
Every system prompt has a <space> block, the Space index. It has one line per enabled tool document (- name — summary, with authored tools tagged (authored)), a one-line summary of the memory top level, and the names of active triggers. It is cached per (account, agent, callable set), invalidated on memory and tool writes, and collapsed to counts above 2048 bytes.
For review, this matters: an agent's own authored tool text lands in its prompt. A lambda's summary reaches the system prompt through this index. Its description (bounded at 16 KiB) reaches the model in full whenever the tool is expanded or promoted. Both are model-authored text that comes back as instruction-shaped context. This is intended, because the agent configures itself. It is also the one prompt source the agent writes.
Ephemeral per-turn blocks
None of these are stored as events. They exist only in the replay handed to Pi.
<plan_state>: planStateBlock() (line 414), appended as the last user message of every turn. The plan verb writes no transcript event, so this block is how a resumed model sees the checklist it published. Step ids and labels pass through escapeActionFraming(). A checklist that fully settled under an earlier run is not injected. The new turn retires it to the run that owned it (#retireSettledSessionPlan) and starts with no plan, so a new request never brings back a finished list.
<background_work_update> (line 4431): pushed when a park-resume leaves the replay ending on an assistant message, which Pi cannot continue from. It tells the model its children finished and to act on their results.
<window_context> (line 4914): the desktop's current window. It arrives as context content parts and is formatted by formatWindowContextLines() (frontend/apps/desktop/src/components/assistant-window-context.ts:39). It stays out of the durable message content, so transcripts stay clean.
<attachments>: formatAttachmentMetadata() (line 5970) lists name, MIME type, size, and id for each attached file, and never the bytes. Its guidance uses the verbs: read attachment:<id> to see an image or read a text file, write ~/memory/<path> with {fromAttachment} to keep one across sessions, and write ipfs:// with {fromAttachment} to publish one. It ends with "Only read what you need." Until 21a492a51 it named the deleted view_attachment, attachment_to_memory, and attachment_to_ipfs tools. This section exists to catch that kind of drift.
Durable system messages
These are written as real events with actor: 'system' (see actor), so they are part of the log record. They are not per-turn nudges:
continuationPrompt() (line 447): every open obligation at once, when a turn ends still owing something;
unmetObligationsNotice() (line 457): the closing notice when the continuation budget is spent.
Trigger-created sessions
File: agents/src/api-service.ts, triggerPromptMessage() (line 6138). The first user message of a triggered session carries three things. The first is the trigger prompt as resolved markdown. The second is a <trigger_context> JSON block with trigger, firing, and activity details. The third is a <trigger_instructions> block. It tells the agent to reply as a threaded comment with the write verb, and names where in the context to find the target document id and replyTo.
This is not a system prompt. It is model-facing prompt text built from activity that other people wrote, so review it together with system-prompt changes.
Delegated children
A model child's brief becomes its first user message word for word (renderSubSessionInput(), line 360). Non-strings fall back to a bare fenced JSON block, so nothing is reworded or hidden. The brief is the interface, and the parent model writes it.
spec.prompt replaces the child's system prompt entirely (line 2622). It is an anonymous worker persona that the parent model writes.
A typed child's return_result parameters are the schema the spawner declared, swapped in at session start (line 7938). See typed result.
Tool contracts
File: agents/protocol/src/tool-registry.ts
A tool's description and its JSON-schema field descriptions are model-facing instructions. For the five verbs they are long. The delegate description alone carries the parallelism rules and the whole ctx surface. The registry is shared with the desktop assistant runtime where the runtimes overlap. Contracts also reach the model at runtime through read ~/tools/<name> and through touch-expand misses on call.
contractMarkdownForThisServer() (line 7641) narrows the execute contract to the runtimes this server can run. The model is never told about a capability that would fail.
Session titling
#generateSessionTitle() (line 2966) runs one small model call with no tools. It has its own fixed system prompt ("You are a session-titling assistant…") and reads a digest of the conversation. It uses the shared provider runtime, so subscription-auth providers produce titles correctly.
Pi boundary
createSeedPiResourceLoader(systemPrompt) (line 6852) injects the assembled prompt through getSystemPrompt() and turns off every Pi discovery source. getAgentsFiles() returns none, getPrompts() returns none, getAppendSystemPrompt() returns an empty array, and skills, extensions, and themes are empty. With noTools: 'builtin', this stops hosted Agents from loading a local AGENTS.md, prompt templates, skills, extensions, or Pi's own host tools.
What others could inject
Ranked by exposure. Everything here is untrusted content that reaches the model.
Fetched web pages (read https://…, web_search results). This is the largest surface. A page the agent reads itself gets no escaping. It arrives as a tool result, which the model already treats as data.
Hypermedia content and comments (read hm://…, activity feed, trigger context). Other accounts write this. Trigger sessions are the sharpest case: the triggering comment is attacker-controlled text, and it arrives in the session's first message next to instructions.
User-action payloads and results: a user's read of a hostile page replays inside <user_action_result>. escapeActionFraming() (line 9179) escapes it. Content that could close the frame would forge a trusted user action for everything after it.
Plan step labels: written by the model and replayed inside <plan_state>. Same escape, same reason.
Child results: these come back to the parent as tool-result data, validated against the schema when typed. A prompt-injected child can corrupt only its own return value.
Authored tool descriptions: see the Space index section above.
Attachment file names: rendered into the <attachments> block without escaping.
App surfaces
Assistant panel (frontend/packages/ui/src/agents/assistant-panel.tsx, shared by the Seed app and the Seed web app) is a client of the agents service. It builds no system prompt of its own. It adds window context as context content parts on the first message. The old app-chat.ts / chat-provider-options.ts local-assistant prompt path is gone. See desktop UI.
Default new-agent prompt: a single Embed block of the Agent Guide (hm://hyper.media/agent/guide). See defaultAgentSystemPrompt in frontend/packages/ui/src/agents/dialogs.tsx. The user can edit it, and it becomes AgentDefinition.systemPrompt. When the service resolves the prompt, agents/src/docs-space.ts rewrites hm://hyper.media to the configured knowledge base account, and the service inlines the page. Agents created before the guide moved embed the old seed.hyper.media skill document, and that URL maps to the guide too. Anyone who can publish to the knowledge base space shapes every new agent's prompt.
Prompt tab (frontend/packages/ui/src/agents/detail.tsx) edits those blocks with the Seed block editor. The server normalizes them and resolves them to markdown before use.
System prompt dialog (frontend/packages/ui/src/agents/session.tsx) shows systemPromptMarkdown, the exact prompt that would be used to continue the session.
Change checklist
Update shared instructions first when behavior should apply everywhere.
Keep #agentSystemPrompt() assembly small and ordered. Every block you add costs context on every turn.
Use an ephemeral per-turn block for a reminder of current state. Use a durable event for a record of something that happened.
If a new block frames text written by anyone but us, escape it and add it to the untrusted list above.
Update tool descriptions in agents/protocol/src/tool-registry.ts when behavior depends on tool use.
Inspect a live session through GetSession.systemPromptMarkdown or the session UI dialog.
Update this page if a prompt source is added, removed, or moved.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime