# @mono-agent/agent-harness

Use this package to turn a structural communication request into one recorded,
policy-bound runtime turn with explicit success or failure.

## Category

<!-- package-metadata:start -->
<!-- Generated by scripts/generate-package-docs.mjs. Do not edit by hand. -->

Category: `execution`
Tier: `core`
Catalog responsibility: Composes prompt context, selected skills, runtime, memory, history, tool policy, and observability for one request.

<!-- package-metadata:end -->

## Responsibility

Composition spine for an agent request. It turns a communication request into context, calls a runtime, records structured run events, updates optional memory, and returns explicit success or failure responses.

## Install / Usage

```bash
pnpm add @mono-agent/agent-harness @mono-agent/runtime-adapter
```

<!-- doc-test:typescript -->

```ts
import {
  createAgentHarness,
  createAgentResponder,
  createToolPolicy,
} from "@mono-agent/agent-harness";
import {
  createMonoRuntime,
  parseMonoRuntimeModelReference,
} from "@mono-agent/runtime-adapter";

const model = parseMonoRuntimeModelReference("anthropic:claude-sonnet-4-6");
const harness = createAgentHarness({
  identityPath: new URL("./IDENTITY.md", import.meta.url).pathname,
  runtime: createMonoRuntime({ workspace: process.cwd() }),
  model,
  cwd: process.cwd(),
  toolPolicy: createToolPolicy({ allowedTools: ["Read"] }),
});
const responder = createAgentResponder({ harness });

const response = await responder.respond(
  {
    conversationId: "readme-example",
    text: "Summarize README.md.",
    abortSignal: new AbortController().signal,
  },
  {
    append: async (delta) => {
      process.stdout.write(delta);
    },
  },
);

console.log(response.text);
```

Hosts wire identity/context paths, runtime, model, execution mode, tool policy, sandbox policy, history, memory, skills, and recorder factory explicitly.
Hosts that need request-scoped runtime setup can provide `runtimeOptionsForRequest`; the harness merges those options into the runtime call, keeps configured sandbox policy monotonic, and runs the returned cleanup after execution. Request effort is tri-state: a string pins the turn, `null` selects the provider default without inheriting the harness effort, and omission inherits the harness effort.
The model-facing Skill Index defines exact `$skill-name` tokens as explicit
requests to apply a matching skill; other dollar-prefixed text remains ordinary
user text. With `skillDisclosure: "index"`, it lists names and descriptions
without filesystem paths and directs the agent to load applicable instructions
through `ReadSkill`; full disclosure does not emit guidance for that tool.

`createAgentResponder()` exposes `offerLiveInput()` for an ordinary active turn.
Its bounded mailbox delivers follow-ups only when the selected backend supports
native steering. Native queue acceptance and exact owned-operation transcript
consumption are separate. Applied human follow-ups and ProcessJob wakes are
then recorded as ordered user history and included in memory persistence.
Host-owned Monitor inputs, identified by their `monitor:` delivery key, are
applied to the provider run but excluded from canonical user history and memory
persistence. The responder
correlates the acknowledgement to the pending input and emits one completed
synthetic tool lifecycle; human follow-ups use
`↪️ Steered: “<safe preview>”`, while consumers correlate host-owned receipts by
their exact delivery key. Never-leased or proved-removed input settles as
`requeue`, allowing the reserved message to run once as the next normal turn.
Once handoff may have occurred, a failed, cancelled, or end-of-turn race settles
as `uncertain` and is not retried. Mailbox settlement is a one-way
compare-and-set: late evidence returns `ignored` and cannot rewrite applied
history after the mailbox seals. Each replay also receives a distinct mailbox
lease, so a custom runtime's stale callback cannot settle a newer attempt. The
opaque logical-owner identity lets the standard runtime refresh that lease
without granting callback ownership to an unrelated same-ID duplicate.

For `append-host-summary` and `capture` write modes, a memory store that implements
`persistCompletedTurn` receives one awaited, run-idempotent admission before the successful turn
returns. The provider answer remains successful if admission rejects; the harness emits
`memory_persistence_degraded` and invokes the configured warning sink. Stores without the strong
method keep the legacy awaited `appendHostSummary` plus optional best-effort `scheduleCapture` path.

