@kernel.chat/kbot 4.0.1 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/futures/debate/index.d.ts +7 -0
  2. package/dist/futures/debate/index.js +6 -0
  3. package/dist/futures/debate/runner.d.ts +34 -0
  4. package/dist/futures/debate/runner.js +140 -0
  5. package/dist/futures/debate/synthesis.d.ts +25 -0
  6. package/dist/futures/debate/synthesis.js +81 -0
  7. package/dist/futures/debate/types.d.ts +72 -0
  8. package/dist/futures/debate/types.js +12 -0
  9. package/dist/futures/forecast/index.d.ts +5 -0
  10. package/dist/futures/forecast/index.js +5 -0
  11. package/dist/futures/forecast/projection.d.ts +31 -0
  12. package/dist/futures/forecast/projection.js +177 -0
  13. package/dist/futures/forecast/synthesize.d.ts +19 -0
  14. package/dist/futures/forecast/synthesize.js +89 -0
  15. package/dist/futures/forecast/types.d.ts +59 -0
  16. package/dist/futures/forecast/types.js +15 -0
  17. package/dist/futures/harness/critic-evaluator.d.ts +39 -0
  18. package/dist/futures/harness/critic-evaluator.js +131 -0
  19. package/dist/futures/harness/evolution-loop.d.ts +41 -0
  20. package/dist/futures/harness/evolution-loop.js +168 -0
  21. package/dist/futures/harness/index.d.ts +16 -0
  22. package/dist/futures/harness/index.js +13 -0
  23. package/dist/futures/harness/meta-evolution.d.ts +32 -0
  24. package/dist/futures/harness/meta-evolution.js +52 -0
  25. package/dist/futures/harness/noop-evolution.d.ts +23 -0
  26. package/dist/futures/harness/noop-evolution.js +29 -0
  27. package/dist/futures/harness/persistence.d.ts +30 -0
  28. package/dist/futures/harness/persistence.js +99 -0
  29. package/dist/futures/harness/types.d.ts +147 -0
  30. package/dist/futures/harness/types.js +18 -0
  31. package/dist/futures/index.d.ts +16 -0
  32. package/dist/futures/index.js +22 -0
  33. package/dist/futures/latent-state/envelope.d.ts +39 -0
  34. package/dist/futures/latent-state/envelope.js +178 -0
  35. package/dist/futures/latent-state/index.d.ts +5 -0
  36. package/dist/futures/latent-state/index.js +3 -0
  37. package/dist/futures/latent-state/types.d.ts +47 -0
  38. package/dist/futures/latent-state/types.js +13 -0
  39. package/dist/futures/persona/check.d.ts +45 -0
  40. package/dist/futures/persona/check.js +205 -0
  41. package/dist/futures/persona/index.d.ts +5 -0
  42. package/dist/futures/persona/index.js +5 -0
  43. package/dist/futures/persona/registry.d.ts +22 -0
  44. package/dist/futures/persona/registry.js +124 -0
  45. package/dist/futures/persona/types.d.ts +68 -0
  46. package/dist/futures/persona/types.js +28 -0
  47. package/dist/futures/skill-graph/graph.d.ts +31 -0
  48. package/dist/futures/skill-graph/graph.js +151 -0
  49. package/dist/futures/skill-graph/index.d.ts +13 -0
  50. package/dist/futures/skill-graph/index.js +10 -0
  51. package/dist/futures/skill-graph/synthesis.d.ts +20 -0
  52. package/dist/futures/skill-graph/synthesis.js +83 -0
  53. package/dist/futures/skill-graph/types.d.ts +53 -0
  54. package/dist/futures/skill-graph/types.js +19 -0
  55. package/dist/tools/forecast-summary.d.ts +25 -0
  56. package/dist/tools/forecast-summary.js +204 -0
  57. package/dist/tools/swarm-2026-04.js +2 -0
  58. package/package.json +1 -1
