Prompt Injection Map
A map of every place model-facing text is written, assembled, or sent to a provider, for anyone changing agent behaviour or reviewing where untrusted text reaches the model.

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(). The agents server passes no currentTime: the clock reaches the model as a per-turn block (below), so the system prompt stays byte for byte the same between turns and the provider's prompt cache holds;

    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";

    <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.

Nothing in this prompt depends on what the session has done or on the state of the agent's Space, so every request of a session starts with the same bytes. GetSession returns the assembled prompt followed by the current Space index 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 turn's input is followed by a <space> block, the Space index. It is one of the per-turn state blocks, not part of the system prompt: it changes whenever the agent writes a memory file or gains a tool, and a system prompt that changed with it would lose the provider's prompt cache for the whole conversation. 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. The first six are also not stored in the session's durable store: a hook adds them to every provider request of the turn, right behind the turn's input (turnStateMessages in #runPiAgent).

    <space>: buildSpaceIndex(), the Space index described above, as it stands when the turn starts.

    <delegation_budget>: delegationBudgetBlock(), how many children the run may still start. Absent until it has started one.

    <plan_state>: planStateBlock() (line 414), rendered once per 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.

    <context_usage>: contextUsageBlock(), how full the model's context was on the last turn, only where the run may continue the session.

    <session_status>: sessionStatusBlock(), the session's current title and description.

    <current_time>: the time the turn started.

    <background_work_update> (#importSessionLog): the turn's input when a park-resume has no new user message to answer. The children's results are already in place of their placeholders. It tells the model its children finished and to act on their results. A turn resumed after a restart with nothing else to answer gets <turn_resumed> in its place. Both are stored in the durable conversation and never in the log.

    <window_context> (line 4914): the desktop's current window. It arrives as context content parts and is formatted by formatWindowContextLines() (frontend/packages/ui/src/agents/assistant-window-context.ts). 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

The harness of a turn is built from an empty registry. #runPiAgent installs one extension in it, holding the assembled prompt as its only system prompt section, the turn's verbs as its only tools, and one hook. Pi Durable discovers nothing by itself: it reads no AGENTS.md, prompt templates, skills, or settings files, and its coding tools (read, write, edit, bash) are a separate extension that Seed never installs.

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.