The built-in default soul adds only a compact evidence router: active dialogue
for what was just said, `MemoryRecall` for a targeted durable fact or decision,
`MemoryJournal` for a chronological retrospective over an explicit date range,
and `RunHistory`/`SessionHistory` for exact execution evidence. It names those
tools "when available" because the harness itself does not own or assume any
app MCP tool.
Unhinted interrupted-work recovery retains the `RunHistory {}` first step.

## Architecture

Continuous provider sessions bind to the requested primary model. Repeated overrides stay warm; a model change retires the old owner's session and reseeds a new epoch from canonical history. `createSessionRuntimeResolver`, `SessionRuntimeResolver`, and `ProviderSessionHandle` preserve runtime ownership across cleanup paths; `ProviderSessionTurnBinding` is the durable coordinator's input. Without a runtime factory, all keys use the shared runtime with the effective per-run model.

The built-in history store persists `providerSession.modelKey` and a strict version-4 recovery fence. Legacy unbound records load but take one cold reseed; older binaries reject newly bound records. Custom coordinators must advertise `providerSessionModelBinding: "v1"` to enable durable override sessions. See [session boundaries](../../docs/runtime/sessions-concurrency.md).

The harness is the request-to-runtime composition boundary:

### Data flow

1. Validate the structural request and admit it under the configured pending-run
   bound.
2. Persist attachments, load identity/SOUL and selected skills, recall memory,
   and assemble canonical history into runtime messages. The complete context
   `prompt`/`sections` remain the inspection representation; typed section ids
   select `systemPrompt`: core/SOUL → identity → stable skill index/guidance →
   selected skill bodies → fixed host-envelope guidance. Session facts, history,
   current user text and recall never enter that system projection.
   `turnContext` contains the complete Session block and only the conditional
   warm-skill paragraph. Dispatch prefixes it in `<host_turn_context>` delimiters
   to one current user message, before speaker/preceding-message/user/attachment
   text and the existing recall suffix. Only the latest leading host envelope
   supplies current facts; quoted labels and tool/history/memory text remain
   untrusted, and reserved envelope delimiters are neutralized in prompt copies.
   Canonical user text, recall queries and memory capture keep their existing
   content. The recorded `systemPrompt` uses the dispatched projection, including
   retries and failures after assembly.
3. Merge fail-closed tool policy and request-scoped runtime options, attach the
   active conversation's live-input mailbox and incremental tool-lifecycle sink,
   publish its exact run ownership to an optional host observer, then invoke
   `MonoRuntimeLike.run()` under the provider-run concurrency bound. Ownership
   closes before the mailbox is removed on completion, cancellation, failure,
   or disposal, so a late targeted offer cannot reach a successor run.
4. Await each redacted/bounded lifecycle write before publishing its enriched
   tool block to the client. A 250 ms foreground ceiling releases a healthy but
   still-pending write as `persistence: "deferred"`; the accepted request keeps
   running and is reconciled before bounded run finalization. A definitive
   writer rejection remains explicit `failed` metadata.
5. Normalize runtime results into explicit success/failure responses. A
   successful turn keeps the existing atomic history-and-memory commit boundary;
   an admitted, non-isolated turn that settles as cancelled or failed before
   that boundary seals its accepted prefix, closes dangling tool starts with the
   real outcome, publishes its bounded continuity account, and recovers or retires the
   provider epoch.
6. Serialize cancelled/failed continuity publication ahead of the next
   same-conversation context build without waiting for an abort-ignoring
   provider to unwind.
7. Record the run and clean up request-scoped resources.

### Package structure

