UNPKG

kestrel.markets

Version:

A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.

157 lines 12.9 kB
/** * # session/config — the sim config descriptor: canonicalize → ConfigId → the a57.14 envelope (kestrel-5zl.6) * * The comparison axis of Simulate is the **{@link AgentConfig} descriptor**: the same recorded tape graded * under N configs yields comparable {@link import("../blotter").Blotter}s, keyed by config. This module is * the CAPTURE side of that axis — three pure derivations, no wall clock, no RNG (RUNTIME §0): * * 1. {@link canonicalizeConfig} — `(AgentConfig) → a canonical, normalized descriptor`. Deterministic and * ORDER-INSENSITIVE: the SAME field values in ANY key insertion order (top-level or nested) canonicalize * byte-identically, via the Blotter's own sorted-key / drop-undefined canonicalizer (the agent-harness ADR (d)). * 2. {@link deriveConfigId} — `(canonical) → ConfigId = sha256(canonical)` (the agent-harness ADR (d)). The **grid * column key** the config matrix (5zl.7 run-identity, 5zl.8 grid/leaderboard) keys a run by: same config * ⇒ same id; ANY changed field ⇒ a DIFFERENT id (a config edit mints a new column, never contaminates an * old one). The ConfigId is a SUPERSET of the envelope identity — it additionally folds the sampling axis * (`temperature`/`thinkingLevel`), `format`, and the leaderboard `label`, none of which are envelope * fields — so two runs differing only in `temperature` are different columns yet share every envelope field. * 3. {@link envelopeOf} / {@link envelopeForConfig} — map the canonical descriptor to the a57.14 * {@link ExperimentalEnvelope} (its INPUT half — the agent-harness ADR (d): one definition, never a second one), which * the Simulate driver stamps onto the graded META so the finalized Blotter carries it. The `prompt_hash` * carries a HASH, NEVER prompt/strategy content (the provenance fence, AGENTS.md §7). * * ## The descriptor → envelope field map (deliverable 1) * ModelIdentity provider → provider · model → model · version → version · trainingCutoff → training_cutoff * RenderingIdentity tokenizer → tokenizer · renderingIdentity → rendering_identity (format rides the ConfigId only) * AuthorPolicy promptHash → prompt_hash (a HASH) · toolPolicy → tool_policy · wakeSource → wake_source · * corpusTier → corpus_tier * Interface/harness face → face (http|sdk|cli|mcp) · adapter → adapter · adapterVersion → adapter_version * (m9i.2 owner tweaks — the m9i.7 tournament axes, mandatory-for-certified) * injected tokenAccounting → token_accounting (off the deterministic record path) * ConfigId-only cachePolicy (kestrel-rul, System Profile v1.1 `cache policy`) — folds into the ConfigId * like `format`/`temperature`, but is DELIBERATELY NOT an envelope field: caching is * transport-side + off the graded path, so two policies grade identically yet mint * distinct grid columns (the m9i.5 append-only-vs-redraw / cache-on-off A/B). * cacheTtl (kestrel-wa0j.1) rides the same rule: a ConfigId-only transport axis (absent ⇒ * provider default ⇒ existing ConfigIds are untouched), never an envelope field. * clockHonest + latencyBufferMs (ADR-0040 / kestrel-w7la.1) are ConfigId axes too, never * envelope fields (the a57.14 fourteen stay frozen): they fold in automatically via the * generic canonicalizer, so a clocked run — or a different buffer — NEVER shares a grid * column with an unclocked one (clock-honest wakes §4). Absent ⇒ latency-blind ⇒ every * existing ConfigId is byte-identical. * * ## Fail-closed by omission (the blast-radius rule, a57.14) * A NO-LLM Backtest ({@link import("./agent.ts").fixedPlanAgent} / `recordedAgent`, whose * {@link import("./agent.ts").BACKTEST_CONFIG} authors nothing — no `provider`/`version`/`promptHash`/…) * DECLARES no envelope: {@link declaresEnvelope} is false ⇒ no stamp ⇒ the Blotter stays `certified` (exactly * a57.14's "a bus declaring NO envelope stays certified", and "never fabricate identity"). Only a config that * declares authorship stamps — and a declared-but-INCOMPLETE identity (e.g. a blank `provider`) is stamped as * an incomplete envelope so a57.14's projector fails the Blotter closed to PROVISIONAL, never omitted (which * would certify-by-implication) and never silently defaulted. */ import type { AgentConfig } from "./agent.ts"; import type { ExperimentalEnvelope, SampledQualificationClaim } from "../bus/index.ts"; import { type Json } from "../canonical/json.ts"; /** The canonical, normalized config descriptor: a plain JSON object whose keys (top-level and nested) are * lexicographically sorted, so `JSON.stringify` is a byte-stable function of the DATA, never of insertion * order — the substrate {@link deriveConfigId} hashes. `Json` is the canonical value type * ({@link ../canonical/json.ts}); `deepSort` was a byte-identical copy now folded into that one owner. */ export type CanonicalConfig = { readonly [k: string]: Json; }; /** * Canonicalize an {@link AgentConfig} into its normalized descriptor: sorted keys, dropped `undefined`, * recursive — the SAME {@link canonicalize} the Blotter serializes through, so the ConfigId and the Blotter * share one notion of "canonical". PURE and ORDER-INSENSITIVE — the same field values in any key order * (incl. the nested `tokenAccounting`) fold to byte-identical output. No wall clock, no RNG (RUNTIME §0). */ export declare function canonicalizeConfig(config: AgentConfig): CanonicalConfig; /** * Derive the {@link ConfigId} — the deterministic **grid column key** — as `sha256(canonical)` (the agent-harness ADR * (d)). Re-canonicalizes its input so the id is stable even if handed a raw descriptor. Same config ⇒ same * 64-hex id; ANY changed field ⇒ a different id. Pure (no wall clock, no RNG). */ export declare function deriveConfigId(canonical: unknown): string; /** The canonical descriptor with the {@link REPLICATE_AXIS_KEYS} (fillSeed) removed — the substrate the CELL * config axis hashes. Identical to {@link canonicalizeConfig} for a config that carries no replicate axis, so * a seedless run's cell axis equals its full ConfigId. Pure, order-insensitive. */ export declare function cellCanonicalConfig(config: AgentConfig): CanonicalConfig; /** * The **cell config-axis id**: `deriveConfigId(cellCanonicalConfig(config))` — the full ConfigId with the * replicate axes (fillSeed) stripped. This is the config axis the grid {@link import("./run-identity.ts").CellKey * CellKey} keys on (m9i.2 owner tweak 1 — temperature-only variants are DISTINCT cells — reconciled with * ADR-0016: seed-only variants are REPLICATES in ONE cell, not distinct columns). The ONE definition shared by * the driver's `cell_config_id` stamp and the grid's CellKey/cellOf, so they can never drift. For a seedless * config it equals the full {@link deriveConfigId ConfigId}. Pure (no wall clock, no RNG). */ export declare function cellConfigId(config: AgentConfig): string; /** * Derive the stamped `rendering_identity` from a renderer revision — the DRIFT-PROOF binding (ADR-0013(d), * SYSTEM-PROFILE.md `rendering_identity`). A content-addressed handle `sha256:${sha256("kestrel.rendering/" + * rev)}` (the codebase's {@link import("../catalog/session-catalog.ts") RenderingIdentity} handle idiom — the * analog of the catalog's NO-LLM machine-frame handle), NOT a free per-config string a human bumps. * * A REAL authoring run sets `renderingIdentity = deriveRenderingIdentity()`, so the identity is a function of * the renderer's actual revision: a renderer BEHAVIORAL change bumps {@link RENDERER_REVISION} ⇒ a DIFFERENT * handle ⇒ a DIFFERENT {@link deriveConfigId ConfigId}/{@link import("./run-identity.ts").CellKey CellKey} (a * fresh grid column, never a collapse into the old one — the drift m9i.24 closes). The default argument binds * to the ONE-place {@link RENDERER_REVISION} source (`src/frame/renderer-revision.ts`), so there is exactly one * place a renderer revision is declared. Pure — no wall clock, no RNG (RUNTIME §0). * * NO-CHURN: this is purely ADDITIVE — it does NOT touch {@link canonicalizeConfig}/{@link deriveConfigId}/ * {@link cellConfigId}/{@link envelopeOf}, so every existing explicit-label ConfigId/CellKey/conformance-root/ * grid pin is byte-identical; the derived value for the current renderer is a stable pin, and only a FUTURE * revision bump moves it. */ export declare function deriveRenderingIdentity(rendererRevision?: string): string; /** * Does this config DECLARE an experimental envelope (i.e. is it an authoring run, not a NO-LLM Backtest)? * True iff it carries any {@link ENVELOPE_DECLARING_KEYS} field. A {@link import("./agent.ts").BACKTEST_CONFIG} * carries none ⇒ false ⇒ no envelope is stamped (fail-closed by omission — the Blotter stays `certified`, * a57.14). A config with a BLANK required field (e.g. `provider: ""`) still DECLARES (it carries the other * identity fields) ⇒ true ⇒ its incomplete envelope IS stamped so the projector fails it closed. */ export declare function declaresEnvelope(config: AgentConfig): boolean; /** * Map a canonical descriptor to the a57.14 {@link ExperimentalEnvelope} (the 1:1 field map above). `format`, * `temperature`, `thinkingLevel`, and `label` are NOT envelope identity — they ride the ConfigId only. The * `prompt_hash` is carried verbatim as a HASH; this function never sees prompt/strategy content (the * provenance fence). Pure — a deterministic function of the descriptor. */ export declare function envelopeOf(canonical: CanonicalConfig): ExperimentalEnvelope; /** * The envelope a Simulate run stamps for `config`, or `undefined` when the config declares none (a NO-LLM * Backtest — {@link declaresEnvelope} false ⇒ the driver stamps no `meta.envelope` ⇒ the Blotter stays * `certified`, a57.14). When it declares one, the canonicalized descriptor is mapped through * {@link envelopeOf} (complete ⇒ the projector stamps it + certifies; incomplete ⇒ the projector fails it * closed to PROVISIONAL — capture happens HERE, the fail-closed evaluation stays in a57.14). Pure. */ export declare function envelopeForConfig(config: AgentConfig): ExperimentalEnvelope | undefined; /** * The `runSeed` a Simulate run realizes sampled (seeded-Bernoulli) fills under, or `undefined` when the * config declares none — in which case **the sampled channel is OFF** (fail-closed, ADR-0016 §5): the run * grades the deterministic floor + analytic expected totals only, taking no draws. A malformed seed (a * non-finite or non-integer value) is treated exactly like an absent one — the sampler is never armed off a * value that would not replay byte-identically, never a silent `0`. * * This is the ONE provenance read the Simulate driver (kestrel-9gu.8.3) uses to decide whether to construct * the engine with a seeded rng. It reads the SamplingConfig axis, NOT the envelope: `fillSeed` folds into the * full {@link deriveConfigId ConfigId} / {@link import("./run-identity.ts").SimRunId SimRunId} (each seed is a * distinct RUN) but is a **replicate axis** STRIPPED from the cell config axis ({@link cellConfigId}), so two * seeds share one {@link import("./run-identity.ts").CellKey CellKey} (replicates in one cell — ADR-0016 * ensembles) while each graded bus — carrying different realized fills — hashes to a distinct SimRunId. Pure. */ export declare function fillSeedOf(config: AgentConfig): number | undefined; /** * The owner-recorded {@link SampledQualificationClaim} a config carries (`sampledQualification`), or * `undefined` when it declares none — in which case **the qualification gate fails closed** and the * strict-cross floor stays the headline (kestrel-9gu.12; 9gu.8 AC#7). A malformed claim (a missing leg, a * non-boolean, an empty reference) is treated exactly like an absent one — never coerced onto a passing * shape (RUNTIME §8, via {@link readSampledQualificationClaim}). * * Like `fillSeed`, the claim folds into the {@link deriveConfigId ConfigId} (a qualification edit mints a * new grid column-run) but not the a57.14 envelope. As of kestrel-9gu.12 **no study has passed** — the * study is kestrel-9gu.8.6 — so no shipped config declares this; the field exists so the gate seam is * exercised end-to-end and flipping the headline later is DATA, not code. Pure. */ export declare function sampledQualificationOf(config: AgentConfig): SampledQualificationClaim | undefined; //# sourceMappingURL=config.d.ts.map