import type { AffordanceUse } from "./affordance.js"; import type { CodexAppServerRunResult, CodexAppServerStatus } from "./codex-app-server.js"; import type { ActorEstimatedCost } from "./pricing.js"; import type { TaskFunnel } from "./tasks.js"; export declare const ACTOR_TRACE_SCHEMA = "humanish.actor-trace.v1"; /** * How a session ended, from the point of view of the STUDY rather than the harness. * * The distinction matters because two of these are participant outcomes and the rest are not. A * participant who abandons a task is the single most valuable thing a usability study produces, and * recording that as `failed` — as this type used to force — reads as the instrument breaking. See * docs/principles/three-roles.md. * * - `passed` the participant reached the goal * - `abandoned` the participant stopped trying. A FINDING, not a malfunction * - `incomplete` the session ended (time or budget) before the goal was reached * - `blocked` the participant could not proceed: an approval the run could not give, or a * blocker they described in their own final words (#476) * - `timed_out` the session hit its deadline with no productive activity at all * - `failed` the HARNESS failed: a dead sandbox, a provider error, a broken artifact */ export type ActorStatus = "passed" | "abandoned" | "incomplete" | "blocked" | "timed_out" | "failed"; /** * What the PARTICIPANT said happened, in a field rather than a paragraph (#570). Providers whose * reply is schema-constrained (the local-agent routes) fill it on their final turn; a free-text * provider leaves it absent and the lane falls back to reading the closing message. `reached`: * the task is finished. `blocked`: something in the app stopped the participant. `not_reached`: * the participant stopped for another reason (gave up, ran out of ideas). */ export type ParticipantDeclaredOutcome = "reached" | "not_reached" | "blocked"; /** Statuses that describe what happened to a PARTICIPANT rather than a harness malfunction. Verify * treats these as study results, so a run whose evidence is sound is not called untrustworthy just * because a persona gave up. */ export declare const PARTICIPANT_OUTCOME_STATUSES: readonly ActorStatus[]; /** Optional precise interruption cause; completionReason and status retain their original meaning. */ export type ActorStopCause = "provider_output_limit" | "provider_token_limit" | "time_limit" | "spend_limit" | "study_spend_limit" | "adapter_limit" | "provider_incomplete" | "provider_status" | "harness_aborted" | "usage_unreported"; export type ActorCompletionReason = "goal_satisfied" | "turn_completed" | "gave_up" | "blocked_approval" | "timed_out" | "budget_reached" | "actor_error" | "step_failed" | "harness_error"; export type ActorLane = "code" | "app" | "computer-use" | "scripted-browser" | "terminal"; export type ActorProtocol = "json-rpc" | "json-stream" | "in-process-sdk" | "cua-loop" | "scripted-steps" | "terminal-exec"; export type ActorTraceItemKind = "message" | "reasoning" | "tool_call" | "command" | "file_change" | "approval" | "screenshot" | "ui_action" | "plan" | "notice"; export interface ActorTraceItem { id: string; kind: ActorTraceItemKind; lifecycle: "started" | "completed"; status?: string; title: string; tool?: { server?: string; name?: string; }; command?: { text?: string; cwd?: string; exitCode?: number; outputTail?: string; }; screenshotRef?: { path: string; redaction: "blurred" | "ocr_scrubbed" | "none"; }; text?: string; /** When the item was recorded (ISO-8601), from the loop's injected clock. Additive * (#441): items from older bundles and non-stamping producers lack it, so every * consumer must treat absence as "timing unknown", never as t=0. */ at?: string; /** Structured pointer coordinates for click-like `ui_action` items (#441) — the * recorded fact the Observer's pins previously re-parsed out of the title text. */ coord?: { x: number; y: number; }; } export interface ActorCapabilities { headless: boolean; structuredTrace: boolean; lanes: ActorLane[]; producesScreenshots: boolean; byoModel: boolean; preGrantableApprovals: boolean; inProcessTools: boolean; license: "open" | "source-available" | "proprietary"; /** * WHERE this actor's runtime key lives, per the placement rule (invariants-and-defaults.md): * "keys live where the keyed process runs — and nowhere else." Registry metadata the engine * enforces, NOT a code convention. * - "external" (the implicit default for every existing actor): the keyed process (e.g. a * computer-use provider loop) runs OUTSIDE any sandbox, so its key never enters one. * - "in-sandbox-command-scoped": the keyed process is an agent-harness-under-test that runs * INSIDE the sandbox; its runtime key is injected ONLY into the per-command `envs` of that * invocation (never `Sandbox.create({envs})`, which is sandbox-global), the key is presumed * exfiltratable, and the blast radius is bounded by key scoping + a spend budget. * Absent === "external". On the shipped terminal-product live route, the engine enforces this * declaration before sandbox creation and passes the key only to the agent command. */ keyPlacement?: "external" | "in-sandbox-command-scoped"; } export interface ActorPersonaRef { id: string; traitsApplied: string[]; promptDigest: string; } export interface ActorTokenUsage { input?: number; output?: number; /** Of `input`, how many tokens were served from the provider's prompt cache. Optional and * HONESTLY ABSENT: a provider that does not report it leaves this undefined rather than * reporting 0, because 0 and "unknown" price very differently (#391). */ cachedInput?: number; /** Of `input`, how many tokens were newly WRITTEN to the provider's prompt cache * (OpenAI 5.6+ bills these at a surcharge and reports `cache_write_tokens`). Same * honestly-absent discipline as `cachedInput` (#334). */ cacheWriteInput?: number; /** Per provider-REQUEST usage, in request order. Recorded fact, not pricing: a provider * that re-prices whole requests past an input-size threshold (long-context tiers) can * only be priced exactly from per-request sizes; totals cannot say which requests * crossed. Additive and honestly absent on producers that do not record it (#334). */ turns?: Array<{ input?: number; cachedInput?: number; cacheWriteInput?: number; output?: number; }>; total?: number; costUsd?: number; } /** The participant's account, not an independently confirmed product diagnosis. */ export interface ParticipantClosingReport { summary: string; frictionReports: string[]; } /** Runtime declarations and executable-version observations; not provider request attestation. */ export interface ActorRuntimeProvenance { schema: "humanish.actor-runtime.v1"; package: string; requestedVersion: string; observedVersion?: string; versionStatus: "unobserved" | "verified" | "failed"; requestedModel?: string; modelStatus: "declared" | "runtime_default_unobserved"; requestedReasoningEffort?: string; /** Codex turn.completed aggregates requests; it cannot establish per-request pricing tiers. */ usageGranularity: "runtime_turn"; } export interface ActorTrace { schema: typeof ACTOR_TRACE_SCHEMA; provider: string; providerVersion?: string; runtime?: ActorRuntimeProvenance; protocol: ActorProtocol; lane: ActorLane; persona: ActorPersonaRef; redaction: { status: "passed"; screenshots: "n/a" | "raw" | "blurred" | "ocr_scrubbed"; notes: string; }; startedAt: string; completedAt: string; durationMs: number; status: ActorStatus; completionReason: ActorCompletionReason; /** Absent in older traces and on routes that do not record a precise interruption cause. */ stopCause?: ActorStopCause; reason: string; ids: { sessionId?: string; threadId?: string; turnId?: string; model?: string; }; /** * ADDITIVE + OPTIONAL record of HOW the model was asked to run (humanish.model-settings.v1, * #497). `ids.model` says which model; this says the reasoning effort the request actually * carried. Present on lanes whose provider declares settings; absent everywhere else and on * every pre-existing bundle, and its absence is tolerated by verify. * * It exists because effort was a silent constant: unreachable from a lab, so every run took the * provider default. Effort is part of WHO the participant was, not of how the instrument was * tuned (docs/principles/actor-fidelity.md), so a trace that does not carry it is a result with * half its sample description missing — and two such traces cannot honestly be compared. */ modelSettings?: { reasoningEffort: string; maxOutputTokens?: number; }; counts: Record; /** * ADDITIVE + OPTIONAL affordance record (humanish.affordance-use.v1, #369): which KIND of route * this actor took — pointer, keyboard, url-navigation, script-execution, devtools, * browser-internal, observation — as per-class counts over the run's dispatched actions. * Present on computer-use lanes that dispatched at least one action; absent elsewhere and on * every pre-existing bundle (its absence is tolerated by verify). The harness records the class * and states NO verdict: whether a class is faithful depends on the population the study * declares, which is product semantics and belongs to the adopter's scorer. See * docs/principles/actor-fidelity.md. */ affordanceUse?: AffordanceUse; /** * ADDITIVE + OPTIONAL task funnel (humanish.task-funnel.v1, #414): how far this participant got * through the lab's declared protocol, corroborated per task by observations rather than by the * actor's own narration. Present only when the lab declared `tasks` and the session ran; absent * on every pre-existing bundle and on dry-run contract bundles (honest absence — a funnel that * was never measured is not an empty funnel). Its absence is tolerated by verify. */ taskFunnel?: TaskFunnel; /** * ADDITIVE + OPTIONAL (#570): the outcome the participant declared on its final turn, when its * provider's reply carries the field. Absent on free-text providers and on every older bundle. * The lane reads this before it reads the closing paragraph; three regex patches in one month * (#453, #549, #565) each fixed a false refusal and each left the next shape unhandled. */ declaredOutcome?: ParticipantDeclaredOutcome; /** Closing report after a harness-owned stop. Does not change task outcomes or permit actions. */ debrief?: { trigger: "stop_when" | "dwell"; status: "completed" | "skipped" | "failed"; reason: string; /** Absent if no request was made; false means that request's cost is unreported. */ usageReported?: boolean; report?: ParticipantClosingReport; /** Links the readable projection so it is not heuristically classified a second time. */ messageId?: string; }; items: ActorTraceItem[]; tokenUsage?: ActorTokenUsage; /** A stalled or adapter-reported ambiguous interaction may have additional unreported usage. * Known tokenUsage remains usable as a partial total. Absence is not proof of completeness. */ interactionUsageIncomplete?: true; /** * ADDITIVE + OPTIONAL token-derived cost ESTIMATE for this lane (humanish.actor-estimated-cost.v1). * Distinct from `tokenUsage.costUsd`, which is RESERVED for a real provider-returned charge: a * bare `costUsd` always means "the provider billed this", while `estimatedCost.estimatedCostUsd` * is a rate-table multiply named honestly as an estimate (invariant 6). Absent on codex/scripted * lanes and on every pre-existing bundle — its absence is tolerated by verify (fail-open on * display). A `null` estimatedCostUsd is DECLARED ABSENT (unknown rate / no usage), never 0. */ estimatedCost?: ActorEstimatedCost; capabilities: ActorCapabilities; } export declare const CODEX_APP_SERVER_CAPABILITIES: ActorCapabilities; export declare const PI_AGENT_CORE_CAPABILITIES: ActorCapabilities; export declare const CLAUDE_AGENT_SDK_CAPABILITIES: ActorCapabilities; export declare const SCRIPTED_BROWSER_CAPABILITIES: ActorCapabilities; export declare const TERMINAL_AGENT_CAPABILITIES: ActorCapabilities; export declare function codexStatusToCompletionReason(status: CodexAppServerStatus): ActorCompletionReason; /** * Map a Codex app-server run result into the provider-neutral ActorTrace. Pure * and side-effect-free. The persona reference is supplied by the harness; until * personas are load-bearing it is a minimal stub ({ id, traitsApplied: [], * promptDigest }). */ export declare function codexResultToActorTrace(result: CodexAppServerRunResult, persona: ActorPersonaRef): ActorTrace;