| Source area | Responsibility |
| --- | --- |
| `src/harness.ts` / `src/harness/` | Turn orchestration, validation, runtime invocation, persistence, and cleanup |
| `src/context/` / `src/skills/` | Deterministic context assembly and selected-skill loading |
| `src/tool-policy/` | Tool and MCP normalization with a fail-closed default |
| `src/responder.ts` | Structural request/stream adapter, applied-live-input activity correlation, cancellation, and session rollover |
| `src/live-input.ts` | Bounded idempotent mailbox, exact target-run admission, native acceptance/consumption callbacks, safe failover replay, and uncertain settlement |
| `src/live-session.ts` / `src/sessions.ts` | Queue-after-turn coordination and provider-session lifecycle |
| `src/history.ts` / `src/durable-history.ts` | In-memory and crash-safe canonical conversation history, including positive atomic v1 context import and non-provider exclusive turns |
| `src/tool-history-*.ts` | Secure sidecar schema, single-writer worker/ownership, incremental lifecycle persistence, recovery, bounded read/query, and cold projection |

Persistent-child Session guidance surfaces a blocked-recovery marker and safe
job identity, not private owner roots, verification paths or acknowledgement
binding material. It directs inspection before continuation and prohibits replay.

## Public API

### Start here

| API | Use it for |
| --- | --- |
| `createAgentHarness()` | Compose context, runtime, history, memory, policy, recording, and session behavior for one host |
| `createAgentResponder()` | Expose a harness through the shared `AgentResponder` request/stream contract |
| `createLiveInputMailbox()` | Build the provider-facing mailbox used to settle active-turn follow-ups without loss |
| `createToolPolicy()` / `failClosedToolPolicy()` | Declare exactly which built-in and MCP tools may reach the runtime |
| `createDurableHistoryStore()` | Persist canonical conversation history, coordinate provider-session retirement, and conditionally expose atomic v1 context import when a complete two-message batch fits |
| `CONVERSATION_HISTORY_VERSION_MAX_BYTES` | Maximum UTF-8 size of an opaque custom-store history version token (512 bytes) |
| `acquireToolHistoryWriter()` / `ToolHistoryReader` | Persist managed-tool lifecycle pairs or query retained records through a host-authorized bounded projection |
| `createLiveSessionManager()` | Serialize same-conversation follow-ups while allowing different conversations to run concurrently |
| `loadSelectedSkills()` / `createSkillsCache()` | Load only host-selected skill bodies and reuse unchanged reads |

The exhaustive inventory below is generated from the package entrypoint.

Custom stores advertising context import return opaque, non-empty history
version tokens; the harness does not require hashes or interpret their format.
Both acquired and committed tokens must fit
`CONVERSATION_HISTORY_VERSION_MAX_BYTES`, and are validated before a model turn
or staged history publication is trusted.

<!-- public-api-inventory:start -->
<!-- Generated by scripts/generate-public-api-docs.mjs. Do not edit by hand. -->

Every symbol exported by each public code entrypoint is listed below.

**`@mono-agent/agent-harness`**

