# Input and prompt assembly

## What it does

`createDefaultInputBuilder()` turns common host input into Prism `Message[]` without starting an agent loop or calling a provider. It accepts strings, `Message`, or `Message[]`, and can add host-supplied instructions, history, summaries, attachments, explicit text resources, tool results, and metadata. It never applies `input_assembly` middleware itself: `assembleProviderInput()` owns that hook and runs it exactly once after whichever `InputBuilder` is installed returns, so a custom builder cannot bypass it.

`createDefaultPromptBuilder()` composes messages, context blocks, selected skills, and host-supplied active tools into provider-ready messages. The `Available tools:` text listing is emitted only for models without declared tool support (`model.capabilities.tools !== true` — unknown capability keeps it, fail-safe for text-only providers); tool-capable models receive schemas via `request.tools` and skip the duplicated text. `assembleProviderInput()` wires input assembly, ordered context resolution, prompt middleware, and prompt composition into a `ProviderRequest` without calling a provider. Layered system prompts are composed before this helper and passed as `systemInstructions`. `renderPromptTemplate()` expands tiny `{{name}}` variables for CLI/RPC prompt strings before input assembly.

## When to use it

Use it when a host wants the boring default shape before a later prompt builder or provider request step. Use `renderPromptTemplate()` when CLI/RPC callers need simple variable replacement before sending a string to the input builder. Use a custom `InputBuilder` or `PromptBuilder` when an app has its own message or prompt policy.

Do not use it for tool execution, provider calls, file discovery, credential lookup, package activation, template logic, or an agent/session runtime.

## Inputs / request

```ts
import { createDefaultInputBuilder } from "@arnilo/prism";

const messages = await createDefaultInputBuilder().build("Summarize", {
  systemInstructions: "Be accurate.",
  developerInstructions: "Cite supplied context only.",
  history,
  attachments: [{ name: "notes.md", text: "# Notes" }],
  toolResults: [{ toolCallId: "call_1", name: "lookup", value: { ok: true } }],
  metadata: { requestId: "r1" },
});
```

Prompt templates:

```ts
import { renderPromptTemplate } from "@arnilo/prism";

const prompt = renderPromptTemplate("Review {{file}} for {{focus}}", {
  file: "src/index.ts",
  focus: "public exports",
});
```

Prompt/provider assembly:

```ts
import { assembleProviderInput, createDefaultPromptBuilder } from "@arnilo/prism";

const request = await assembleProviderInput({
  model: { provider: "mock", model: "demo" },
  input: "Explain this file",
  inputLayout: "cache_aware",
  contextProviders: [projectContext],
  promptBuilder: createDefaultPromptBuilder(),
  tools: activeTools,
});
```

Useful exported types:

- `AgentInput`: `string | Message | readonly Message[]`.
- `DefaultInputBuilder`: the default `InputBuilder` with typed default context.
- `InputAssemblyLayout`: `"legacy" | "cache_aware"`; `cache_aware` is default.
- `DefaultInputBuildContext`: optional input layout, instructions, history, summaries, attachments, resource loader/URIs, tool results, middleware, ids, metadata, and abort signal.
- `InputAttachment`: already-loaded text/content blocks (including `audio`, `file`, and `document`) or an explicit URI loaded through a caller-provided `ResourceLoader`.
- `PromptInstruction`: labeled system instruction text.
- `DefaultPromptBuilder`: the default `PromptBuilder`; cache-aware by default and legacy-preserving when `inputLayout: "legacy"` is passed in its request.
- `AssembleProviderInputOptions`: model, input, optional builders, context providers, selected skills, active tools, generic provider options, metadata, signal, optional session-owned `tailSegments`, and optional `contextBudget` (`maxInputTokens` / `maxInputBytes` / `reportOmissions` / `tokenEstimator`).
- `applyContextBudget` / `getContextBudgetReport` / `resolveContextBudget`: deterministic eviction + omission report helpers (estimate = UTF-16 code units ÷ 4, or the host's `tokenEstimator`).
- `PromptTemplateOptions`: missing-variable behavior for `renderPromptTemplate()`.

## Outputs / response / events

The builder returns `readonly Message[]`.

- String input becomes one user text message.
- `Message` and `Message[]` input are preserved.
- `cache_aware` layout is the default. Set `inputLayout: "legacy"` on the default builder, `assembleProviderInput()`, `AgentConfig`, or `RunOptions` to restore the prior order.

| Layout | Input message order |
| --- | --- |
| `legacy` | instructions → summaries → history → current input → attachments/resources → tool results |
| `cache_aware` | instructions → attachments/resources → summaries → history → tool results → current input |

The default prompt builder preserves one composition path while honoring layout:

- `cache_aware` (default): leading system messages from input assembly → resolved context blocks → selected/progressively disclosed skill catalogs → text tool declarations for text-only/unknown models → remaining input-builder messages (attachments/resources → summaries → history → tool results → current input) → optional session tail.
- `legacy`: context blocks → skills → text tool declarations → all input-builder messages (instructions → summaries → history → current input → attachments/resources → tool results).

In cache-aware mode, leading system instructions form the stable boundary before dynamic context and skill catalogs. The provider `tools` field remains the host-supplied schema list; text declarations are only a fallback for models without declared tool support. `RuntimeAgentSession` supplies a run-owned `tailSegments` map: URI resources and loaded skill bodies move to the final tail, while their catalog rows remain in place. First insertion fixes tail order (`resource:<uri>` / `skill:<name>`); re-derivation of an id replaces only that segment's bytes, making changed source content an explicit cache-invalidation boundary. Context-budget eviction can omit a tail segment. Custom prompt builders receive the tail in `messages` plus `tailSkillBodies`; a builder that independently renders `skills` must honor that flag. A stable prefix persists only while those stable inputs stay byte-stable; provider cache hits remain best-effort.
- History is prepended before current input.
- Instructions and summaries are system messages; compacted branch summaries from `rebuildSessionContext()` use the same path.
- Text attachments and explicit text resources are user messages; inline `audio`/`file`/`document` blocks pass through unchanged on attachments with `content`.
- Tool results are tool messages containing `tool_result` content; the agent/session runtime uses this to feed dispatched tool results into the next provider turn, placing the assistant `tool_call` and the matching role `tool` `tool_result` before any final assistant content. Cache-aware layout keeps tool results before the current user suffix so it does not split tool transcripts. A result with no `value`, no `type:text` content, and no error carries the constant `EMPTY_TOOL_RESULT_TEXT` (`"(tool completed with no output)"`) as its `result`, so no provider route serializes an empty or absent tool payload.
- Middleware runs only when `middleware` is supplied in the context.
- `assembleProviderInput()` returns a `ProviderRequest` with the caller's model/tools/provider options/metadata/signal and composed messages/context. It stamps missing `sessionId`/`cacheKey` via `applyDefaultProviderRequestOptions` when `sessionId` is passed (agent sessions always pass `session.id`). It also calls `assertMessagesSupportModelCapabilities()` so unsupported `audio`/`file`/`document`/`image` blocks fail with `UnsupportedModalityError` when the model declares `capabilities.input`.
- Optional `contextBudget` (at least one of `maxInputTokens` / `maxInputBytes`) runs after default message groups are built and before final flatten. Sessions forward `AgentConfig.contextBudget` here, so an agent-level budget gets the same semantics. `tokenEstimator` replaces the built-in ÷4 heuristic for **eviction accounting** — and, with `usageEstimation: "fallback"`, also feeds the missing-usage estimate ([Runs and usage](runs-and-usage.md#automatic-fallback-agentconfigusageestimation)); it never reaches billing, provider usage reports, or the wire, byte caps (`maxInputBytes`) stay estimator-independent and are always enforced, and an estimator that returns a non-finite or negative count (or is not a function) fails the assembly closed with `TypeError` instead of making eviction decisions unsound. Eviction drops droppable sections first (toolResults → history → summaries → context → skills → attachments; layout-aware). Within `history`, oldest messages drop first. Protected instructions + current user `input` (+ tools catalog) fail closed with `ContextBudgetError` if they alone exceed the budget. When `reportOmissions: true`, attach `ProviderRequest.metadata[CONTEXT_BUDGET_REPORT_METADATA_KEY]` and read via `getContextBudgetReport(request)` (kinds/ids/sizes only — no secrets). Raw session store entries are never deleted.
- `renderPromptTemplate()` replaces top-level `{{name}}` variables with caller-supplied JSON-compatible values. Strings are inserted directly; numbers, booleans, `null`, arrays, and objects are stringified deterministically with sorted object keys. Missing variables throw by default or stay unchanged with `{ missing: "preserve" }`.

## Request/response example

```json
{
  "template": "Review {{file}} for {{focus}}",
  "variables": { "file": "src/index.ts", "focus": "public exports" },
  "rendered": "Review src/index.ts for public exports"
}
```

```json
{
  "input": "Hello",
  "context": {
    "systemInstructions": "Answer briefly.",
    "attachments": [{ "name": "notes.md", "text": "Remember the release date." }]
  }
}
```

```json
[
  { "role": "system", "content": [{ "type": "text", "text": "System instruction:\nAnswer briefly." }] },
  { "role": "user", "content": [{ "type": "text", "text": "Attachment notes.md:\nRemember the release date." }] },
  { "role": "user", "content": [{ "type": "text", "text": "Hello" }] }
]
```

## Implementation example

```ts
import { createDefaultInputBuilder, createMiddlewareRegistry, renderPromptTemplate } from "@arnilo/prism";

const middleware = createMiddlewareRegistry();
middleware.use("input_assembly", (messages) => messages);

const prompt = renderPromptTemplate("Review {{resource}}", { resource: "package://demo/prompt.md" });
const messages = await createDefaultInputBuilder().build(prompt, {
  resourceUris: ["package://demo/prompt.md"],
  resourceLoader: {
    async load(uri) {
      return { uri, text: "Host-loaded resource text." };
    },
  },
  middleware,
});

await session.run("Explain this", { inputLayout: "cache_aware" });
```

Cache-aware mode is the default. Set `inputLayout: "legacy"` when compatibility with the prior whole-prompt order is required.

## Extension and configuration notes

Extensions can contribute `InputBuilder`, `PromptBuilder`, and `ContextProvider` objects through the extension API, but contributions stay inert until the host resolves and calls or passes them. The agent/session runtime uses configured builders/providers only when the host puts them on `AgentConfig`; it does not load extensions or registries itself. Defaults are built-ins; hosts can replace them with compatible builders. Prompt templates are caller-side string expansion only; they do not load resources or contributions.

```ts
const kernel = createExtensionKernel();
await kernel.load([extension]);

const request = await assembleProviderInput({
  model: { provider: "mock", model: "demo" },
  input: "Hello",
  inputBuilder: kernel.registries.inputBuilders.resolve("custom-input"),
  promptBuilder: kernel.registries.promptBuilders.resolve("custom-prompt"),
  contextProviders: [kernel.registries.contextProviders.resolve("project")],
  middleware: kernel.middleware,
});
```

`input_assembly`, `context`, and `prompt_build` middleware are not global. They run only for helper calls that receive a `MiddlewareRegistry`, in that assembly order. Inside `assembleProviderInput()`, `input_assembly` always runs — for both the default and any custom `InputBuilder`, and on both the plain and context-budget paths. `assembleProviderInput()` keeps provider `tools` equal to the host-supplied active tool list after prompt middleware.

## Security and performance notes

- Input grouping and default prompt composition are linear in supplied messages, attachments, resources, context blocks, skills, and tools. Layout selection is one branch over already-built groups; no message sorting or canonicalization is performed.
- Template expansion is dependency-free string replacement over `{{name}}` variables. It does not evaluate expressions, filters, loops, partials, JavaScript, globals, or prototype properties.
- It performs no provider calls, tool execution, credential resolution, package discovery, filesystem scan, network access, timers, or watchers.
- URI attachments/resources load only through the caller-provided `ResourceLoader`. Binary media uses `resolveMediaContentBlock()` / `loadBinaryResource()` with bounded bytes, SSRF checks for URLs, and MIME magic validation — see [Multimodal content](multimodal-content.md).
- Do not place secrets in templates, variables, instructions, messages, attachments, tool results, metadata, middleware payloads, or docs examples.
- Active tools are passed through from the host; prompt middleware cannot grant additional provider tools.
- Skill selection is handled by the host/skill registry path; this builder only includes selected skills passed by the caller.

## Related APIs

- [SDK customization guide](customization.md): high-level map of replaceable provider resolution, middleware, context, builder, injector, loop, compaction, retry, store, and skill seams.
- [Public contracts](public-contracts.md): `Message`, `ContentBlock`, `InputBuilder`, `InputBuildContext`, `ToolResult`, and `ResourceLoader` shapes.
- [Context and skills](context-and-skills.md): ordered context resolution feeding prompt composition.
- [Multimodal content](multimodal-content.md): `audio`/`file`/`document` blocks, bounded media resolution, and capability checks.
- [Resource loading](resource-loading.md): `loadTextResource()` and `loadBinaryResource()` behavior used for explicit URI resources.
- [Middleware hooks](middleware-hooks.md): ordered middleware registry and `input_assembly`, `context`, and `prompt_build` hooks.
- [System prompts](system-prompts.md): compose layered package/app/user/run prompts before input assembly.
- [Contribution registries](contribution-registries.md): inert input, prompt, context, and skill contributions.
- [Agent/session runtime](agent-session-runtime.md): calls assembly each turn and supplies runtime tool results to the next provider request.
- [Tools](tools.md): host-owned tool registry and tool result boundary.
- [Compaction and retry policies](compaction-and-retry.md): default compaction strategy that feeds summaries into input assembly.
- [Attention compiler](attention-compiler.md): opt-in per-turn ratio gate that strips old thinking and stubs old tool results on the assembled groups before they reach the prompt builder.
