@zephyrcloudio/harness

The platform-neutral Ze Harness Core. It owns durable Session coordination, Turn execution, plugin generations, policy, cancellation, terminal outcomes, and the Storage service contract.

Advanced hosts compose createCoreRuntime, makeSessionServices, and a StorageService from the package root. Most applications should use @zephyrcloudio/harness-sdk instead.

import { createCoreRuntime, makeSessionServices, Storage } from '@zephyrcloudio/harness';

Core has no Node, HTTP, config-discovery, plugin-loading, CLI, SDK, or SQLite imports. Required state changes are awaited; events are observational only.

Core owns ordered action/resource permission evaluation, Tool projection validation, durable pending requests, Session grants, and terminal settlement. Configured rules use full-string * and ? matching; the last matching rule wins and no match asks. The effective order is Ze defaults, Server, Session, then Agent. Ze defaults allow every Tool inside the session workdir and ask only for external_directory paths and .env reads (.env.example stays allowed). Saved grants and hook allowances never override a configured deny.

Hybrid CodeMode

Agents can use execute({ code }) to combine tool calls in JavaScript. Direct tools remain available, and each operation inside a script still requires its usual permissions. execute itself never asks for approval. Rejecting one operation can be handled by the script; cancelling the Turn stops the whole execution. Interrupted scripts are not automatically replayed after a restart.

CodeMode is on by default. Each host has one switch to turn it off:

Host Setting
Node server, CLI, and SDK "codemode": { "enabled": false } in ze-harness.config.json or the SDK config option
WorkerHarnessHost embedders, such as TAP codemode: { enabled: false } passed to the constructor
The Worker deployment in packages/worker HARNESS_CODEMODE_ENABLED=false, which that Worker passes as the codemode option

A configured execute deny rule also hides it.

See the CLI guide for inspecting executions and the CodeMode package for scripting usage and limits.

Activity streams

SessionServices.observation exposes two cursor streams over durable state. watchSessionActivity pages one Session's changed Turns (and its title) after a per-Session SessionActivityCursor. watchServerActivity pages every stamped Session, Turn, and Session deletion in the server's database after a ServerActivityCursor: one monotonic counter per database that is stamped in the same transaction as the change and never reused after a delete, so one reader observes spawned children, their approvals and questions, and deletions without polling each Session. Omit afterServerCursor to watch live from the current cursor (Storage.readServerActivityCursor); pass one to replay from it. Both streams wake on the in-process activity hub, re-read when idle if idleRefresh is set, and advance to the page's observed cursor once caught up.

Conversation entries

The composed SessionServices expose a conversation slice that appends outside conversation to a Session without admitting work:

import { ConversationEntryId } from '@zephyrcloudio/harness-schema';

const receipt =
  yield *
  services.conversation.appendConversation({
    sessionId,
    request: {
      entryId: ConversationEntryId.make('00000000-0000-4000-8000-000000000002'),
      content: [{ type: 'text', text: 'Alice: the deploy finished.' }],
    },
    attribution: { clientId },
  });

Each entry commits to Session history under the next history revision as a plain user message. It never wakes the runner, never creates a Turn, and never calls a model. Every Turn and manual compaction pins its model-visible history to its own admission revision, so work admitted before an append never sees it; the next admitted work sees the full conversation. Replaying the same entryId with identical content returns the recorded receipt, and reusing it with different content fails with ConversationEntryConflict.

Provider authority and catalog composition

Core owns ProviderAuthority, which:

  • Loads models and pricing from a host-supplied models.dev snapshot or the bundled offline snapshot (see Model catalog).
  • Compiles one declarative provider plan for Turn registration and discovery. The plan maps supported models.dev package and shape metadata to Ze's native chat, responses, and messages adapters, while unknown explicit packages and shapes fail closed.
  • Resolves credentials from valid stored connections, a configured providers.<id>.options.apiKey, or each definition's environment variables. Discovery checks stored state directly; Turn authentication checks the configured key first.
  • The discovery default is the configured default when it names an available composed model, otherwise no default (discovery stays up so credentials can be connected later). With no configured default, the first available model in lexical ref order. Turn setup instead fails loudly on a configured-but-unavailable ref — never a silent substitution to another provider.
  • Emits atomic catalog revisions upon credential change and manages reconnect_required status safely.