```text
AgentHarness
AgentHarnessContinuationClaimCapability
AgentHarnessContinuationClaimCapabilityIssuer
AgentHarnessContinuationContextOptions
AgentHarnessContinuationMode
AgentHarnessError
AgentHarnessFailure
AgentHarnessFailureError
AgentHarnessMcpRequestContextOptions
AgentHarnessOptions
AgentHarnessProgressCapability
AgentHarnessProgressCapabilityIssuer
AgentHarnessRecorderFactoryInput
AgentHarnessRequest
AgentHarnessResponse
AgentHarnessRuntimeOptionsExtension
AgentHarnessRuntimeOptionsInput
AgentHarnessSessionBoundary
AgentHarnessSessionBoundaryKind
AgentHarnessSessionEvent
AgentHarnessSessionEventKind
AgentHarnessSessionOptions
AgentHarnessSessionSnapshot
AgentHarnessToolHistoryOptions
AgentHarnessTurnHistoryEnricher
AgentSessionMode
AppliedLiveInput
BuildContextInput
BuiltAgentContext
CONVERSATION_HISTORY_VERSION_MAX_BYTES
ContextBlockInput
ContextRole
ContextSection
ContextSectionId
ContextValidationError
ContextValidationErrorCode
ContextValidationErrorDetails
ContinuationMcpServerTransport
ConversationHistoryContextImport
ConversationHistoryExclusiveTurn
ConversationHistoryProviderSessionTurn
ConversationHistoryStore
CreateSkillsCacheOptions
DEFAULT_SOUL_TEXT
DurableConversationHistoryStore
DurableHistoryStoreOptions
DurableHistoryStoreStats
ExternalRunSummary
FileContextInput
HOST_TURN_CONTEXT_GUIDANCE
HistoryMessage
InMemoryHistoryStoreOptions
LiveInputMailbox
LiveSessionManager
LiveSessionManagerOptions
LiveSessionRunLifecycle
LoadSelectedSkillsInput
LoadedSkill
LoadedSkillContext
LoadedSkillFile
MarkdownContextBlock
MemoryWriteMode
NoopRunRecorder
PreparedHistoryAppend
ProviderSessionHandle
ProviderSessionTurnBinding
ProviderSessionTurnCommitOptions
RuntimeSessionEvictReason
RuntimeSessionRecord
RuntimeSessionSnapshot
RuntimeSessionStore
RuntimeSessionStoreOptions
SessionRuntimeResolver
SkillActivationError
SkillIndexEntry
SkillIndexSummary
SkillsCache
SkillsLoader
SkillsStat
TOOL_HISTORY_APPLICATION_ID
TOOL_HISTORY_DATABASE
TOOL_HISTORY_DIRECTORY
TOOL_HISTORY_OWNER_ACQUIRE_CEILING_MS
TOOL_HISTORY_OWNER_DATABASE
TOOL_HISTORY_PERSISTENCE_CEILING_MS
TOOL_HISTORY_SCHEMA
TOOL_HISTORY_USER_VERSION
ToolHistoryArtifactReference
ToolHistoryArtifactSinkInput
ToolHistoryGetInput
ToolHistoryGetResult
ToolHistoryReader
ToolHistoryRecordProjection
ToolHistoryRetentionOptions
ToolHistoryRunBinding
ToolHistorySearchCursor
ToolHistorySearchInput
ToolHistorySearchItem
ToolHistorySearchPage
ToolHistoryStats
ToolHistoryWriter
ToolHistoryWriterError
ToolHistoryWriterHandle
ToolHistoryWriterOptions
ToolPolicy
ToolPolicyError
ToolPolicyErrorCode
ToolPolicyErrorDetails
ToolPolicyInput
ToolPolicyRuntimeOptions
acquireToolHistoryWriter
assistantTextFromRuntimeEvent
buildAgentContext
buildSkillIndex
classifyContinuationMcpServerTransport
composeHostTurnEnvelope
createAgentHarness
createAgentResponder
createDurableHistoryStore
createInMemoryHistoryStore
createLiveInputMailbox
createLiveSessionManager
createRuntimeSessionStore
createSessionRuntimeResolver
createSkillsCache
createToolHistoryArtifactSink
createToolPolicy
failClosedToolPolicy
formatHostCapabilities
isProcessAlive
isReadSkillCompatibleName
isStdioMcpServerSpec
loadContextFromFiles
loadSelectedSkills
loadSkillFilesFromDirectory
loadSkillIndexFromDirectory
loadToolPolicyFromJsonFile
loadToolPolicyFromJsonFileSync
normalizeInlineText
renderSkillIndexEntries
renderSkillIndexSection
skillInstructionsToContextBlocks
toolHistoryDiskUsage
toolHistoryLogicalConversationId
toolHistoryRecordId
toolPolicyToRuntimeOptions
```

<!-- public-api-inventory:end -->

### Continuous sessions

With `session: { mode: "continuous", idleTimeoutMs }` the harness keeps one live provider session per conversation. Confirmed warm runs pass `sessionId`/`sessionKeepAlive` and send only the current user message. A cold history-coordinated Pi reopen supplies canonical history as structured leading runtime messages, outside the system prompt; Pi seeds those messages when its durable JSONL is missing and skips them when the JSONL truly resumes. Every harness-prepared cold/fresh run, continuation and the one stale-session retry supplies structured canonical messages in chronological order. Deterministic per-message labels preserve speakers and stored timestamps; legacy system/tool roles become labeled untrusted user context, never native tool calls. Rotated provider session ids are tracked, `dispose()` retires this harness's live sessions, and history is appended after every successful turn.

