The Pi harness defines configuration, API communication, and message-handling logic for QM's Pi integration, bridging Config objects to PiHarnessOptions, managing turn detection and emoji reactions, and orchestrating SSE calls against Anthropic's API. Pi harness message seeding reconstructs tool rounds and pushes them into the session's live state and persistence layer; error handling surfaces structured API errors and gracefully tolerates missing Pi internals. SSE (Server-Sent Events) is a protocol in which a server streams a sequence of text events to a client over a single HTTP connection; the Pi harness uses SSE to receive Anthropic API responses incrementally rather than waiting for a single blocking response. src/harness/pi-harness.ts routes turns dispatched through the Pi harness to the correct Pi-targeted destination.
src/harness/pi-harness.ts defines PiHarnessOptions, the configuration interface for the Pi harness, with options covering model resolution, API keys, tool toggles, timeout budgets, and signal wiring.[1] piHarnessConfigOptions() translates a Config object into PiHarnessOptions, mapping fields such as modelId→defaultModelId, detectModelId→detectModelId, titleModelId→titleModelId, anthropicApiKey→apiKey, piCaptureRequests→captureRequests, piSystemCacheSplit→systemCacheSplit, scratchExecEnabled→scratchExec, sharedOwnerAuthIsolation→ownerAuthExec, reachExecEnabled→reachExec, turnWallClockMs→turnWallClockMs, execTimeoutDefaultMs→execTimeoutMs, execTimeoutMaxMs→execTimeoutCeilingMs, backgroundJobTtlMs→backgroundJobTtlMs, and backgroundJobTtlMaxMs→backgroundJobTtlMaxMs.[1][2] Optional fields — defaultModelId, detectModelId, titleModelId, and apiKey — are omitted entirely from the returned object (not present as undefined) when the corresponding config values are unset.[2] piHarnessConfigOptions sets controlTools: true only when both signingSecret and apiBaseUrl are present in the config; either field alone is insufficient.[2]
Valid turnEffortLevel values in src/harness/pi-harness.ts are "off", "minimal", "low", "medium", "high", "xhigh", "max", "ultracode", and "auto"; the legacy thinking-level set omits "max", "ultracode", and "auto".[1]
buildDetectionPrompt() constructs the system prompt used to decide whether the AI assistant should reply. When reactionGuidance is supplied, the verdict set expands to include REACT (for emoji acknowledgements); otherwise only YES or NO are valid first-line responses.[1] The turn-detection prompt instructs the model to prefer YES when a message is feedback or a preference about the assistant's own behaviour (even a flat statement with no question mark), and to prefer NO when genuinely unsure — though it should lean YES when a message is plausibly directed at the assistant, because blanking a directed message is worse than a brief reply.[1] parseDetectVerdict() parses the LLM's raw turn-detection output: it strips common preamble tokens (answer:, verdict:), then checks the first line for YES, NO, or (when reactionsEnabled) REACT. A REACT response extracts up to 3 emoji tokens via parseEmojiTokens(); if none are found the result still omits respond: true.[1] parseEmojiTokens() extracts emoji reactions from a line by matching both :name: shortcodes and Unicode Extended_Pictographic characters, deduplicates them, and returns at most 3 tokens.[1]
oneShot in src/harness/pi-harness.ts sends the Anthropic API key as the x-api-key request header and includes the system prompt and user prompt in the request body, returning the assistant's text content on a successful SSE turn.[2] Example: oneShot completes a full Pi 0.82 SSE turn against a local stub server, verifying auth header and response text extraction.[2] oneShot cleans up all temporary directories it creates even when the underlying session call throws, leaving no temp dirs behind on failure.[2]
toPiMessage in src/harness/pi-harness.ts gives assistant-role seed messages a synthetic usage: { totalTokens: 0 } block and stopReason: "stop" so that Pi's pre-prompt compaction check cannot crash on missing usage; user-role seeds carry no usage field.[2] seedRawMessagesIntoSession reconstructs a tool round from history as the sequence [user, assistant, toolResult, assistant], preserving the toolCallId and result content on the toolResult entry.[2] seedRawMessagesIntoSession simultaneously pushes reconstructed messages (including toolResult entries) onto the live agent.state.messages array and persists each via sessionManager.appendMessage, leaving the two arrays identical.[2] seedRawMessagesIntoSession is a no-op when called with an empty message list, and degrades gracefully — without throwing — when called with a session object that lacks Pi internals.[2]
piLastAssistantTextOrThrow in src/harness/pi-harness.ts throws with the provider's error message when the last assistant message has stopReason: "error", rather than returning a blank reply.[2] piLastAssistantTextOrThrow parses JSON error bodies in the assistant's errorMessage field and surfaces a human-readable message in the format Model provider API error (<type>): <message>.[2] piTurnError inspects the session's last assistant message for a structured JSON error; if the thrown error is a generic catch-all (e.g. "An unknown error occurred"), it replaces it with the richer structured error from the session.[2] piTurnError falls back to the thrown error unchanged when the session's last assistant message has no structured error (e.g. stopReason: "stop"); it also accepts a plain string as the thrown value and wraps it in an Error.[2]
src/deployment/postdeploy-smoke.ts runs post-deploy smoke tests to catch failure modes before traffic is accepted, narrowing the safety window between deploy and serve.
Sources