Provider discovery adds model IDs to the snapshot list; explicit definition.models entries take precedence. Expected discovery failures log a warning and retain snapshot and configured models. Unknown discovered or configured models default to text input/output with tools enabled. Explicit capabilities, including tools: false, take precedence. Unknown limits remain zero until supplied by discovery or config; no context or output window is guessed.

Model catalog

One pipeline keeps two catalogs in step:

  • The bundled snapshot (src/model/bundled-models.json) ships in every build, so a harness works with no download.
  • The published catalog is a models.dev api.json document that Node hosts refresh from ZE_HARNESS_MODELS_URL (see the Server Node README). It uses the models.dev format so every released binary reads it with the normalizer it already applies to models.dev; there is no second format.

pnpm update:models-dev (from the repository root) builds Core, fetches models.dev, and applies scripts/model-catalog-corrections.json. It keeps only providers the harness can use: those whose declared package maps to a native protocol, plus the declarative presets. It then normalizes that document with the runtime normalizer and rewrites the bundled snapshot only when provider content changed. Pass --published <file> to also write the published document; the bundled snapshot is that document's normalized form, so shipped and published cannot drift. The build refuses to write anything when anthropic, openai, opencode-go, or openrouter has no usable models. Set MODELS_DEV_URL to override the https://models.dev/api.json source. Commit the updated bundle and rebuild before distributing it.

The normalizer maps each model's wire protocol from models.dev metadata: a model's provider.shape (completions is chat, responses is responses) wins over its provider.npm (@ai-sdk/openai-compatible is chat, @ai-sdk/openai is responses, @ai-sdk/anthropic is messages), which wins over the provider's package. Unknown explicit packages, shapes, statuses, and invalid catalog IDs are dropped rather than silently routed.

Corrections

scripts/model-catalog-corrections.json fixes upstream data before it is published or bundled:

{
  "models": [
    { "provider": "openai", "model": "gpt-x", "reason": "Retired upstream", "hidden": true },
    { "provider": "openrouter", "model": "a/b", "reason": "Typo", "displayName": "A B" },
    {
      "provider": "opencode-go",
      "model": "c",
      "reason": "Served on /messages",
      "protocol": "messages"
    }
  ]
}

Each entry names one model and a reason, and either hides it or sets displayName and/or protocol (chat, responses, or messages; written as the matching models.dev package). Unknown fields, duplicate entries, entries that change nothing, and entries for models upstream no longer lists fail the build, so a stale correction is removed instead of silently ignored.

TAP credits