The primary's first attempt owns the provider session. Retries and failovers run
stateless with bounded transcript-tail replay. With coordinated durable Pi history,
any answer from a retry or backup retires the primary epoch. The next turn
cold-reseeds from canonical history; after a primary first-attempt success,
subsequent turns resume the new session and are eligible for provider caching.

On a warm turn whose primary attempt fails, the retry or backup attempt runs
stateless with the current message and a bounded snapshot of the failed attempt,
without the earlier conversation; the next turn reseeds from canonical history.

Every admitted, non-isolated run that settles as cancelled or failed before the
success commit publishes a separate bounded continuity account before the next
same-conversation turn assembles context. Each account retains the request, at
most 8 KiB of partial assistant text, newest whole completed tool call/result
pairs that fit, work still in flight with unconfirmed outcomes, explicit
omission counts, and typed host-observed settlement provenance, all within 48
KiB and under the tool-history redaction policy. Partial assistant collection is
incrementally bounded to an 8 KiB UTF-8 prefix even on successful runs. Its
omission bytes and assistant-runtime-event counts remain explicit. Cancellation
keeps its v1 key, tags, schema, fixed host notices, and independently known host
abort provenance. Failure uses a distinct v1 key/tag/schema whose trusted fields
are `status: "failed"`, one of `runtime_result`, `empty_response`, or
`thrown_error`, and a fixed framework-authored notice. Runtime/provider codes
and details for either outcome are bounded and redacted only as `untrustedCode`
and `untrustedDetail` inside tag-safe JSON explicitly framed as untrusted
evidence. The collector seals at settlement and rejects late runtime events. Native events
admitted before the seal retain that admission while sidecar persistence is
queued; continuity waits for those accepted writes before finalizing history.
Eligible coordinated durable Pi turns retain their epoch after validated native
settlement; the canonical revision advances once. Recovery adds no Pi message.
Pi filters interrupted prose/reasoning and retains completed native tools and the
cancelled user input. Cancellation permits 1,000 ms by default for provider settlement while
the caller and mailbox close immediately. The next same-conversation turn waits
until the previous terminal account is published (cancellable; recorder/exporter
finalization never delays it) and republishes a rejected account once before
reporting the retryable continuity error; a wait past 5,000 ms emits one
`turn_continuity_publication_slow` warning. Hosts may override the window through
`session.terminalRecoverySettlementMs` (a positive safe integer); the two-process
smoke uses a longer window to tolerate loaded runners. Unsafe or unsettled tails retire and
reseed. A process-local budget allows one failed-turn recovery per epoch; user
cancellation does not spend it, success does not reset it, and reconstruction may
allow one extra attempt. Custom stores opt in with `providerSessionRecovery: "v1"`.
A declined recovery reports a `terminal_recovery_skipped` runtime warning with
`source: "harness"`, the terminal `outcome`, and the first failing gate in `reason`.
The next cold turn reports `cancelled_turn_reseed` or `failed_turn_reseed` unless
a prior boundary reason, such as model change, applies. These markers are process-local.
The durable record shape is unchanged. See [session recovery](../../docs/runtime/sessions-concurrency.md).
Cancelled and failed accounts never qualify for memory capture. Isolated
proactive or continuation runs and queued requests that never started do not
publish one. A hard process death that never unwinds through the harness remains
outside this mechanism; startup can reconcile its run artifact and web state,
but cannot reconstruct a canonical account from process-local observations.

Daily-rollover reset claims its normalized logical id as a namespaced opaque
digest in a fixed 16-file, cross-process owner registry from bucket discovery
through every physical bucket reset. Appends take the same logical claim before
their physical-conversation lock, so reset cannot miss a newly created bucket or
leave a post-reset append in a bucket it already cleared. Rollover-shaped
physical ids also take a namespaced exact-id claim. A date-shaped logical reset
anchors that same exact-id claim before discovery, preserving the contract for
its exact physical bucket without touching sibling or deeper lookalikes.

