# Agent Core Runtime

> The reusable agent loop, event stream, tool-call execution model, harness layer, session storage, and compaction responsibilities.

- Repository: earendil-works/pi
- GitHub: https://github.com/earendil-works/pi
- Human wiki: https://grok-wiki.com/public/wiki/earendil-works-pi-121d322b171c
- Complete Markdown: https://grok-wiki.com/public/wiki/earendil-works-pi-121d322b171c/llms-full.txt

## Source Files

- `packages/agent/src/agent.ts`
- `packages/agent/src/agent-loop.ts`
- `packages/agent/src/harness/agent-harness.ts`
- `packages/agent/src/harness/types.ts`
- `packages/agent/src/harness/messages.ts`
- `packages/agent/src/harness/session/session.ts`
- `packages/agent/src/harness/session/jsonl-repo.ts`
- `packages/agent/src/harness/compaction/compaction.ts`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [packages/agent/src/agent.ts](packages/agent/src/agent.ts)
- [packages/agent/src/agent-loop.ts](packages/agent/src/agent-loop.ts)
- [packages/agent/src/types.ts](packages/agent/src/types.ts)
- [packages/agent/src/harness/agent-harness.ts](packages/agent/src/harness/agent-harness.ts)
- [packages/agent/src/harness/types.ts](packages/agent/src/harness/types.ts)
- [packages/agent/src/harness/messages.ts](packages/agent/src/harness/messages.ts)
- [packages/agent/src/harness/session/session.ts](packages/agent/src/harness/session/session.ts)
- [packages/agent/src/harness/session/jsonl-repo.ts](packages/agent/src/harness/session/jsonl-repo.ts)
- [packages/agent/src/harness/session/jsonl-storage.ts](packages/agent/src/harness/session/jsonl-storage.ts)
- [packages/agent/src/harness/compaction/compaction.ts](packages/agent/src/harness/compaction/compaction.ts)
</details>

# Agent Core Runtime

The agent core runtime is the reusable execution layer behind Pi’s agent behavior. It separates the low-level loop that streams model responses and executes tools from higher-level harness concerns such as sessions, resources, provider request hooks, queueing, and compaction.

This page is source-anchored to repository code. No `STRATEGY.md`, `docs/solutions/**`, or prior generated wiki pages were present in this checkout; the requested Compound Engineering guidance was used only as portable page-shaping guidance from the bundled profile metadata, not as a runtime dependency.

## Runtime boundaries

```mermaid
flowchart LR
  subgraph API["Public runtime APIs"]
    Agent["Agent\npackages/agent/src/agent.ts"]
    Harness["AgentHarness\nharness/agent-harness.ts"]
  end

  subgraph Loop["Core loop"]
    RunLoop["runAgentLoop / runLoop"]
    Stream["streamAssistantResponse"]
    Tools["executeToolCalls"]
  end

  subgraph Persistence["Harness persistence"]
    Session["Session"]
    JsonlRepo["JsonlSessionRepo"]
    JsonlStorage["JsonlSessionStorage"]
    Compact["prepareCompaction / compact"]
  end

  Provider["pi-ai streamSimple / completeSimple"]
  ToolImpl["AgentTool.execute"]

  Agent --> RunLoop
  Harness --> RunLoop
  Harness --> Session
  Session --> JsonlStorage
  JsonlRepo --> JsonlStorage
  Harness --> Compact
  RunLoop --> Stream
  Stream --> Provider
  RunLoop --> Tools
  Tools --> ToolImpl
```

`Agent` is the transient stateful wrapper: it owns current transcript arrays, runtime state, event subscribers, steering and follow-up queues, and calls the low-level loop. `AgentHarness` is the application-facing orchestration layer: it builds turn snapshots from persisted sessions, resolves resources and system prompts, wraps provider request hooks, persists messages, and exposes compaction/tree operations. Sources: [packages/agent/src/agent.ts:166-218](), [packages/agent/src/harness/agent-harness.ts:164-213](), [packages/agent/src/harness/agent-harness.ts:313-451]()

## Low-level loop model