With TAP_GATEWAY_URL set (for example https://gateway.theaiplatform.app), the build adds a tap provider (TAP credits) from the gateway's public GET /v1/credits/models listing: chat protocol, base URL <gateway>/v1/credits, gateway prices, and limits and capabilities from the matching models.dev OpenRouter model. Listed models without a models.dev OpenRouter entry are skipped because the listing has no output limit. Without TAP_GATEWAY_URL the section is omitted.

The tap preset owns routing and keeps discovering models from GET /v1/credits/models whether or not the section is present. The preset base URL takes precedence over the section's URL, and the section supplies limits and prices for the models it lists.

Publishing

.github/workflows/publish-models.yml runs daily and on demand. It builds the published document, validates it with the harness normalizer (scripts/publish-model-catalog.mjs), and, on main once the bucket is configured, uploads it to R2 as api.json with Cache-Control: public, max-age=300. R2 derives the ETag from the bytes, so an unchanged catalog answers a harness's If-None-Match with 304. Pull requests that change the pipeline build and validate without uploading.

One-time setup before Node hosts default to the published catalog:

  1. Create an R2 bucket in the Cloudflare account that owns theaiplatform.app.
  2. Attach the custom domain models.theaiplatform.app to the bucket.
  3. Create an R2 API token with Object Read & Write on that bucket only.
  4. Add repository variables ZE_MODELS_R2_ACCOUNT_ID and ZE_MODELS_R2_BUCKET, and secrets ZE_MODELS_R2_ACCESS_KEY_ID and ZE_MODELS_R2_SECRET_ACCESS_KEY from the token. Optionally add variable ZE_MODELS_TAP_GATEWAY_URL (the gateway origin) to publish the tap section.
  5. Run the workflow, then check that https://models.theaiplatform.app/api.json returns an ETag and answers that ETag in If-None-Match with 304.
  6. Change the ZE_HARNESS_MODELS_URL default in packages/server-node/src/bootstrap/models-dev-cache.ts and the Server Node README to https://models.theaiplatform.app/api.json.

Named provider entries

Each providers.<id> entry is its own provider with its own credential. A named entry can inherit the transport and model list of a built-in with base, then override the display name, credential source, and any definition fields:

{
  "providers": {
    "openai-work": {
      "base": "openai",
      "definition": {
        "displayName": "Work GPT",
        "credentials": { "environment": ["OPENAI_WORK_API_KEY"] },
      },
    },
    "openai-personal": {
      "base": "openai",
      "definition": {
        "displayName": "Personal GPT",
        "credentials": { "environment": ["OPENAI_PERSONAL_API_KEY"] },
      },
    },
  },
}

Models are addressed per entry (openai-work/gpt-5-mini), and each entry keeps one stored connection, one live-port key, and its own environment list. The base must name a known preset or snapshot provider, cannot point at the entry itself, and cannot chain through another base entry. Unknown, self, and chained bases fail closed at setup.

MCP screenshots

MCP tool results containing images preserve the ordered text and image blocks plus any structuredContent as a multipart canonical tool result. Other MCP blocks remain JSON parts, and private _meta values are excluded. Audio and blob resources beyond 10 MB decoded are replaced with an explicit omission note instead of model text. Supported images use canonical base64 with PNG, JPEG, GIF, or WebP MIME types; unsupported image content fails explicitly. Results survive tool settlement, checkpoints, history replay, and compaction input.

The selected model must accept images, and the existing images.maxBase64Bytes setting also applies to tool images. New oversized results settle as failed tools with a request for smaller output, allowing the model to continue; oversized images already in history still fail request validation. URL images are not measured by this base64-only limit. Failed MCP calls retain their image content and failure status. Exact JSON text duplicates of structuredContent are omitted; accompanying prose remains. Text and JSON tools retain their existing result formats. This adds image handling to MCP tools; ordinary plugin tool return values continue to be interpreted as JSON.

The default request-size estimator counts serialized bytes, including base64 images. Hosts that need provider-specific image token accounting can supply requestSizeEstimator to createCoreRuntime.

Public TypeScript exports

  • AdmittedPrompt
  • AdmittedSteer
  • AppendConversationCommand
  • AppendConversationEntryCommand
  • CancelSessionMcpCredentialCommand
  • ClaimedContextCompaction
  • ClaimedSessionWork
  • ClaimedTurn
  • CompleteSessionMcpCredentialCallbackCommand
  • ConfiguredAgent
  • ContextCompactionConflict
  • ContextCompactionNotFound
  • ConversationEntryConflict
  • CoreMcpCandidate
  • CoreRuntime
  • CoreRuntimeCandidate
  • CoreRuntimeConfig
  • createCoreRuntime
  • CreateCoreRuntimeOptions
  • defaultProviderIds
  • DeleteSessionCommand
  • DurableContentResolutionError
  • DurableContentResolver
  • DurableContextSelection
  • DurableHistoryMessage
  • ForkSessionCommand
  • ForkSessionInput
  • GenerationCompanion
  • HistoryVisibilityConflict
  • InvalidServerCursor
  • InvalidSessionCursor
  • InvalidTurnTransition
  • ListPluginJournalInput
  • ListServerActivityInput
  • ListSessionActivityInput
  • ListSessionCompactionsInput
  • ListSessionConversationInput
  • ListSessionTurnsInput
  • ListTurnEventsInput
  • loadBundledModelsDevSnapshot
  • makeLateBoundMcpCredentialConnections
  • makeReconnectingMcpCredentialLifecycle
  • makeSessionMcpCredentialRuntime
  • makeSessionMcpCredentialServerResolver
  • makeSessionMutationGate
  • makeSessionServices
  • MCP_CREDENTIAL_AUTHORIZATION_REQUIRED_REASONS
  • MCP_CREDENTIAL_CALLBACK_DENIED_REASONS
  • MCP_CREDENTIAL_REVOCATION_STATUSES
  • McpCredentialHostConnections
  • McpCredentialIdentity
  • McpPromptReservation
  • McpPromptReservationInput
  • NormalizedModelRecord
  • NormalizedModelsDevSnapshot
  • NormalizedProviderRecord
  • PermissionConflict
  • PermissionReplyReceipt
  • PermissionReplyResolution
  • PermissionRequestNotFound
  • PermissionResolution
  • PluginJournalPage
  • PreparedTurnInput
  • PrivateHistoryMessageContinuation
  • PrivateModelMessageContinuation
  • PrivateSessionPluginIntegration
  • PrivateTurnMessageContinuation
  • ProgrammaticPluginCandidate
  • PromotedSteer
  • PromptAdmissionContext
  • PromptAdmissionStale
  • PromptConflict
  • ProviderConfigEntry
  • ProviderConfigMap
  • ProviderDefinitionMap
  • PublicMcpCatalogPrompt
  • PublicMcpCatalogResource
  • PublicMcpCatalogResourceTemplate
  • PublicMcpCatalogServer
  • PublicMcpCatalogSnapshot
  • PublicMcpCatalogTool
  • PublicMcpResourceAnnotations
  • PublicMcpResourceContents
  • PublicMcpResourceReadResult
  • PublicMcpServerCatalog
  • publicSessionMcpCredentialForgetResult
  • publicSessionMcpCredentialStatus
  • QuestionConflict
  • QuestionReplyReceipt
  • QuestionRequestNotFound
  • QuestionResolution
  • QuestionResolutionInput
  • ReadMcpResourceCommand
  • RecoveryResult
  • RejectQuestionInput
  • ReloadResult
  • ReplaceSessionConfigurationInput
  • ReplyPermissionInput
  • ReplyQuestionInput
  • RequestSizeEstimationError
  • RequestSizeEstimator
  • ResolvedModel
  • ResolvedPrompt
  • RunInput
  • RuntimeCandidateError
  • RuntimeStartupError
  • SavedPermissionNotFound
  • ServerActivityEntry
  • ServerActivityPage
  • ServerDeletionReceipt
  • ServerName
  • SessionActivityPage
  • SessionCompactionHistoryBoundary
  • SessionCompactionPosition
  • SessionCompactionsApi
  • SessionCompactionsPage
  • SessionCompactionsService
  • SessionCompactionState
  • SessionConfigurationConflict
  • SessionConflict
  • SessionConversationApi
  • SessionConversationPage
  • SessionConversationService
  • SessionDeletionApi
  • SessionDeletionReceipt
  • SessionDeletionService
  • SessionExecutionApi
  • SessionExecutionService
  • SessionHistoryBoundary
  • SessionLifecycleApi
  • SessionLifecycleService
  • SessionMcpCredentialAuthorization
  • SessionMcpCredentialAuthorizationRequiredReason
  • SessionMcpCredentialCallbackDeniedReason
  • SessionMcpCredentialError
  • sessionMcpCredentialFailure
  • SessionMcpCredentialForgetResult
  • SessionMcpCredentialRevocationStatus
  • SessionMcpCredentialRuntime
  • SessionMcpCredentialRuntimeInput
  • SessionMcpCredentialServerBinding
  • SessionMcpCredentialStatus
  • SessionMcpError
  • SessionMcpRuntime
  • SessionMcpSurfaceApi
  • SessionMcpSurfaceService
  • SessionMutationGate
  • SessionMutationLease
  • SessionNotFound
  • SessionObservationApi
  • SessionObservationService
  • SessionOverlayRevision
  • SessionPromptsApi
  • SessionPromptsService
  • SessionRuntime
  • SessionRuntimeError
  • SessionServiceClosed
  • SessionServices
  • SessionServicesInput
  • SessionTurnRef
  • SessionTurnsPage
  • SessionTurnState
  • StartSessionMcpCredentialCommand
  • SteerConflict
  • SteeringModelBoundaryInput
  • SteeringModelBoundaryResult
  • SteeringTerminalBoundaryResult
  • SteerRejected
  • Storage
  • StorageError
  • StorageFailure
  • StorageService
  • SubmitContextCompactionCommand
  • SubmitMcpPromptCommand
  • SubmitPromptCommand
  • TerminalCommit
  • TerminalProjection
  • TerminalToolOutputArtifact
  • ToolCallCompletion
  • ToolCallNotFound
  • ToolCallRequest
  • ToolCallSettlement
  • ToolDispatchError
  • ToolDispatchLease
  • ToolDispatchLifecycle
  • ToolDispatchOutcome
  • ToolPermissionCallbackResolution
  • Turn
  • TurnEventsPage
  • TurnNotFound
  • VERSION
  • WatchServerActivityInput
  • WatchSessionActivityInput
  • WebFetch
  • WebFetchError
  • WebFetchFormat
  • withSessionMutation
  • withSessionMutationExclusive
  • WorkspaceDirectoryEntry
  • WorkspaceFileError
  • WorkspaceFiles
  • WorkspaceFileTarget
  • WorkspaceShell
  • WorkspaceShellError