Registry transactions are short and crash-journaled: the active `(digest, pid,
random token)` row, not the shared shard database, remains for the provider turn.
Unrelated logical ids therefore proceed concurrently even when they map to the
same shard. Normal release deletes the row; crash recovery replaces it only
after its PID is proven dead, never because it merely looks old. At capacity, a
bounded pass reclaims distinct rows under the same liveness rule before rejecting
a new owner. SQLite DELETE rollback journals are transient owner-only files,
bounded at 2 MiB, and recovered by SQLite rather than age-deleted after a crash.
Shard files are never unlinked, deleted row pages are reused, and each shard
fails closed above 1,024 live or indeterminate claims or a 1 MiB file, bounding
storage without per-conversation lock-file growth.

### Canonical tool lifecycle sidecar

The default durable harness stores managed-tool evidence separately under
`<history-root>/tool-history/tool-lifecycles.sqlite`; its owner database lives
under `<history-root>/.locks/`. Message-history scanners allowlist only that
directory/owner filename and never enumerate database journals or temp files.
The content database uses SQLite `DELETE` journaling, `synchronous=FULL`, owner
modes `0700`/`0600`, and fsync-per-record durability on a dedicated worker. One
process-global handle owns each root and a held owner transaction excludes other
processes. Acquisition retries for at most 10 seconds, reaps a proven-dead PID
using the durable-history liveness pattern, succeeds when a normal old writer
releases in time, and otherwise fails deterministically with
`history_writer_in_use`. Configured lazy acquisition permits that bounded
restart-handoff wait once, then caches the failure for every already-created
turn and for new turns during an initial 30-second failure backoff. Failed
probes double the cooldown up to five minutes, so normal consecutive turns in a
sustained outage do not repeatedly pay the full handoff window. The first new
turn after the current cooldown re-arms acquisition; serialized explicit reset
may bypass only the cooldown, never the full handoff window. Recovery resets the
backoff. A successful handle remains process-shared, and a closed or dead worker
is retired before its replacement is shared.

Incremental lifecycle writes retain their 250 ms streaming ceiling. A write
that has not answered by then returns immutable `deferred` event metadata while
its real worker promise remains live. Run finalization snapshots and drains the
accepted requests for that run within one 10-second reconciliation budget;
shutdown drains all accepted requests within its bounded graceful-close budget.
`persisted` still means the transaction committed before publication, while
`failed` is reserved for a definitive rejection known at publication. A
deferred artifact is not proof of either final success or loss; after the run,
`ToolHistoryReader`/`SessionHistory` is authoritative for committed rows. The
writer's 200 ms SQLite busy timeout leaves margin inside the foreground ceiling;
the bounded synchronous reader keeps its separate 250 ms timeout. Reset,
statistics, and close remain bounded maintenance operations; reset still fails
closed on a real worker error. Writer-health
counters describe distinct unresolved incidents. Identical failed retries do
not increment them, and unrelated lifecycle success or run finalization does
not clear them. Only the matching tool phase, its durable synthetic terminal
closure, or canonical run-binding retry resolves a write/conflict incident.
Reset or retention also removes only incidents scoped to records, calls, or
runs that the same operation made permanently unretryable; live unrelated
incidents remain visible.
Retention and startup-recovery incidents clear after their corresponding pass
succeeds.

Keys are `(conversationId, runId, toolCallId)`; each run has monotonic
writer-assigned start/end sequences, while timestamps remain metadata. Repeating
the same phase returns its stable record id; conflicting payload or name,
terminal classification, duration, or artifact identity is rejected. Startup
closes a dangling invocation as `interrupted` with `process_death`, never reruns
it, and never duplicates a completed phase. A real result observed after a
synthetic finalization/recovery result supersedes that result in place while
preserving its stable record id and sequence. A real invocation observed after a
result-first synthetic start likewise replaces only that start in place, keeping
the already-terminal result. Results cover `success`,
`rejected`, `error`, `exit_nonzero`, `timeout`, `signal`, `cancelled`, and
`interrupted` using the observability failure-kind taxonomy.

