# @mono-agent/agent-contracts

Adapter-neutral types and small boundary helpers shared by mono-agent hosts,
runtimes, channels, and operator clients. Use this package when two packages
need to exchange a request, stream a reply, run a channel driver, or share safe
configuration and HTTP primitives without depending on one another.

## Category

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

Category: `core`
Tier: `core`
Catalog responsibility: Defines shared structural request/response contracts plus adapter-neutral settings JSON, env, safe-bind, and bearer helpers.

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

## Responsibility

Define the structural request, response, stream, in-flight follow-up,
channel-driver, process-job projection, and memory contracts that packages can implement independently. It also owns
small dependency-free helpers for settings JSON, JSON-to-env mapping, safe
network binding, bearer tokens, attachments, and stream framing.

## Install / Usage

```bash
npm install @mono-agent/agent-contracts
```

```ts
import type { AgentResponder } from "@mono-agent/agent-contracts";

export const responder: AgentResponder = {
  async respond(request, stream) {
    request.abortSignal.throwIfAborted();
    const text = `Received: ${request.text}`;
    await stream.append(text);
    return { text };
  },
};
```

An adapter extends `AgentRequestBase` with transport metadata, supplies an
`AgentMessageStream`, and calls `AgentResponder.respond(request, stream)`. The
returned `AgentResponse` is the terminal result; stream callbacks carry visible
text and structured progress while the request is running.

Managed `tool_call_started` and `tool_call_completed` events may carry
`SessionToolHistoryEventMetadata`. It is the host writer's result for that exact
block—stable record/sequence, persisted-or-failed status, terminal state,
truncation byte counts, opaque artifact availability, and an untrusted marker.
Web/TUI clients render this metadata directly; they do not re-derive canonical
history from run artifacts or their own stores. `persistence: "failed"` is an
explicit fail-soft diagnostic and does not change the tool's provider outcome.

Responders may also implement `offerLiveInput()`. An adapter can then offer one
plain-text follow-up to the active conversation without starting a parallel
turn. The immediate result says whether the active run accepted ownership; the
settlement later reports `applied`, `requeue`, or `discarded`, so the adapter can
preserve its ordinary queue position across provider and end-of-turn races.