@@ -0,0 +1,23 @@
1
+ /**
2
+ * No-op EvolutionAgent — records but never rewrites the harness.
3
+ *
4
+ * The Sylph paper's outer-loop value comes from the EvolutionAgent
5
+ * mutating the harness based on history. Real, safe code rewriting is a
6
+ * multi-month problem; this stub satisfies the interface so the inner
7
+ * loop runs end-to-end. Every other piece of the substrate (trace
8
+ * persistence, regression detection, A/B evaluation, harness diffing)
9
+ * works without any actual mutation.
10
+ *
11
+ * The contract is met; the substrate is shipped.
12
+ */
13
+ import type { EvolutionAgent, EvolutionRecord, Harness } from './types.js';
14
+ export declare class NoopEvolutionAgent implements EvolutionAgent {
15
+ /**
16
+ * Returns the input harness unchanged. Reads `history` only to allow
17
+ * subclasses to subscribe to inspection without forcing a re-read.
18
+ */
19
+ evolve(history: EvolutionRecord[], best: Harness): Promise<Harness>;
20
+ }
21
+ /** Convenience factory mirroring the rest of the futures module style. */
22
+ export declare function createNoopEvolutionAgent(): EvolutionAgent;
23
+ //# sourceMappingURL=noop-evolution.d.ts.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * No-op EvolutionAgent — records but never rewrites the harness.
3
+ *
4
+ * The Sylph paper's outer-loop value comes from the EvolutionAgent
5
+ * mutating the harness based on history. Real, safe code rewriting is a
6
+ * multi-month problem; this stub satisfies the interface so the inner
7
+ * loop runs end-to-end. Every other piece of the substrate (trace
8
+ * persistence, regression detection, A/B evaluation, harness diffing)
9
+ * works without any actual mutation.
10
+ *
11
+ * The contract is met; the substrate is shipped.
12
+ */
13
+ export class NoopEvolutionAgent {
14
+ /**
15
+ * Returns the input harness unchanged. Reads `history` only to allow
16
+ * subclasses to subscribe to inspection without forcing a re-read.
17
+ */
18
+ async evolve(history, best) {
19
+ // Touch parameters explicitly so the type-checker stays happy in strict
20
+ // mode and the intent — "we saw history, we chose not to act" — is clear.
21
+ void history;
22
+ return best;
23
+ }
24
+ }
25
+ /** Convenience factory mirroring the rest of the futures module style. */
26
+ export function createNoopEvolutionAgent() {
27
+ return new NoopEvolutionAgent();
28
+ }
29
+ //# sourceMappingURL=noop-evolution.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Harness Evolution Loop — JSONL trace persistence.
3
+ *
4
+ * Each task gets its own append-only JSONL file at
5
+ * `~/.kbot/futures/harness/<task-id>.jsonl`. One line per `EvolutionRecord`
6
+ * (or arbitrary JSON-serializable record). Append-only on the hot path so
7
+ * concurrent loops don't trample each other; reads parse line-by-line and
8
+ * skip malformed lines rather than throwing on a single bad row.
9
+ *
10
+ * Pattern mirrors `src/planner/hierarchical/persistence.ts`: state dir is
11
+ * configurable (default `~/.kbot/futures/harness`), atomic writes where
12
+ * possible, ENOENT swallowed on read paths.
13
+ */
14
+ import type { EvolutionRecord } from './types.js';
15
+ /** Default on-disk root: `~/.kbot/futures/harness/`. */
16
+ export declare function defaultStateDir(): string;
17
+ /** Append a single record as one JSONL line. */
18
+ export declare function appendTrace(taskId: string, record: EvolutionRecord, stateDir?: string): Promise<void>;
19
+ /**
20
+ * Read all records for a task in append order. Returns empty array if the
21
+ * file doesn't exist. Malformed lines are skipped — one bad row never
22
+ * invalidates the whole history.
23
+ */
24
+ export declare function readHistory(taskId: string, stateDir?: string): Promise<EvolutionRecord[]>;
25
+ /**
26
+ * Delete trace files older than `days` (by mtime). Returns the list of
27
+ * removed task ids. Pure janitor — never throws on individual failures.
28
+ */
29
+ export declare function pruneOlderThan(days: number, stateDir?: string): Promise<string[]>;
30
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Harness Evolution Loop — JSONL trace persistence.
3
+ *
4
+ * Each task gets its own append-only JSONL file at
5
+ * `~/.kbot/futures/harness/<task-id>.jsonl`. One line per `EvolutionRecord`
6
+ * (or arbitrary JSON-serializable record). Append-only on the hot path so
7
+ * concurrent loops don't trample each other; reads parse line-by-line and
8
+ * skip malformed lines rather than throwing on a single bad row.
9
+ *
10
+ * Pattern mirrors `src/planner/hierarchical/persistence.ts`: state dir is
11
+ * configurable (default `~/.kbot/futures/harness`), atomic writes where
12
+ * possible, ENOENT swallowed on read paths.
13
+ */
14
+ import { promises as fs } from 'node:fs';
15
+ import * as os from 'node:os';
16
+ import * as path from 'node:path';
17
+ /** Default on-disk root: `~/.kbot/futures/harness/`. */
18
+ export function defaultStateDir() {
19
+ return path.join(os.homedir(), '.kbot', 'futures', 'harness');
20
+ }
21
+ function safeId(taskId) {
22
+ // Restrict to filesystem-safe chars; collapse anything else to '_'.
23
+ return taskId.replace(/[^a-zA-Z0-9._-]/g, '_');
24
+ }
25
+ function tracePath(stateDir, taskId) {
26
+ return path.join(stateDir, `${safeId(taskId)}.jsonl`);
27
+ }
28
+ async function ensureDir(dir) {
29
+ await fs.mkdir(dir, { recursive: true });
30
+ }
31
+ /** Append a single record as one JSONL line. */
32
+ export async function appendTrace(taskId, record, stateDir = defaultStateDir()) {
33
+ await ensureDir(stateDir);
34
+ const line = JSON.stringify(record) + '\n';
35
+ await fs.appendFile(tracePath(stateDir, taskId), line, 'utf8');
36
+ }
37
+ /**
38
+ * Read all records for a task in append order. Returns empty array if the
39
+ * file doesn't exist. Malformed lines are skipped — one bad row never
40
+ * invalidates the whole history.
41
+ */
42
+ export async function readHistory(taskId, stateDir = defaultStateDir()) {
43
+ let raw;
44
+ try {
45
+ raw = await fs.readFile(tracePath(stateDir, taskId), 'utf8');
46
+ }
47
+ catch (err) {
48
+ if (err.code === 'ENOENT')
49
+ return [];
50
+ throw err;
51
+ }
52
+ const out = [];
53
+ for (const line of raw.split('\n')) {
54
+ const trimmed = line.trim();
55
+ if (!trimmed)
56
+ continue;
57
+ try {
58
+ out.push(JSON.parse(trimmed));
59
+ }
60
+ catch {
61
+ // skip malformed line
62
+ }
63
+ }
64
+ return out;
65
+ }
66
+ /**
67
+ * Delete trace files older than `days` (by mtime). Returns the list of
68
+ * removed task ids. Pure janitor — never throws on individual failures.
69
+ */
70
+ export async function pruneOlderThan(days, stateDir = defaultStateDir()) {
71
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
72
+ let entries;
73
+ try {
74
+ entries = await fs.readdir(stateDir);
75
+ }
76
+ catch (err) {
77
+ if (err.code === 'ENOENT')
78
+ return [];
79
+ throw err;
80
+ }
81
+ const removed = [];
82
+ for (const entry of entries) {
83
+ if (!entry.endsWith('.jsonl'))
84
+ continue;
85
+ const full = path.join(stateDir, entry);
86
+ try {
87
+ const stat = await fs.stat(full);
88
+ if (stat.mtimeMs < cutoff) {
89
+ await fs.unlink(full);
90
+ removed.push(entry.replace(/\.jsonl$/, ''));
91
+ }
92
+ }
93
+ catch {
94
+ // skip — permission, race, etc.
95
+ }
96
+ }
97
+ return removed;
98
+ }
99
+ //# sourceMappingURL=persistence.js.map
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Harness Evolution Loop — type definitions.
3
+ *
4
+ * Maps onto the formalism from "The Last Harness You'll Ever Build"
5
+ * (Seong, Yin, Zhang — Sylph.AI, arXiv:2604.21003):
6
+ *
7
+ * Agent = Model + Harness
8
+ * Harness = prompts + tools + orchestration + hooks + model config
9
+ *
10
+ * Inner loop: Worker(τ) → Evaluator(τ) → EvolutionAgent(history) → H'
11
+ * Outer loop: HarnessEvolution × N tasks → MetaEvolutionAgent(history) → Λ'
12
+ *
13
+ * This module is types-only. Runtime lives in evolution-loop.ts and
14
+ * meta-evolution.ts. No imports from heavy modules so it can be loaded by
15
+ * tools, tests, and remote runners cheaply.
16
+ */
17
+ /** Identifier for a single task instance the worker is being optimized against. */
18
+ export interface Task {
19
+ id: string;
20
+ /** Concrete instructions the worker reads. */
21
+ instructions: string;
22
+ /** Verifiable success criteria — the evaluator's checklist. */
23
+ acceptance: string[];
24
+ /** Optional free-form metadata (domain, expected runtime, etc.). */
25
+ meta?: Record<string, unknown>;
26
+ }
27
+ /**
28
+ * The harness — every piece of code/config that surrounds the model.
29
+ * Kept as data so it can be diffed, persisted, and rewritten by EvolutionAgent.
30
+ */
31
+ export interface Harness {
32
+ /** Stable identifier; new id on every evolution step. */
33
+ id: string;
34
+ systemPrompt: string;
35
+ /** Tool names the worker is allowed to call. */
36
+ toolAllowlist: string[];
37
+ /** Hooks/middleware applied around tool calls. */
38
+ hooks: HookSpec[];
39
+ /** Model routing — which model handles which subtask kind. */
40
+ modelRouting: ModelRoute[];
41
+ /** Loop hyperparameters: max iterations, parallelism, revert thresholds. */
42
+ hyperparams: Hyperparams;
43
+ }
44
+ export interface HookSpec {
45
+ name: string;
46
+ /** When to fire: before tool call, after, on error, etc. */
47
+ phase: 'pre-tool' | 'post-tool' | 'on-error' | 'pre-response';
48
+ /** Free-form config; the runtime resolves to actual hook code. */
49
+ config?: Record<string, unknown>;
50
+ }
51
+ export interface ModelRoute {
52
+ /** Pattern match on task kind / tool / phase. */
53
+ match: string;
54
+ model: string;
55
+ temperature?: number;
56
+ maxTokens?: number;
57
+ }
58
+ export interface Hyperparams {
59
+ maxIterations: number;
60
+ /** Stop early if score >= this on successive iterations. */
61
+ earlyStopScore?: number;
62
+ /** If a step regresses by more than this, revert to best. */
63
+ revertThreshold?: number;
64
+ }
65
+ /** Trace produced by Worker.execute() for the Evaluator to inspect. */
66
+ export interface ExecutionTrace {
67
+ taskId: string;
68
+ harnessId: string;
69
+ steps: TraceStep[];
70
+ finalState: Record<string, unknown>;
71
+ llmTimeMs: number;
72
+ toolTimeMs: number;
73
+ }
74
+ export interface TraceStep {
75
+ index: number;
76
+ phase: 'plan' | 'tool' | 'response' | 'observe';
77
+ action: string;
78
+ output?: string;
79
+ error?: string;
80
+ durationMs: number;
81
+ }
82
+ /** Evaluator output: pass/fail + score + diagnostic narrative. */
83
+ export interface EvaluationReport {
84
+ taskId: string;
85
+ harnessId: string;
86
+ pass: boolean;
87
+ /** Two-tier score: pass yields 1.0, scaled by efficiency tiebreaker. */
88
+ score: number;
89
+ /** Per-criterion verdict. Length matches Task.acceptance. */
90
+ criteriaResults: CriterionResult[];
91
+ /** Categorized failure modes for the Evolution Agent to act on. */
92
+ failureModes: FailureMode[];
93
+ /** Free-form diagnostic prose. */
94
+ notes?: string;
95
+ }
96
+ export interface CriterionResult {
97
+ criterion: string;
98
+ passed: boolean;
99
+ evidence?: string;
100
+ }
101
+ export type FailureModeKind = 'incorrect-tool-usage' | 'reasoning-loop' | 'misinterpreted-state' | 'excessive-latency' | 'missing-capability' | 'hallucinated-state' | 'other';
102
+ export interface FailureMode {
103
+ kind: FailureModeKind;
104
+ detail: string;
105
+ }
106
+ /** One row in the evolution history. */
107
+ export interface EvolutionRecord {
108
+ iteration: number;
109
+ harness: Harness;
110
+ trace: ExecutionTrace;
111
+ report: EvaluationReport;
112
+ verdict: 'improved' | 'regressed' | 'no-op';
113
+ }
114
+ /** Final result of a single inner-loop run. */
115
+ export interface EvolutionResult {
116
+ taskId: string;
117
+ bestHarness: Harness;
118
+ bestScore: number;
119
+ history: EvolutionRecord[];
120
+ }
121
+ /** Worker = the agent under optimization, parameterized by harness. */
122
+ export interface Worker {
123
+ execute(task: Task, harness: Harness): Promise<ExecutionTrace>;
124
+ }
125
+ /** Evaluator = adversarial reviewer; produces EvaluationReport. */
126
+ export interface Evaluator {
127
+ evaluate(trace: ExecutionTrace, task: Task): Promise<EvaluationReport>;
128
+ }
129
+ /** EvolutionAgent = mutates the harness based on history. */
130
+ export interface EvolutionAgent {
131
+ evolve(history: EvolutionRecord[], best: Harness): Promise<Harness>;
132
+ }
133
+ /** Λ — the evolution protocol itself. */
134
+ export interface EvolutionProtocol {
135
+ worker: Worker;
136
+ evaluator: Evaluator;
137
+ evolution: EvolutionAgent;
138
+ initialHarness: Harness;
139
+ hyperparams: Hyperparams;
140
+ }
141
+ /** Outer-loop result. */
142
+ export interface MetaResult {
143
+ bestProtocol: EvolutionProtocol;
144
+ bestMetaScore: number;
145
+ perTask: EvolutionResult[];
146
+ }
147
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Harness Evolution Loop — type definitions.
3
+ *
4
+ * Maps onto the formalism from "The Last Harness You'll Ever Build"
5
+ * (Seong, Yin, Zhang — Sylph.AI, arXiv:2604.21003):
6
+ *
7
+ * Agent = Model + Harness
8
+ * Harness = prompts + tools + orchestration + hooks + model config
9
+ *
10
+ * Inner loop: Worker(τ) → Evaluator(τ) → EvolutionAgent(history) → H'
11
+ * Outer loop: HarnessEvolution × N tasks → MetaEvolutionAgent(history) → Λ'
12
+ *
13
+ * This module is types-only. Runtime lives in evolution-loop.ts and
14
+ * meta-evolution.ts. No imports from heavy modules so it can be loaded by
15
+ * tools, tests, and remote runners cheaply.
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * kbot v5 futures — experimental architectural skeleton.
3
+ *
4
+ * Six modules drawn from frontier research published in late April 2026.
5
+ * Each is opt-in, additive, and reversible. None of them changes default
6
+ * agent behavior unless explicitly invoked.
7
+ *
8
+ * Plan: packages/kbot/V5_FUTURES_PLAN.md
9
+ */
10
+ export * as harness from './harness/index.js';
11
+ export * as skillGraph from './skill-graph/index.js';
12
+ export * as latentState from './latent-state/index.js';
13
+ export * as forecast from './forecast/index.js';
14
+ export * as persona from './persona/index.js';
15
+ export * as debate from './debate/index.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * kbot v5 futures — experimental architectural skeleton.
3
+ *
4
+ * Six modules drawn from frontier research published in late April 2026.
5
+ * Each is opt-in, additive, and reversible. None of them changes default
6
+ * agent behavior unless explicitly invoked.
7
+ *
8
+ * Plan: packages/kbot/V5_FUTURES_PLAN.md
9
+ */
10
+ // Harness Evolution Loop — Sylph.AI 2604.21003
11
+ export * as harness from './harness/index.js';
12
+ // Skill Graph — Tencent Hunyuan 2604.25727
13
+ export * as skillGraph from './skill-graph/index.js';
14
+ // Latent State Envelope — Recursive MAS 2604.25917
15
+ export * as latentState from './latent-state/index.js';
16
+ // Forecast — predictions module
17
+ export * as forecast from './forecast/index.js';
18
+ // Persona — privilege scoping (Cequence)
19
+ export * as persona from './persona/index.js';
20
+ // Debate — BARRED-style asymmetric debate runner
21
+ export * as debate from './debate/index.js';
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Latent-state envelope — runtime helpers.
3
+ * Pure functions over `LatentEnvelope`. Node `crypto` (sha256) for hashing.
4
+ */
5
+ import type { LatentEnvelope, ProvenanceEntry } from './types.js';
6
+ /** JSON.stringify with deterministic key order (Object.keys().sort()). */
7
+ export declare function stableStringify(value: unknown): string;
8
+ export interface CreateEnvelopeOpts {
9
+ from: string;
10
+ to: string;
11
+ text?: string;
12
+ structured?: Record<string, unknown>;
13
+ createdAt?: string;
14
+ note?: string;
15
+ }
16
+ /**
17
+ * Build a fresh envelope. Auto-derives `kind`, stamps `createdAt`, seeds
18
+ * provenance with a single step from `opts.from`, and computes `contentHash`.
19
+ */
20
+ export declare function createEnvelope(opts: CreateEnvelopeOpts): LatentEnvelope;
21
+ /** JSON-serialize an envelope with stable key order. */
22
+ export declare function serialize(env: LatentEnvelope): string;
23
+ /** Parse, validate shape, recompute hash, throw if mismatch. */
24
+ export declare function deserialize(s: string): LatentEnvelope;
25
+ /** True iff stored `contentHash` matches a fresh hash of the body. */
26
+ export declare function verifyHash(env: LatentEnvelope): boolean;
27
+ /** Append a provenance step and rehash. Returns a new envelope. */
28
+ export declare function withProvenance(env: LatentEnvelope, entry: Omit<ProvenanceEntry, 'step'> & {
29
+ step?: number;
30
+ }): LatentEnvelope;
31
+ /**
32
+ * Combine two envelopes from the same from/to pair.
33
+ * - text: a + '\n' + b
34
+ * - structured: shallow merge with one-level deep-merge for plain objects
35
+ * - provenance: a then b, renumbered sequentially
36
+ * - createdAt: fresh ISO timestamp; contentHash: recomputed
37
+ */
38
+ export declare function merge(a: LatentEnvelope, b: LatentEnvelope): LatentEnvelope;
39
+ //# sourceMappingURL=envelope.d.ts.map
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Latent-state envelope — runtime helpers.
3
+ * Pure functions over `LatentEnvelope`. Node `crypto` (sha256) for hashing.
4
+ */
5
+ import { createHash } from 'node:crypto';
6
+ /** JSON.stringify with deterministic key order (Object.keys().sort()). */
7
+ export function stableStringify(value) {
8
+ return JSON.stringify(canonicalize(value));
9
+ }
10
+ function canonicalize(value) {
11
+ if (value === null || typeof value !== 'object')
12
+ return value;
13
+ if (Array.isArray(value))
14
+ return value.map(canonicalize);
15
+ const obj = value;
16
+ const out = {};
17
+ for (const key of Object.keys(obj).sort())
18
+ out[key] = canonicalize(obj[key]);
19
+ return out;
20
+ }
21
+ function hashableBody(env) {
22
+ return {
23
+ version: env.version, from: env.from, to: env.to, kind: env.kind,
24
+ text: env.text, structured: env.structured,
25
+ provenance: env.provenance, createdAt: env.createdAt,
26
+ };
27
+ }
28
+ function computeHash(env) {
29
+ return createHash('sha256').update(stableStringify(hashableBody(env))).digest('hex');
30
+ }
31
+ function classify(text, structured) {
32
+ if (text !== undefined && structured !== undefined)
33
+ return 'mixed';
34
+ if (structured !== undefined)
35
+ return 'structured';
36
+ return 'text';
37
+ }
38
+ /**
39
+ * Build a fresh envelope. Auto-derives `kind`, stamps `createdAt`, seeds
40
+ * provenance with a single step from `opts.from`, and computes `contentHash`.
41
+ */
42
+ export function createEnvelope(opts) {
43
+ const kind = classify(opts.text, opts.structured);
44
+ const createdAt = opts.createdAt ?? new Date().toISOString();
45
+ const provenance = [
46
+ {
47
+ step: 1,
48
+ agent: opts.from,
49
+ ts: createdAt,
50
+ ...(opts.note !== undefined ? { note: opts.note } : {}),
51
+ },
52
+ ];
53
+ const body = {
54
+ version: 1,
55
+ from: opts.from,
56
+ to: opts.to,
57
+ kind,
58
+ ...(opts.text !== undefined ? { text: opts.text } : {}),
59
+ ...(opts.structured !== undefined ? { structured: opts.structured } : {}),
60
+ provenance,
61
+ createdAt,
62
+ };
63
+ return { ...body, contentHash: computeHash(body) };
64
+ }
65
+ /** JSON-serialize an envelope with stable key order. */
66
+ export function serialize(env) {
67
+ return stableStringify(env);
68
+ }
69
+ /** Parse, validate shape, recompute hash, throw if mismatch. */
70
+ export function deserialize(s) {
71
+ let raw;
72
+ try {
73
+ raw = JSON.parse(s);
74
+ }
75
+ catch (err) {
76
+ throw new Error(`latent-state: invalid JSON: ${err.message}`);
77
+ }
78
+ const env = assertShape(raw);
79
+ if (!verifyHash(env)) {
80
+ throw new Error('latent-state: contentHash mismatch — envelope tampered');
81
+ }
82
+ return env;
83
+ }
84
+ function assertShape(raw) {
85
+ if (!raw || typeof raw !== 'object')
86
+ throw new Error('latent-state: envelope must be an object');
87
+ const o = raw;
88
+ if (o.version !== 1)
89
+ throw new Error(`latent-state: unsupported version ${String(o.version)}`);
90
+ if (typeof o.from !== 'string' || typeof o.to !== 'string')
91
+ throw new Error('latent-state: from/to must be strings');
92
+ if (o.kind !== 'text' && o.kind !== 'structured' && o.kind !== 'mixed')
93
+ throw new Error(`latent-state: invalid kind ${String(o.kind)}`);
94
+ if (typeof o.createdAt !== 'string' || typeof o.contentHash !== 'string')
95
+ throw new Error('latent-state: createdAt/contentHash must be strings');
96
+ if (!Array.isArray(o.provenance) || o.provenance.length === 0)
97
+ throw new Error('latent-state: provenance must be a non-empty array');
98
+ return o;
99
+ }
100
+ /** True iff stored `contentHash` matches a fresh hash of the body. */
101
+ export function verifyHash(env) {
102
+ const { contentHash, ...body } = env;
103
+ return computeHash(body) === contentHash;
104
+ }
105
+ /** Append a provenance step and rehash. Returns a new envelope. */
106
+ export function withProvenance(env, entry) {
107
+ const nextStep = entry.step ?? Math.max(0, ...env.provenance.map((p) => p.step)) + 1;
108
+ const provenance = [
109
+ ...env.provenance,
110
+ {
111
+ step: nextStep,
112
+ agent: entry.agent,
113
+ ts: entry.ts,
114
+ ...(entry.note !== undefined ? { note: entry.note } : {}),
115
+ },
116
+ ];
117
+ const { contentHash: _drop, ...rest } = { ...env, provenance };
118
+ void _drop;
119
+ return { ...rest, contentHash: computeHash(rest) };
120
+ }
121
+ /**
122
+ * Combine two envelopes from the same from/to pair.
123
+ * - text: a + '\n' + b
124
+ * - structured: shallow merge with one-level deep-merge for plain objects
125
+ * - provenance: a then b, renumbered sequentially
126
+ * - createdAt: fresh ISO timestamp; contentHash: recomputed
127
+ */
128
+ export function merge(a, b) {
129
+ if (a.from !== b.from || a.to !== b.to) {
130
+ throw new Error('latent-state: cannot merge envelopes with different from/to');
131
+ }
132
+ const text = mergeText(a.text, b.text);
133
+ const structured = mergeStructured(a.structured, b.structured);
134
+ const kind = classify(text, structured);
135
+ const provenance = [...a.provenance, ...b.provenance].map((p, i) => ({ ...p, step: i + 1 }));
136
+ const body = {
137
+ version: 1,
138
+ from: a.from,
139
+ to: a.to,
140
+ kind,
141
+ ...(text !== undefined ? { text } : {}),
142
+ ...(structured !== undefined ? { structured } : {}),
143
+ provenance,
144
+ createdAt: new Date().toISOString(),
145
+ };
146
+ return { ...body, contentHash: computeHash(body) };
147
+ }
148
+ function mergeText(a, b) {
149
+ if (a === undefined && b === undefined)
150
+ return undefined;
151
+ if (a === undefined)
152
+ return b;
153
+ if (b === undefined)
154
+ return a;
155
+ return `${a}\n${b}`;
156
+ }
157
+ function mergeStructured(a, b) {
158
+ if (!a && !b)
159
+ return undefined;
160
+ if (!a)
161
+ return { ...b };
162
+ if (!b)
163
+ return { ...a };
164
+ const out = { ...a };
165
+ for (const k of Object.keys(b)) {
166
+ const av = a[k], bv = b[k];
167
+ if (av && bv &&
168
+ typeof av === 'object' && typeof bv === 'object' &&
169
+ !Array.isArray(av) && !Array.isArray(bv)) {
170
+ out[k] = { ...av, ...bv };
171
+ }
172
+ else {
173
+ out[k] = bv;
174
+ }
175
+ }
176
+ return out;
177
+ }
178
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1,5 @@
1
+ /** Latent-state envelope — public surface. */
2
+ export type { AgentTransfer, EnvelopeKind, LatentEnvelope, ProvenanceEntry, } from './types.js';
3
+ export { createEnvelope, deserialize, merge, serialize, stableStringify, verifyHash, withProvenance, } from './envelope.js';
4
+ export type { CreateEnvelopeOpts } from './envelope.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ /** Latent-state envelope — public surface. */
2
+ export { createEnvelope, deserialize, merge, serialize, stableStringify, verifyHash, withProvenance, } from './envelope.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Latent-state envelope — type definitions.
3
+ *
4
+ * Maps onto "Recursive Multi-Agent Systems" (Stanford/UIUC/NVIDIA/MIT,
5
+ * arXiv:2604.25917). Today most inter-agent handoffs are plain text. As
6
+ * models gain native structured-state IO, the harness should already be
7
+ * carrying typed envelopes so the upgrade path is a payload swap, not an
8
+ * interface change.
9
+ *
10
+ * Runtime lives in `envelope.ts`.
11
+ */
12
+ /** One step in the chain of custody. */
13
+ export interface ProvenanceEntry {
14
+ step: number;
15
+ agent: string;
16
+ ts: string;
17
+ note?: string;
18
+ }
19
+ /**
20
+ * Payload discriminator.
21
+ * - 'text' — text only (today's common case)
22
+ * - 'structured' — structured payload only (latent-state native)
23
+ * - 'mixed' — both present
24
+ */
25
+ export type EnvelopeKind = 'text' | 'structured' | 'mixed';
26
+ /**
27
+ * `contentHash` is sha256 over a canonical JSON serialization of the body
28
+ * (every field except contentHash itself). Used by `verifyHash` to detect
29
+ * tampering and by `merge` to derive fresh hashes after combining.
30
+ */
31
+ export interface LatentEnvelope {
32
+ version: 1;
33
+ from: string;
34
+ to: string;
35
+ kind: EnvelopeKind;
36
+ text?: string;
37
+ structured?: Record<string, unknown>;
38
+ provenance: ProvenanceEntry[];
39
+ createdAt: string;
40
+ contentHash: string;
41
+ }
42
+ /** Wraps an envelope for transport. `ackToken` reserved for future ack flows. */
43
+ export interface AgentTransfer {
44
+ envelope: LatentEnvelope;
45
+ ackToken?: string;
46
+ }
47
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Latent-state envelope — type definitions.
3
+ *
4
+ * Maps onto "Recursive Multi-Agent Systems" (Stanford/UIUC/NVIDIA/MIT,
5
+ * arXiv:2604.25917). Today most inter-agent handoffs are plain text. As
6
+ * models gain native structured-state IO, the harness should already be
7
+ * carrying typed envelopes so the upgrade path is a payload swap, not an
8
+ * interface change.
9
+ *
10
+ * Runtime lives in `envelope.ts`.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=types.js.map