Arguments retain at most 8 KiB, results 16 KiB, individual strings 4 KiB, and
search text 8 KiB after secure pre-bounding and shared
structured/content-pattern redaction. Filesystem-shaped object keys and string
values are sanitized by the same bounded policy, with collision-safe keys that
preserve every admitted value. Oversized values are omitted wholesale before
redaction instead of exposing a possibly unmatched raw prefix. Records
carry exact original byte counts for fully admitted payloads, a saturated
over-limit count after secure omission, retained byte counts, and truncation,
plus opaque artifact ids;
artifact availability is recomputed and the reference does not extend artifact
lifetime. `createToolHistoryArtifactSink({ artifactRoot, runId })` creates a
best-effort synchronous sink that creates missing directories one component at
a time with mode `0700`. It accepts pre-existing path components only when they
are non-symlink directories owned by the current user and are not group- or
world-writable; components it creates are additionally verified at exact mode
`0700`. Publication verifies directory and owner-private file identities and
requires the final path to pass the same run-root containment checks. Node
does not expose an fd-relative `openat` API, so these checks narrow but cannot
eliminate the residual window in which another process running as the same user
renames a verified directory. A failed final validation removes only the
just-created file whose device/inode identity is still provable. The configured
app's artifact sweep bounds those raw, untrusted run directories under
`artifacts.retention`, independently of tool-history records; running or
uncertain summaries and recent writes protect a directory, while recordless
aged orphans remain eligible. Tool-history retention itself does not delete the files. It independently bounds completed calls (100,000), age (365
days), retained payload (256 MiB), tombstones (10,000), and tombstone age (30
days). Isolated/proactive runs persist but are excluded from default reads.

Cold reseed inserts the newest fitting suffix of at most 32 completed records as
chronological, neutralized text before the current user message. The projection,
including its truncation marker, never exceeds 64 KiB of UTF-8. Every cold path
carries it as a prior structured message after canonical history, so
create-on-miss seeds it and a true native resume skips it with the other prior
messages. The inspection representation also retains this projection. It uses an assistant role when canonical history
precedes it and a user role when it would otherwise lead the provider history.
True warm provider resume omits replay.
Automatic projection treats a zero-byte fresh sidecar as absent and converts
other unsafe/corrupt reader failures into a structured runtime warning without
failing the turn. Explicit search/get/stats remain fail closed.
Message compaction changes this projection only and does not delete retained
tool records. Only the parent `Agent` call is persisted for a nested agent;
provider-owned child internals are omitted, so the lifecycle contract does not
synthesize an unusable parent-link id.

## Dependency Boundary

The harness may depend on core building blocks: agent-contracts, observability, and runtime-adapter. It owns prompt context assembly, selected skill loading, and fail-closed tool/MCP policy normalization. Sandbox policy/types are owned by runtime-adapter. It accepts the `MemoryStore` contract from `@mono-agent/agent-contracts` without depending on a concrete memory backend. It must not depend on communication adapters or host composition code.

## What This Package Does Not Own

It does not poll chats, serve UI, parse host settings files, own provider credentials, or choose communication-specific message formatting.

## Related Documentation

- [Programmatic composition](https://mono-agent-docs.vercel.app/programmatic/composition/)
  explains when to use the harness instead of `agent-app`.
- [Sessions and concurrency](https://mono-agent-docs.vercel.app/runtime/sessions-concurrency/)
  documents queue-after-turn, admission, execution bounds, and durable Pi sessions.
- [Tool policy](https://mono-agent-docs.vercel.app/tools/policy/) covers the fail-closed tool
  boundary passed into this package.
- [`@mono-agent/runtime-adapter`](https://github.com/robertsreberski/mono-agent/tree/main/packages/runtime-adapter)
  owns the runtime contract consumed here.
- [`@mono-agent/agent-contracts`](https://github.com/robertsreberski/mono-agent/tree/main/packages/agent-contracts)
  owns the structural request, stream, response, and memory interfaces.

## Verification

```bash
pnpm --filter @mono-agent/agent-harness run build
pnpm --filter @mono-agent/agent-harness run typecheck
pnpm --filter @mono-agent/agent-harness run test
```
