/** * ⚠️ EXPERIMENTAL — the EMIT delivery for a typed result: the skill CALLS a tool * carrying its outcome instead of ENDING its turn with a fenced block. * * ## Why this exists * * A typed `output` (an `OutputContract`) is valid only on a forked skill: compile * hard-errors `output-without-fork` on the other 31, because an inline skill is * spliced into the conversation, has no call→return boundary, and therefore has * no return value to type (`research/spec-syntax-and-railway-scope.md`). * * A TOOL CALL needs no return boundary. The skill does not return the structure — * it EMITS it, mid-conversation, and the call lands in `Trace.toolCalls`. So the * objection that grounds the exclusion does not apply to this delivery. Same * `OutputContract`; a different way of getting it out. * * ## What is UNPROVEN (why the `experimental_` prefix is on every runtime export) * * 1. **N=8, one skill, one model.** Measured 2026-08-13 against `paper-status` * (unforked; `allowed-tools: Bash, Read, Grep, Glob`) on sonnet: 8 runs, 8 * emits, all on the `ok` track, all parsing against the contract, none * repeated. That answers "does it land at all" and nothing about the rate — * 8 of 8 bounds the true failure rate at about 31%, which is not evidence of * reliability. Raw arguments + the free re-scorer: * `examples/experimental-emit/`. * 2. **Nobody depends on it.** Neither `compileSkill` nor `compileAgent` emits * this instruction; the caller pastes `.instruction` into a skill body and * serves `.tool` from their own MCP server by hand. There is no compile-time * path, so no skill in any corpus is typed by it yet. * 3. **The transport is not part of the contract.** MCP is how the tool reached * the model in the measurement. Whether a plugin can hand the model a tool * WITHOUT a separate server process is untested, and the answer changes what * this surface should look like. * 4. **The runtime does not enforce `required`.** Measured: Claude Code accepted a * call omitting three declared-required fields (raw proof in `mine`, * `vigiles/repro/output-contract-2026-08-13/mcp-arm/schema-probe-emitted.jsonl`). * `inputSchema` is DESCRIPTION for the model, not a runtime gate — which is * exactly why `experimental_parseEmitted` re-validates on the receiving side * and why "the API validates it for you" must not be claimed. * * ## What would have to be true to drop the prefix * * - A rate, not an existence proof: ≥30 trials across ≥2 unforked skills and ≥2 * models, with the emit-landing rate reported and its failure modes named. * - One consumer inside vigiles that compiles the instruction from the spec, so * the tool name and the shape cannot drift apart by hand. * - A measured answer to (3) — plugin-served tool vs external MCP server — since * an in-process tool would remove the standing-up cost this surface assumes. * * Until then: not covered by the stability guarantee, may change or be removed * without a major bump. See `docs/../STABILITY.md` and `src/experimental.ts`. * * @experimental * @module */ import type { OutputContract } from "./core/spec.js"; import type { ToolCall } from "./core/harness-driver.js"; import { type ParsedAgentResult } from "./adapters/claude-code/agent-result.js"; /** A JSON-Schema fragment for one declared field. */ export type EmitFieldSchema = { readonly type: "string"; } | { readonly type: "number"; } | { readonly type: "boolean"; } | { readonly type: "array"; readonly items: { readonly type: "string"; }; } | { readonly type: "string"; readonly enum: readonly string[]; }; /** The `track` discriminator's schema — the enum this module owns, not the author's. */ export interface EmitTrackSchema { readonly type: "string"; readonly enum: readonly ["ok", "err"]; } /** Anything that can sit under `properties`: a field, the discriminator, a track. */ export type EmitPropertySchema = EmitFieldSchema | EmitTrackSchema | EmitObjectSchema; /** A JSON-Schema object node — one track's payload, or the whole argument. */ export interface EmitObjectSchema { readonly type: "object"; readonly properties: Readonly>; readonly required: readonly string[]; readonly additionalProperties: false; } /** An MCP tool definition, in the shape a `tools/list` response carries. */ export interface EmitToolDefinition { readonly name: string; readonly description: string; readonly inputSchema: EmitObjectSchema; } /** What `experimental_emitTool` hands back: the tool, and the prose that asks for it. */ export interface ExperimentalEmitTool { /** Serve this from your MCP server's `tools/list`. */ readonly tool: EmitToolDefinition; /** * Markdown fragment for the skill body. The SAME contract rendered for the * model — kept next to the schema so the two cannot drift when hand-wired. */ readonly instruction: string; } /** * ⚠️ EXPERIMENTAL. Derive an emit TOOL from an `OutputContract` — the same * contract the fork rail renders as a `vigiles:ok` / `vigiles:err` fenced block. * * const emit = experimental_emitTool(contract); * // emit.tool → serve from your MCP server * // emit.instruction → paste into the (unforked) skill's body * * The two tracks are NESTED (`{ track, ok? , err? }`), not flattened into one bag * of fields. That is deliberate: a flat union cannot say which fields are required * on which track, so "success fields mixed with error fields" would be a * well-formed call. Nested, it is not expressible. * * 🔴 `required` in the returned schema is DESCRIPTION, not enforcement — measured, * see the module header (4). Validate what arrives with * `experimental_parseEmitted`. * * @experimental */ export declare function experimental_emitTool(contract: OutputContract, options?: { readonly name?: string; }): ExperimentalEmitTool; /** * ⚠️ EXPERIMENTAL. Read the emitted result out of a run's tool calls, validated * against the contract. Pure — returns the same `ParsedAgentResult` vocabulary the * fenced rail's `parseAgentResult` returns, so an eval `measure` can use it as a * metric and the assertion below can wrap it, without one dual-purpose function. * * Accepts `Trace["toolCalls"]`, `SubagentTrace["toolCalls"]` or an eval * `ctx.toolCalls`. Names are matched bare (`emit_result`) or MCP-prefixed * (`mcp____emit_result`). * * Differs from the fenced rail in ONE deliberate way: **more than one call is * `malformed`, not last-one-wins.** The fenced parser takes the LAST block because * an earlier block may be illustrative reasoning; a tool call is an action, never * illustrative, so "exactly once" is checkable here and is not checkable there. * * @experimental */ export declare function experimental_parseEmitted(toolCalls: readonly ToolCall[], contract: OutputContract, options?: { readonly name?: string; }): ParsedAgentResult; /** * ⚠️ EXPERIMENTAL. Assert the run emitted a SUCCESS result, and return its value — * the emit-channel counterpart of `assertAgentOk`, for a skill that has no return * value to assert on. * * Throws on a missing emit, a repeated emit, an error track, or a payload that * does not match the contract. The failure message names every tool the run DID * call, because "the skill never emitted" and "the skill emitted the wrong shape" * are different bugs and the tool list separates them at a glance. * * The error track is reachable through `experimental_parseEmitted`; a matching * `…EmittedErr` is deliberately NOT shipped while the surface is this young — * three exports is the whole prototype. * * @experimental */ export declare function experimental_assertEmittedOk(toolCalls: readonly ToolCall[], contract: OutputContract, options?: { readonly name?: string; }): Record; //# sourceMappingURL=experimental-emit.d.ts.map