@zephyrcloudio/harness-protocol
The validated, versioned HTTP and SSE contract shared by the Server and Client.
Import endpoint metadata, Effect schemas, limits, and transport error values from the package root:
import { V1, TurnResponse, decodeProtocol } from '@zephyrcloudio/harness-protocol';
The protocol does not perform network requests. Decode all external values, respect
MAX_WIRE_BYTES, apply the shared byte-safe Session system-prompt schema, and treat SSE values as
observations rather than commands. A caught-up Session event stream repeats its current
session.cursor at least every SESSION_EVENT_HEARTBEAT_MILLIS (15 seconds), and a quiet Turn event
stream sends turn.heartbeat at least every TURN_EVENT_HEARTBEAT_MILLIS (15 seconds), so clients
can treat a longer silence as a lost connection. A turn.heartbeat carries no new state: every
durable event through its revision has already been sent.
The Fetch Server adds Ze-Harness-Version: <protocol>/<release> to responses, including SSE
and API errors. For example, 1/0.6.0 advertises protocol 1 and release 0.6.0. The header is
exposed through Access-Control-Expose-Headers without changing origin authorization.
SERVER_VERSION_HEADER, ServerVersion, and parseServerVersionHeader define this advisory
metadata. It is not an admission or compatibility gate: clients can warn without retrying an
already accepted mutation. Old servers may omit it.
The v1 contract includes revision-checked Session configuration replacement, durable permission
replies (once, always, or reject), and listing or removing saved Session grants. Replies target
a PermissionRequestId, not a Tool call ID.
Sessions fork with POST /v1/servers/:server/sessions/:sessionId/forks/:forkedSessionId: settled history before
an optional turnId is copied into the caller-selected identity, the targeted Turn stays behind
for resending, and an absent body forks the whole settled history. A through boundary instead
copies the named message inclusively (a Turn message selects its whole Turn; a conversation
entry selects itself), and visibility restricts the copy to what the forking actor may
observe. The fork inherits the parent
workdir, default model, system prompt, and permission rules, and records forkedFrom lineage
(parent Session plus the boundary) queryable on the Session; saved grants,
overlay state, and unsettled work stay behind, and exact retries are idempotent.
Sessions carry an immutable working directory (workdir, server root by default): relative
file paths resolve inside it, shell commands run inside it, and file and shell permission
resources are judged against it. Creation may send a clientWorkdir absolute path
instead; the server resolves it against its root (an explicit workdir always wins) and
rejects hints outside the root. List a server's Sessions with
GET /v1/servers/:server/sessions (oldest first, deleted identities excluded) and delete a
server — every Session plus shared credentials, with files kept — with
DELETE /v1/servers/:server.
Watch a whole server with GET /v1/servers/:server/events?afterServerCursor=N. Omit the cursor to
start live from the current one, which the first server.cursor announces; pass one to replay every
stamped row after it. The stream is the control-plane feed over the durable ServerActivityCursor: one monotonic counter per server database
that stamps every Session creation and title change, every Turn change, and every Session deletion.
Each ServerStreamEvent carries the serverCursor it was observed at and reuses the Session stream's
Turn event shapes (session.turn.snapshot, .tool.activity, .started, .usage, .tool.requested,
.approval.requested, .approval.resolved, .tool.completed, .question.asked, .question.replied,
.question.rejected) with their per-Session envelope intact; the live text, refusal, and reasoning
deltas stay on the per-Session stream. Session-scoped events are session.created (the Session
snapshot, sent the first time a stream sees a Session and therefore repeatable after a resume),
session.title, and session.deleted (sessionId, deletedAt). A Session's first event on a
stream is always session.created: a Turn event for a Session the stream has not announced yet is
preceded by one stamped one serverCursor before the Turn's, so a resume between the two replays
the Turn and may repeat that session.created. A caught-up stream repeats
server.cursor at least every SERVER_EVENT_HEARTBEAT_MILLIS (15 seconds), and a page whose rows
produced no event still advances with server.cursor.
Sub-agents spawn through the task tool, not the API: each call runs one prompt to
completion on a child Session pinned to the named agent, or continues task_id, and settles
to a result envelope. The child inherits the parent workdir and default model and records
spawnedFrom lineage; deleting a parent deletes its spawned children while forks stay
independent. Read children with GET /v1/servers/:server/sessions/:sessionId/children,
report host support with GET /v1/capabilities, and detach blocking foreground chains with
POST /v1/servers/:server/sessions/:sessionId/background (true when any chain detached;
each result arrives as a queued parent turn).
It also defines model discovery and provider connection endpoints:
- Model discovery:
GET /v1/runtime/models,GET /v1/runtime/models/events,GET /v1/servers/:server/sessions/:sessionId/models, andGET /v1/servers/:server/sessions/:sessionId/models/events. - Provider management:
GET /v1/runtime/providers,GET /v1/runtime/providers/:providerId,POST /v1/runtime/providers/:providerId/connect/key,PATCH /v1/runtime/providers/:providerId/connections/:connectionId, andDELETE /v1/runtime/providers/:providerId/connections/:connectionId. - Revisions are single-shot current-revision checks over SSE: each
.../models/eventscall emits the present revision once and closes. Clients re-fetch the correspondingGET .../modelssnapshot for fresh data. Secrets are never exposed over protocol routes.
Public TypeScript exports
AddSessionPluginRequestAddSessionPluginRequestValueAppendConversationReceiptResponseAppendConversationRequestAppendConversationRequestValueAuthorizationCancelContextCompactionRequestCancelMcpCredentialRequestCancelTurnRequestCapabilitiesResponseCapabilitiesResponseValueCLIENT_ID_HEADERClientWorkdirClientWorkdirValueCompleteMcpCredentialCallbackRequestCompleteMcpCredentialCallbackRequestValueConflictErrorConflictErrorsConnectProviderKeyRequestConnectProviderKeyResponseContextCompactionReceiptResponseContextCompactionResponseCreateSessionRequestCreateSessionRequestValueDAEMON_LOG_PAGE_MAXDaemonLogEntryDaemonLogLevelDaemonLogLevelValueDaemonLogQueryDaemonLogResponseDaemonLogResponseValuedecodeProtocolDeleteProviderConnectionResponseDeleteSessionRequestDescendantApprovalsResponseDescendantQuestionsResponseEndpointErrorsForbiddenErrorForgetMcpCredentialRequestForkSessionRequestHARNESS_PROTOCOL_VERSIONHarnessApiHealthResponseHealthResponseValueInternalServerErrormakeHarnessOpenApiMAX_WIRE_BYTESMCP_CREDENTIAL_AUTHORIZATION_REQUIRED_REASONSMCP_CREDENTIAL_CALLBACK_DENIED_REASONSMCP_CREDENTIAL_REVOCATION_STATUSESMcpCatalogPromptMcpCatalogResourceMcpCatalogResourceTemplateMcpCatalogResponseMcpCatalogServerMcpCatalogSnapshotMcpCatalogSnapshotValueMcpCatalogToolMcpCredentialChallengeMcpCredentialChallengeValueMcpCredentialForgetResponseMcpCredentialForgetResponseValueMcpCredentialStartResponseMcpCredentialStartResponseValueMcpCredentialStatusMcpCredentialStatusResponseMcpCredentialStatusValueMcpPromptArgumentsMcpPromptNameMcpResourceContentsMcpResourceReadResponseMcpResourceReadResultMcpResourceReadResultValueMcpServerCatalogMcpServerNameModelRevisionEventNotFoundErrorparseServerVersionHeaderPermissionReplyRequestPermissionReplyResponsePluginJournalCursorPluginJournalQueryPluginJournalResponsePluginJournalResponseValueProviderModelRefreshOutcomeProviderModelRefreshOutcomeValuePublicPromptAdmissionQuestionReplyRequestQuestionReplyResponseReadMcpResourceRequestRefreshProviderModelsRequestRefreshProviderModelsResponseRefreshProviderModelsResponseValueRefreshSessionPluginRequestRefreshSessionPluginRequestValueReloadSessionPluginRequestRemoveSessionPluginRequestReplaceSessionConfigurationRequestREQUEST_ID_HEADERRuntimeApiRuntimePluginRuntimePluginsResponseRuntimePluginValueRuntimeProviderResponseRuntimeProvidersResponseRuntimeReloadRequestRuntimeReloadResponseRuntimeSkillsResponseSafeFailureSafeProviderSummarySavedPermissionResponseSavedPermissionsResponseSchemaErrorSERVER_EVENT_HEARTBEAT_MILLISSERVER_VERSION_HEADERServerCursorEventServerDeletedResponseServerDeletedResponseValueServerSessionCreatedEventServerSessionDeletedEventServerSessionTitleEventServerStreamEventServerStreamEventValueServerTurnApprovalRequestedEventServerTurnApprovalResolvedEventServerTurnQuestionAskedEventServerTurnQuestionRejectedEventServerTurnQuestionRepliedEventServerTurnSnapshotEventServerTurnStartedEventServerTurnToolActivityEventServerTurnToolCompletedEventServerTurnToolRequestedEventServerTurnUsageEventServerVersionSESSION_COMPACTION_PAGE_MAXSESSION_EVENT_HEARTBEAT_MILLISSESSION_JOURNAL_PAGE_MAXSESSION_TURN_PAGE_MAXSessionAgentEntrySessionAgentEntryValueSessionAgentsResponseSessionAgentsResponseValueSessionBackgroundResponseSessionCompactionHistoryBoundarySessionCompactionHistoryBoundaryValueSessionCompactionPositionSessionCompactionPositionValueSessionCompactionsContinuationQuerySessionCompactionsFirstQuerySessionCompactionsQuerySessionCompactionsResponseSessionCompactionsResponseValueSessionCompactionStateSessionConfigurationResponseSessionCursorEventSessionDeletedResponseSessionDeletedResponseValueSessionHistoryBoundarySessionHistoryBoundaryValueSessionListQuerySessionListResponseSessionPluginActivationSessionPluginActivationValueSessionPluginRevisionSessionPluginRevisionValueSessionPluginsResponseSessionPluginsStatusSessionPluginsStatusValueSessionResponseSessionsApiSessionSkillsResponseSessionStreamEventSessionStreamEventValueSessionTurnApprovalRequestedEventSessionTurnApprovalResolvedEventSessionTurnQuestionAskedEventSessionTurnQuestionRejectedEventSessionTurnQuestionRepliedEventSessionTurnReasoningDeltaEventSessionTurnRefusalDeltaEventSessionTurnsContinuationQuerySessionTurnsFirstQuerySessionTurnSnapshotEventSessionTurnsQuerySessionTurnsResponseSessionTurnsResponseValueSessionTurnStartedEventSessionTurnStateSessionTurnTextDeltaEventSessionTurnToolActivityEventSessionTurnToolCompletedEventSessionTurnToolRequestedEventSessionTurnUsageEventSkillListEntrySkillListEntryValueStartMcpCredentialRequestStartMcpCredentialRequestValueSteerConflictErrorSubmitContextCompactionRequestSubmitContextCompactionRequestValueSubmitMcpPromptRequestSubmitMcpPromptRequestValueSubmitPromptReceiptResponseSubmitPromptRequestSubmitPromptRequestValueSystemApitoSafeProviderSummaryTURN_EVENT_HEARTBEAT_MILLISTurnApprovalRequestedEventTurnApprovalResolvedEventTurnHeartbeatEventTurnHeartbeatEventValueTurnNotActiveErrorTurnNotSteerableErrorTurnQuestionAskedEventTurnQuestionRejectedEventTurnQuestionRepliedEventTurnReasoningDeltaEventTurnRefusalDeltaEventTurnResponseTurnSnapshotTurnSnapshotEventTurnSnapshotEventValueTurnSnapshotValueTurnStartedEventTurnStreamEventTurnStreamEventValueTurnTerminalEventTurnTextDeltaEventTurnToolActivityEventTurnToolCompletedEventTurnToolRequestedEventTurnUsageEventUnauthorizedErrorUnavailableErrorUpdateProviderConnectionRequestValidationErrorWatchServerQueryWatchSessionQueryWatchTurnQuery