The core loop accepts `AgentMessage[]` internally and converts to provider-compatible `Message[]` only at the LLM call boundary. `runAgentLoop()` starts with new prompt messages and emits start/message events for them. `runAgentLoopContinue()` resumes from an existing context and rejects empty contexts or assistant-terminal contexts. Sources: [packages/agent/src/agent-loop.ts:95-141](), [packages/agent/src/agent-loop.ts:275-307]()

The main `runLoop()` has two nested responsibilities:

| Responsibility | Behavior |
|---|---|
| Turn lifecycle | Emit `turn_start`, stream one assistant response, execute requested tools, emit `turn_end`. |
| Continuation decisions | Continue when tool results require another model call, steering messages are available, or follow-up messages arrive after the agent would otherwise stop. |
| Stop points | Stop on assistant `error`/`aborted`, `shouldStopAfterTurn`, or when no tool calls/queues remain. |
| Snapshot refresh | `prepareNextTurn` may replace context/model/thinking level before the next provider request. |

Sources: [packages/agent/src/agent-loop.ts:155-263](), [packages/agent/src/types.ts:198-243]()

## Streaming and event stream

`streamAssistantResponse()` optionally transforms `AgentMessage[]`, converts them to provider messages, builds the `Context`, resolves a fresh API key, then calls the configured stream function. Provider stream events update a partial assistant message and emit `message_start`, `message_update`, and `message_end`. Sources: [packages/agent/src/agent-loop.ts:275-360](), [packages/agent/src/types.ts:135-196]()

The public event contract includes agent, turn, message, and tool execution events. `Agent` reduces these events into state: streaming message, persisted transcript messages, pending tool call IDs, and the last error message. Listeners are awaited in order; run settlement happens after event listeners finish. Sources: [packages/agent/src/types.ts:397-419](), [packages/agent/src/agent.ts:231-234](), [packages/agent/src/agent.ts:509-550]()

## Tool-call execution model

Tools are selected from `AgentContext.tools`. A batch runs sequentially when the loop config is `"sequential"` or any called tool has `executionMode: "sequential"`; otherwise the loop prepares calls sequentially and executes allowed calls concurrently. Tool result messages are emitted in assistant source order after finalized calls. Sources: [packages/agent/src/agent-loop.ts:373-512](), [packages/agent/src/types.ts:245-276]()

Tool execution has clear stages:

1. Emit `tool_execution_start`.
2. Find the tool, optionally prepare arguments, validate arguments.
3. Run `beforeToolCall`; blocked or invalid calls become immediate error results.
4. Execute `AgentTool.execute`, forwarding abort signal and partial update callback.
5. Run `afterToolCall`, allowing field-level result overrides.
6. Emit `tool_execution_end`, then `message_start`/`message_end` for the tool result.

Sources: [packages/agent/src/agent-loop.ts:543-743](), [packages/agent/src/types.ts:345-383]()

```ts
// packages/agent/src/types.ts
export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {
  label: string;
  prepareArguments?: (args: unknown) => Static<TParameters>;
  execute: (
    toolCallId: string,
    params: Static<TParameters>,
    signal?: AbortSignal,
    onUpdate?: AgentToolUpdateCallback<TDetails>,
  ) => Promise<AgentToolResult<TDetails>>;
  executionMode?: ToolExecutionMode;
}
```

## Agent wrapper responsibilities

`Agent` provides a minimal reusable runtime API:

| API | Responsibility |
|---|---|
| `prompt()` | Start a new run from text, a message, or message batch. |
| `continue()` | Resume from current transcript if the last message is user/tool-result. |
| `steer()` | Queue a message to inject after the current assistant turn. |
| `followUp()` | Queue a message after the agent would otherwise stop. |
| `abort()` | Abort the active run’s `AbortController`. |
| `waitForIdle()` | Wait for the current run and awaited listeners. |

The wrapper creates loop configs from current state, including model, reasoning level, session ID, transport, dynamic API key resolver, tool hooks, and queue drain callbacks. Sources: [packages/agent/src/agent.ts:325-360](), [packages/agent/src/agent.ts:422-455](), [packages/agent/src/agent.ts:476-502]()

## Harness layer