`AgentResponse.parts` carries adapter-neutral attachment, MCP App, and per-part
failure references. `MAX_AGENT_REPLY_PARTS` is the shared producer/wire ceiling
of 20, and `DEFAULT_AGENT_ATTACHMENT_MAX_BYTES` is 20 MiB. Streams that cannot
represent a part default to concise human fallback; machine and verbatim
adapters pass `unsupportedPartFallback: "none"` so reply text is not mutated.
Artifact/app bytes and HTML remain behind responder authorization methods rather
than entering stream frames. See
[Reply files and MCP Apps](https://mono-agent-docs.vercel.app/tools/rich-replies/).

Machine destinations project unsupported parts through the shared
`AgentReplyPartDeliveryOutcome` contract. `sanitizeReplyPartDeliveryOutcomes()`
accepts an unknown runtime or persisted value and rebuilds a fresh dense array
of at most 20 fixed-shape records. Sparse holes, primitives, accessors, failed
descriptor reads, unknown enums, and extra fields become a safe `unknown`
failure; supplied indices and messages are never copied. A non-array or empty
value is omitted. More than 20 source entries become 19 individual records plus
one counted aggregate at index 19.

The ordinary record shape is:

```json
{
  "partIndex": 0,
  "partType": "attachment",
  "status": "failed",
  "code": "unsupported_destination",
  "message": "Attachment reply parts are unsupported on this destination."
}
```

`partType` is `attachment`, `mcp_app`, `failure`, or `unknown`; `status` is the
terminal literal `failed`; and `code` is one of the closed
`AgentReplyPartFailure.code` values. Only the final overflow record adds
`affectedPartCount`, uses `partType: "unknown"` and
`code: "reply_part_too_large"`, and keeps the same five required fields.
The arrays embedded directly in adapter responses are additive unversioned
fields. A2A and cron's private durable SQLite copy wrap the same array as
`{ "schemaVersion": 1, "replyPartOutcomes": [...] }`. Exact adapter field and
projection names are documented in
[Reply files and MCP Apps](https://mono-agent-docs.vercel.app/tools/rich-replies/#machine-delivery-outcome-wire-contract).
Cron detail projections retain all 20 records. Compact cron summaries retain
the first eight in stable part order so a maximum 100-run page remains below
the operator response ceiling.

## Architecture

### Data flow

The core turn boundary is deliberately structural:

1. A channel normalizes transport input into an `AgentRequestBase`, including a
   required `AbortSignal`.
2. The host calls `AgentResponder.respond(request, stream)`.
3. While that response is active, the adapter may offer bounded live input and
   retain normal-turn admission until the offer settles.
4. The responder sends deltas, replacement text, status, and telemetry through
   `AgentMessageStream` and returns an `AgentResponse` when the turn settles.
5. A `ChannelDriver` combines config loading and the responder with a transport,
   returns a `RunningChannel`, and reports `ChannelStatus` transitions to the
   host. Runtime controls publish replacement summaries through
   `ChannelStartInput.onSummaryChanged`; the host applies the status/discovery
   transition instead of a driver mutating shared summary state.

An addressable driver may additionally declare one unique
`processJobs.conversationScheme` and publish `RunningChannel.processJobs`.
`update` renders lifecycle state without invoking the model; `wake` must return
an honest `steered` or `follow_up` receipt for the completion turn. Drivers that
do not opt in remain ineligible for background schemas and delivery.

### Package structure

Primary modules:

| Area | Modules | Purpose |
| --- | --- | --- |
| Turn contracts | `index.ts`, `types.ts` | Requests, responses, attachments, live-input ownership/settlement, cancellation, and settings value shapes. |
| Channel lifecycle | `channel.ts` | Driver startup, running handles, status, notifications, and interaction hooks. |
| Message delivery | `buffered-message-stream.ts`, `resilient-message-stream.ts`, `stream-text.ts`, `tool-hints.ts` | Collect or safely adapt incremental output and format bounded activity copy. |
| Process transport | `stream-wire.ts` | NDJSON stream frames for operator clients. |
| Process-job projection | `process-jobs.ts` | Neutral lifecycle/error enums, stable public safety/cleanup messages, strict secret-free projection parsers, and the owner-authorized operator interface. |
| Shared safety helpers | `host-safety.ts`, `bearer.ts`, `http-headers.ts`, `config-loader.ts`, `json-source.ts` | Safe binds, bounded HTTP shutdown/streaming, tokens, sanitized headers, layered config coercion, and settings files. |

`ChannelId` is intentionally open so third-party drivers can choose an id.
`isDeliverableConversation` only checks a conversation scheme against the ids a
caller supplies; concrete delivery policy remains with the adapter or host.
`ChannelStartInput` deliberately carries no process-job operator authority.
Owner applications that expose authenticated job routes must compose that
capability through their own private operator path rather than through the
generic third-party channel contract.

`ResilientMessageStream` can keep answer deltas final-only while exposing tool
starts in one transient cumulative status. Its shared formatter uses friendly
tool-family copy, bounded allowlisted previews, and secret redaction; adapters
can optionally delete a confirmed status during cancellation without risking an
answer message. A subagent's child tools stay grouped while it runs, then its
group collapses independently at the first terminal event: child lines are
removed while the header retains its total call count and duration. A meaningful
completion can add one secret-redacted, 120-code-point `Result` or `Reason` line;
later completion bookends may enrich the row but cannot re-expand it. Skill
disclosure renders the selected skill name as
`📚 Reading "<skill>"` without exposing its path. Read-only memory recall is
preview-free as `🧠 Recalling memory`; memory writes and ordinary file reads
retain their distinct `🧠 Updating memory` and `📖 Reading` families. File
paths use suffix-weighted middle truncation so filenames survive; commands keep
a balanced prefix and suffix. A shell call that carries a `description` renders
that intent instead of the command line, dropping the verb so it does not
double up. The field arrives from a runtime that declares it or a model that
volunteers it, since activity events carry the raw tool input rather than a
schema-filtered copy; calls without one fall back to the command. Shell previews first drop a leading `cd <agent root> &&`, which is
constant across every command and would otherwise consume half the budget; a
`cd` elsewhere is kept. Redaction still precedes the 40-code-point cap.
Applied live guidance uses the same boundary through
`formatLiveInputActivityLine()`, producing `↪️ Steered: “…”` without exposing the
full follow-up as tool arguments or metadata. In final-only streams, a marked
steering activity relocates a confirmed transient ledger behind the user's new
message when the transport supports deletion; deletion failure falls back to
editing in place and never changes final-answer delivery.

## Public API

### Start here

| Need | Primary API |
| --- | --- |
| Implement or call an agent turn | `AgentRequestBase`, `AgentResponder`, `AgentMessageStream`, `AgentResponse` |
| Offer a follow-up to an active turn | `AgentLiveInputRequest`, `AgentLiveInputOffer`, `AgentLiveInputSettlement` |
| Implement a channel plugin | `ChannelDriver`, `ChannelStartInput`, `RunningChannel`, `ChannelStatus` |
| Add structured human interaction to a channel | `ChannelInteractionHub`, `ChannelInteractionSink`, `ChannelAskQuestion`, `ChannelAskSnapshot`, `ChannelAskSubmission` |
| Buffer or harden streamed output | `BufferedMessageStream`, `ResilientMessageStream` |
| Format safe applied-steering activity | `formatLiveInputActivityLine` |
| Sanitize or validate reply-part delivery outcomes | `sanitizeReplyPartDeliveryOutcomes`, `isAgentReplyPartDeliveryOutcomes` |
| Carry stream events across a process boundary | `AgentStreamWireFrame`, `serializeAgentStreamFrame`, `parseAgentStreamFrame` |
| Exchange process-job state without kernel/app coupling | `ProcessJobProjection`, `ProcessJobState`, `ProcessJobErrorCode`, `parseProcessJobProjection`, `ProcessJobOperator`, `MAX_PROCESS_JOB_OUTSTANDING_LIFECYCLES` |
| Load adapter settings safely | `readSettingsJson`, `writeSettingsJson`, `layerJsonOntoEnv` |
| Protect an HTTP listener | `assertSafeBind`, `listen`, `generateBearerToken`, `readAuthorizationBearer` |

<!-- 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-contracts`**

```text
AGENT_CONTINUATION_ORIGIN_CONTEXT_MAX_BYTES
AGENT_CONTINUATION_ORIGIN_CONTEXT_MAX_MESSAGES
AGENT_CONTINUATION_ORIGIN_CONTEXT_MAX_MESSAGE_BYTES
AGENT_LIVE_INPUT_MAX_CHARACTERS
AGENT_LIVE_INPUT_MAX_MESSAGES
AGENT_MESSAGE_SENDER_LABEL_MAX_CHARS
AGENT_PRECEDING_MESSAGES_MAX_COUNT
AGENT_PRECEDING_MESSAGES_MAX_TOTAL_BYTES
AGENT_PRECEDING_MESSAGE_MAX_TEXT_BYTES
AgentAttachment
AgentContinuationContextMessage
AgentContinuationOriginContext
AgentContinuationTurn
AgentLiveInputOffer
AgentLiveInputRequest
AgentLiveInputSettlement
AgentLiveInputUnavailableReason
AgentMcpAppHostRequest
AgentMcpAppLoadRequest
AgentMcpAppResource
AgentMessageFinishOptions
AgentMessageSender
AgentMessageStream
AgentPrecedingMessage
AgentReplyArtifactOpenRequest
AgentReplyArtifactReference
AgentReplyArtifactStream
AgentReplyAttachmentPart
AgentReplyMcpAppPart
AgentReplyPart
AgentReplyPartDeliveryOutcome
AgentReplyPartDeliveryType
AgentReplyPartFailure
AgentReplyTarget
AgentRequestBase
AgentRequestMetadata
AgentResponder
AgentResponse
AgentResponseCancelledError
AgentResponseCancelledErrorOptions
AgentResponseMetadata
AgentStreamEvent
AgentStreamWireFrame
AgentSurface
AgentSurfaceMessageBudget
AgentToolEnvironment
BoundedHttpResponseWriter
BoundedHttpResponseWriterOptions
BufferedMessageStream
BufferedMessageStreamOptions
ChannelAskAnswer
ChannelAskOption
ChannelAskQuestion
ChannelAskSnapshot
ChannelAskStatus
ChannelAskSubmission
ChannelAskSubmissionResult
ChannelConfigInput
ChannelConfigViewField
ChannelConfigViewFieldSource
ChannelConfigViewSection
ChannelDeliveryDisposition
ChannelDeliveryError
ChannelDriver
ChannelFailureCertainty
ChannelId
ChannelInteractionHub
ChannelInteractionSink
ChannelLogger
ChannelMessageContentKind
ChannelSendOutcome
ChannelStartInput
ChannelStatus
ChannelTransport
ChannelUserCancelReason
CodedError
ConfigErrorFactory
CronOperatorHealth
CronOperatorJob
CronOperatorOverview
CronOperatorRun
CronOperatorRunBase
CronOperatorRunDetail
CronOperatorRunPage
CronOperatorRunStatus
CronOperatorRunSummary
CronOperatorRunTrigger
CronOperatorRunTruncatedField
CronOperatorWireError
DEFAULT_AGENT_ATTACHMENT_MAX_BYTES
DEFAULT_AGENT_ATTACHMENT_MIME_ALLOWLIST
DEFAULT_EMPTY_FINAL_TEXT
DEFAULT_MAX_MESSAGE_CHARS
EnvEncodeKind
InboundHttpHeaders
JsonEnvFieldSpec
JsonEnvMapping
ListenErrorFactories
MAX_AGENT_REPLY_PARTS
MAX_CRON_OPERATOR_CONVERSATION_ID_BYTES
MAX_CRON_OPERATOR_CURSOR_BYTES
MAX_CRON_OPERATOR_DEGRADED_REASON_BYTES
MAX_CRON_OPERATOR_DETAIL_ARTIFACT_ID_BYTES
MAX_CRON_OPERATOR_DETAIL_ERROR_BYTES
MAX_CRON_OPERATOR_DETAIL_EVENTS
MAX_CRON_OPERATOR_DETAIL_EVENT_BYTES
MAX_CRON_OPERATOR_DETAIL_FAILURE_KIND_BYTES
MAX_CRON_OPERATOR_DETAIL_TEXT_BYTES
MAX_CRON_OPERATOR_EXPRESSION_BYTES
MAX_CRON_OPERATOR_JOBS
MAX_CRON_OPERATOR_JOB_ID_BYTES
MAX_CRON_OPERATOR_RESPONSE_BYTES
MAX_CRON_OPERATOR_RUN_ID_BYTES
MAX_CRON_OPERATOR_RUN_PAGE
MAX_CRON_OPERATOR_SUMMARY_ARTIFACT_ID_BYTES
MAX_CRON_OPERATOR_SUMMARY_ERROR_BYTES
MAX_CRON_OPERATOR_SUMMARY_FAILURE_KIND_BYTES
MAX_CRON_OPERATOR_SUMMARY_REPLY_PART_OUTCOMES
MAX_CRON_OPERATOR_SUMMARY_TEXT_BYTES
MAX_CRON_OPERATOR_TIMEZONE_BYTES
MAX_PROCESS_JOB_OUTSTANDING_LIFECYCLES
MCP_APPS_EXTENSION_ID
MCP_APP_RESOURCE_MIME_TYPE
MCP_APP_SUPPORTED_VERSIONS
MemoryBlock
MemoryCompletedTurn
MemoryCompletedTurnAdmissionStatus
MemoryCompletedTurnResult
MemoryLoadOptions
MemoryStore
MemoryWriteResult
MessageRef
NOTHING_TO_REPORT_SENTINEL
NotifyDeliveryContext
NotifyDeliveryResult
NotifyDestination
NotifySuppression
PROCESS_JOB_ERROR_CODES
PROCESS_JOB_PUBLIC_ERROR_MESSAGES
PROCESS_JOB_STATES
ProcessJobErrorCode
ProcessJobOperator
ProcessJobProjection
ProcessJobProjectionError
ProcessJobProjectionLimits
ProcessJobProjectionOrigin
ProcessJobProjectionOutput
ProcessJobProjectionTimestamps
ProcessJobProjectionWake
ProcessJobState
ProcessJobWakeDeliveryInput
ProcessJobWakeDeliveryResult
ProcessJobWakeDisposition
ProcessJobWakeState
ReadSettingsJsonResult
RedactedSecretValue
ResilientAgentMessageStream
ResilientMessageStream
ResilientMessageStreamLogger
ResilientMessageStreamOptions
RunningChannel
RunningProcessJobChannel
SUBAGENT_TOOL_SEPARATOR
SessionToolHistoryEventMetadata
SessionToolHistoryTerminalState
SettingsJson
SettingsJsonError
SettingsJsonErrorCode
SettingsJsonErrorDetails
SettingsJsonValue
SettingsPrimitive
ToolActivityLineOptions
agentAttachmentKindFromMimeType
appendReplyPartFallback
assertAgentContinuationOriginContext
assertSafeBind
bearerTokensEqual
buildStreamingTailPreview
classifyNotifySuppression
close
closeServerBounded
createChannelUserCancelReason
decodeAgentAttachmentText
encodeJsonEnvValue
fieldSpecMappings
formatLiveInputActivityLine
formatProviderStatusLine
frameFeedingMessageStream
generateBearerToken
hostForUrl
isAgentReplyPartDeliveryOutcomes
isAgentResponseCancelledError
isChannelUserCancelReason
isCodedError
isDeliverableConversation
isLoopbackHost
isProcessJobErrorCode
isProcessJobState
isSubagentLaunchToolName
isWildcardHost
layerJsonOntoEnv
listen
normalizeHostForBind
normalizeOptionalString
normalizeTrailing
parseAgentStreamFrame
parseCronOperatorJob
parseCronOperatorOverview
parseCronOperatorRunDetail
parseCronOperatorRunPage
parseCronOperatorRunSummary
parseProcessJobProjection
parseProcessJobProjections
processJobPublicError
readAuthorizationBearer
readBoolean
readChoice
readCsv
readInteger
readJsonSection
readRecord
readRequired
readSettingsJson
readString
redactedSecret
sanitizeInboundHttpHeaders
sanitizeReplyPartDeliveryOutcomes
serializeAgentStreamFrame
setToolActivityPathRoots
splitSubagentToolName
splitTextByCodePoints
splitTextForChat
suppressesNotification
toolHintFor
toolNameLeaf
unsupportedReplyPartDeliveryOutcomes
writeSettingsJson
```

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

## Dependency Boundary

This package has no workspace or provider dependency. It must remain adapter-neutral and should not mention transport-specific packages, host config semantics, or runtime implementations.

## What This Package Does Not Own

It does not normalize transport messages, run model providers, build prompts, persist memory, stream to a concrete UI, or define host configuration semantics.

## Related Documentation

- [Programmatic composition](https://mono-agent-docs.vercel.app/programmatic/)
- [Custom channel drivers](https://mono-agent-docs.vercel.app/programmatic/custom-channels/)
- [Runtime, tools, and guard boundaries](https://mono-agent-docs.vercel.app/runtime/tools-and-guards/)
- [Reply files and MCP Apps](https://mono-agent-docs.vercel.app/tools/rich-replies/)
- [Package source and generated API inventory](https://github.com/robertsreberski/mono-agent/tree/main/packages/agent-contracts)

## Verification

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