`AgentHarness` turns the reusable loop into a durable application runtime. Each prompt/skill/template run starts by creating a turn state from the session context, current resources, session metadata, active tools, system prompt, model, thinking level, and stream options. It then runs `runAgentLoop()` with harness-specific hooks and a harness-created stream function. Sources: [packages/agent/src/harness/agent-harness.ts:313-451](), [packages/agent/src/harness/agent-harness.ts:526-616]()

Harness events and hooks cover queue updates, save points, provider request/payload/response hooks, tool-call hooks, compaction hooks, tree navigation hooks, model/thinking/resource updates, abort, and settled notifications. Sources: [packages/agent/src/harness/types.ts:493-657](), [packages/agent/src/harness/agent-harness.ts:969-1002]()

Provider neutrality is preserved by passing `Model`, stream options, headers, API keys, and `transport` through generic hooks and `streamSimple`; the harness does not assume a hosted service or specific provider. Sources: [packages/agent/src/harness/types.ts:81-105](), [packages/agent/src/harness/agent-harness.ts:358-388]()

## Session storage and context reconstruction

Sessions are append-only trees. `SessionTreeEntry` covers messages, model/thinking changes, compactions, branch summaries, custom entries/messages, labels, session info, and leaf pointers. The storage interface exposes metadata, leaf management, entry append/read, labels, path-to-root, and full entry listing. Sources: [packages/agent/src/harness/types.ts:366-485]()

`buildSessionContext()` reduces the active branch into model/thinking state and visible messages. If a compaction entry exists, it inserts a compaction-summary message and keeps messages from `firstKeptEntryId` onward. Sources: [packages/agent/src/harness/session/session.ts:21-75](), [packages/agent/src/harness/messages.ts:120-163]()

`JsonlSessionRepo` creates, opens, lists, deletes, and forks JSONL sessions under a sessions root grouped by encoded cwd. `JsonlSessionStorage` writes a header, appends entries as JSON lines, reconstructs the current leaf by replaying entries, and resolves the branch path by walking parent IDs. Sources: [packages/agent/src/harness/session/jsonl-repo.ts:75-159](), [packages/agent/src/harness/session/jsonl-storage.ts:136-158](), [packages/agent/src/harness/session/jsonl-storage.ts:191-258](), [packages/agent/src/harness/session/jsonl-storage.ts:275-288]()

## Compaction responsibilities

Compaction is a harness-owned structural operation that requires the harness to be idle. The harness prepares compaction from the current session branch, allows a `session_before_compact` hook to cancel or provide a result, runs summarization when needed, appends a compaction entry, and emits `session_compact`. Sources: [packages/agent/src/harness/agent-harness.ts:681-733]()

The compaction helpers estimate context tokens from provider usage when available, fall back to token heuristics, select a cut point that keeps recent context, preserve previous summaries, detect split turns, track file operations, and generate or update structured summaries with `completeSimple`. Sources: [packages/agent/src/harness/compaction/compaction.ts:112-198](), [packages/agent/src/harness/compaction/compaction.ts:455-537](), [packages/agent/src/harness/compaction/compaction.ts:541-704]()

## Branch navigation and follow-up queues

Tree navigation is separate from compaction. `navigateTree()` requires idle state, optionally summarizes the branch being left, moves the session leaf, and emits `session_tree`. During turns, `steer()` and `followUp()` are accepted only while non-idle, while `nextTurn()` queues messages for the next user-initiated turn. Abort clears steer/follow-up queues, aborts the active run, waits for idle, and emits `abort`; it does not clear `nextTurn`. Sources: [packages/agent/src/harness/agent-harness.ts:652-667](), [packages/agent/src/harness/agent-harness.ts:737-833](), [packages/agent/src/harness/agent-harness.ts:936-963]()

## Summary

The core runtime is intentionally layered: `agent-loop.ts` is the provider/tool/event engine, `agent.ts` is a lightweight stateful wrapper, and `harness/*` adds durable sessions, hooks, resources, provider request customization, tree navigation, and compaction. This keeps the loop reusable while letting host applications bring their own model registry, auth, tools, storage environment, and skill/resource sources. Sources: [packages/agent/src/agent-loop.ts:155-263](), [packages/agent/src/agent.ts:166-218](), [packages/agent/src/harness/agent-harness.ts:313-451]()
