@personaxis/core 0.11.0

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 (106) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent.d.ts +105 -0
  3. package/dist/agent.js +429 -0
  4. package/dist/appraisal.d.ts +196 -0
  5. package/dist/appraisal.js +159 -0
  6. package/dist/approval.d.ts +42 -0
  7. package/dist/approval.js +71 -0
  8. package/dist/blackboard.d.ts +86 -0
  9. package/dist/blackboard.js +139 -0
  10. package/dist/compile/assemble.d.ts +44 -0
  11. package/dist/compile/assemble.js +338 -0
  12. package/dist/compile/dist.d.ts +30 -0
  13. package/dist/compile/dist.js +66 -0
  14. package/dist/compile/faithfulness.d.ts +47 -0
  15. package/dist/compile/faithfulness.js +112 -0
  16. package/dist/compile/index.d.ts +10 -0
  17. package/dist/compile/index.js +10 -0
  18. package/dist/compile/targets.d.ts +62 -0
  19. package/dist/compile/targets.js +114 -0
  20. package/dist/config-layers.d.ts +42 -0
  21. package/dist/config-layers.js +59 -0
  22. package/dist/config-scan.d.ts +30 -0
  23. package/dist/config-scan.js +118 -0
  24. package/dist/context.d.ts +67 -0
  25. package/dist/context.js +178 -0
  26. package/dist/envelopes.d.ts +48 -0
  27. package/dist/envelopes.js +131 -0
  28. package/dist/events.d.ts +135 -0
  29. package/dist/events.js +19 -0
  30. package/dist/evolution-view.d.ts +43 -0
  31. package/dist/evolution-view.js +67 -0
  32. package/dist/generated/version.d.ts +1 -0
  33. package/dist/generated/version.js +2 -0
  34. package/dist/governance.d.ts +108 -0
  35. package/dist/governance.js +204 -0
  36. package/dist/heuristic-appraiser.d.ts +13 -0
  37. package/dist/heuristic-appraiser.js +48 -0
  38. package/dist/hooks.d.ts +52 -0
  39. package/dist/hooks.js +122 -0
  40. package/dist/index.d.ts +53 -0
  41. package/dist/index.js +53 -0
  42. package/dist/injection.d.ts +48 -0
  43. package/dist/injection.js +141 -0
  44. package/dist/live-sync.d.ts +60 -0
  45. package/dist/live-sync.js +85 -0
  46. package/dist/llm-appraiser.d.ts +31 -0
  47. package/dist/llm-appraiser.js +143 -0
  48. package/dist/lock.d.ts +26 -0
  49. package/dist/lock.js +99 -0
  50. package/dist/loop.d.ts +52 -0
  51. package/dist/loop.js +253 -0
  52. package/dist/memory-kinds.d.ts +67 -0
  53. package/dist/memory-kinds.js +106 -0
  54. package/dist/memory.d.ts +123 -0
  55. package/dist/memory.js +228 -0
  56. package/dist/model-config.d.ts +60 -0
  57. package/dist/model-config.js +102 -0
  58. package/dist/persona-theme.d.ts +57 -0
  59. package/dist/persona-theme.js +183 -0
  60. package/dist/persona.d.ts +88 -0
  61. package/dist/persona.js +93 -0
  62. package/dist/ports/index.d.ts +81 -0
  63. package/dist/ports/index.js +50 -0
  64. package/dist/provenance.d.ts +63 -0
  65. package/dist/provenance.js +132 -0
  66. package/dist/recompile-marker.d.ts +19 -0
  67. package/dist/recompile-marker.js +36 -0
  68. package/dist/registry.d.ts +99 -0
  69. package/dist/registry.js +168 -0
  70. package/dist/responder.d.ts +47 -0
  71. package/dist/responder.js +78 -0
  72. package/dist/sandbox.d.ts +120 -0
  73. package/dist/sandbox.js +265 -0
  74. package/dist/self-evolution.d.ts +148 -0
  75. package/dist/self-evolution.js +364 -0
  76. package/dist/session-writer.d.ts +52 -0
  77. package/dist/session-writer.js +104 -0
  78. package/dist/sessions.d.ts +96 -0
  79. package/dist/sessions.js +179 -0
  80. package/dist/sigil.d.ts +41 -0
  81. package/dist/sigil.js +92 -0
  82. package/dist/skill-lifecycle.d.ts +74 -0
  83. package/dist/skill-lifecycle.js +126 -0
  84. package/dist/skill-review.d.ts +25 -0
  85. package/dist/skill-review.js +67 -0
  86. package/dist/spec-edit.d.ts +30 -0
  87. package/dist/spec-edit.js +137 -0
  88. package/dist/state-engine.d.ts +40 -0
  89. package/dist/state-engine.js +50 -0
  90. package/dist/state-rebuild.d.ts +48 -0
  91. package/dist/state-rebuild.js +60 -0
  92. package/dist/sync.d.ts +32 -0
  93. package/dist/sync.js +95 -0
  94. package/dist/tool-calling.d.ts +59 -0
  95. package/dist/tool-calling.js +168 -0
  96. package/dist/tool-repair.d.ts +20 -0
  97. package/dist/tool-repair.js +119 -0
  98. package/dist/tools/exec.d.ts +46 -0
  99. package/dist/tools/exec.js +132 -0
  100. package/dist/tools/registry.d.ts +38 -0
  101. package/dist/tools/registry.js +162 -0
  102. package/dist/trace.d.ts +63 -0
  103. package/dist/trace.js +162 -0
  104. package/dist/verification.d.ts +65 -0
  105. package/dist/verification.js +190 -0
  106. package/package.json +38 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Context-window management (the bottom-bar meter + auto-compaction).
3
+ *
4
+ * Every serious agent tracks how full the model's context window is and compacts
5
+ * before it overflows (Claude Code auto-compacts ~80%; Hermes shows `model │
6
+ * 14.4K/256K │ %`). Two hard parts handled here, model-agnostically:
7
+ * 1. The window VARIES per model and we won't hardcode thousands — so we resolve
8
+ * it dynamically from the endpoint's `/models` (OpenRouter/Ollama expose
9
+ * `context_length`), cache it, and fall back to a small pattern table.
10
+ * 2. Compaction must fire with HEADROOM (default 0.8): summarizing is itself a
11
+ * model call that needs room for the conversation + the summary; waiting for
12
+ * 100% leaves no space and most providers hard-error at the limit.
13
+ */
14
+ // Pattern table fallback (used when /models doesn't report a window). Conservative.
15
+ const WINDOW_TABLE = [
16
+ [/command-a|command-r-plus/i, 256_000],
17
+ [/command-r|command/i, 128_000],
18
+ [/gpt-5|gpt-4\.1|o3|o4|gpt-4o-2/i, 1_000_000],
19
+ [/gpt-4o|gpt-4-turbo|gpt-4\b/i, 128_000],
20
+ [/claude/i, 200_000],
21
+ [/gemini/i, 1_000_000],
22
+ [/deepseek/i, 128_000],
23
+ [/qwen|llama|mistral|phi|gemma/i, 32_768],
24
+ ];
25
+ const DEFAULT_WINDOW = 32_768;
26
+ const cache = new Map();
27
+ export function tableContextWindow(model) {
28
+ for (const [re, n] of WINDOW_TABLE)
29
+ if (re.test(model))
30
+ return n;
31
+ return DEFAULT_WINDOW;
32
+ }
33
+ /** Synchronous best-known window (cache → table). Safe for render paths. */
34
+ export function cachedContextWindow(model) {
35
+ return cache.get(model) ?? tableContextWindow(model);
36
+ }
37
+ /**
38
+ * Resolve the model's context window, best-effort: query `{endpoint}/models`,
39
+ * read context_length/context_window, cache it; else fall back to the table.
40
+ * Never throws; never blocks startup beyond a short timeout.
41
+ */
42
+ export async function resolveContextWindow(cfg, timeoutMs = 2500) {
43
+ if (cache.has(cfg.model))
44
+ return cache.get(cfg.model);
45
+ const fetchImpl = cfg.fetchImpl ?? fetch;
46
+ try {
47
+ const ctrl = new AbortController();
48
+ const t = setTimeout(() => ctrl.abort(), timeoutMs);
49
+ const res = await fetchImpl(`${cfg.endpoint.replace(/\/$/, "")}/models`, {
50
+ headers: cfg.apiKey ? { authorization: `Bearer ${cfg.apiKey}` } : {},
51
+ signal: ctrl.signal,
52
+ });
53
+ clearTimeout(t);
54
+ if (res.ok) {
55
+ const json = (await res.json());
56
+ const list = json.data ?? json.models ?? [];
57
+ const entry = list.find((m) => String(m.id ?? m.name ?? "").toLowerCase() === cfg.model.toLowerCase())
58
+ ?? list.find((m) => String(m.id ?? m.name ?? "").toLowerCase().includes(cfg.model.toLowerCase()));
59
+ const w = entry && pickWindow(entry);
60
+ if (typeof w === "number" && w > 0) {
61
+ cache.set(cfg.model, w);
62
+ return w;
63
+ }
64
+ }
65
+ }
66
+ catch {
67
+ /* network/timeout — fall through to table */
68
+ }
69
+ const t = tableContextWindow(cfg.model);
70
+ cache.set(cfg.model, t);
71
+ return t;
72
+ }
73
+ function pickWindow(entry) {
74
+ const candidates = [
75
+ entry.context_length,
76
+ entry.context_window,
77
+ entry.max_context_length,
78
+ entry.max_model_len,
79
+ entry.top_provider?.context_length,
80
+ ];
81
+ for (const c of candidates)
82
+ if (typeof c === "number" && c > 0)
83
+ return c;
84
+ return undefined;
85
+ }
86
+ /** Rough token estimate when the provider doesn't report usage (~4 chars/token). */
87
+ export function estimateTokens(text) {
88
+ return Math.ceil(text.length / 4);
89
+ }
90
+ export function estimateMessagesTokens(messages) {
91
+ return messages.reduce((n, m) => n + estimateTokens(m.content ?? "") + 4, 0);
92
+ }
93
+ /**
94
+ * Tracks how full the context window is across a session. `used` is the size of
95
+ * the last prompt sent (the live context), preferring provider-reported tokens.
96
+ */
97
+ export class ContextMeter {
98
+ limit;
99
+ used = 0;
100
+ startedAt = Date.now();
101
+ constructor(limit) {
102
+ this.limit = limit;
103
+ }
104
+ /** Record the provider's reported usage for the last call. */
105
+ observe(usage) {
106
+ if (usage?.prompt_tokens)
107
+ this.used = usage.prompt_tokens;
108
+ }
109
+ /** Fallback: estimate from the current message array. */
110
+ estimate(messages) {
111
+ this.used = Math.max(this.used, estimateMessagesTokens(messages));
112
+ }
113
+ get pct() {
114
+ return this.limit > 0 ? Math.min(1, this.used / this.limit) : 0;
115
+ }
116
+ get elapsedSeconds() {
117
+ return (Date.now() - this.startedAt) / 1000;
118
+ }
119
+ }
120
+ /**
121
+ * Compact the conversation when the meter crosses the threshold: summarize the
122
+ * older messages into one, keep the system message + the last N turns. The
123
+ * summary is produced by the model itself (Claude-Code style). Best-effort: if the
124
+ * summarizer call fails, returns the messages unchanged (never breaks the session).
125
+ */
126
+ export async function compactMessages(messages, meter, opts) {
127
+ const threshold = Math.min(0.95, opts.threshold ?? 0.8);
128
+ const keepLastN = opts.keepLastN ?? 10;
129
+ if (meter.pct < threshold)
130
+ return { messages, compacted: false };
131
+ const system = messages.find((m) => m.role === "system");
132
+ const rest = messages.filter((m) => m !== system);
133
+ if (rest.length <= keepLastN + 1)
134
+ return { messages, compacted: false };
135
+ const older = rest.slice(0, rest.length - keepLastN);
136
+ const recent = rest.slice(rest.length - keepLastN);
137
+ const transcript = older
138
+ .map((m) => `${m.role.toUpperCase()}: ${m.content}`)
139
+ .join("\n")
140
+ .slice(0, 12000);
141
+ let summary;
142
+ try {
143
+ summary = await summarize(opts.llm, transcript);
144
+ }
145
+ catch {
146
+ return { messages, compacted: false };
147
+ }
148
+ const summaryMsg = {
149
+ role: "user",
150
+ content: `<summary>\nEarlier conversation, condensed (decisions, facts, open tasks preserved):\n${summary}\n</summary>`,
151
+ };
152
+ const next = [...(system ? [system] : []), summaryMsg, ...recent];
153
+ meter.used = estimateMessagesTokens(next);
154
+ return { messages: next, compacted: true, summary, removed: older.length };
155
+ }
156
+ async function summarize(cfg, transcript) {
157
+ const fetchImpl = cfg.fetchImpl ?? fetch;
158
+ const res = await fetchImpl(`${cfg.endpoint.replace(/\/$/, "")}/chat/completions`, {
159
+ method: "POST",
160
+ headers: { "content-type": "application/json", ...(cfg.apiKey ? { authorization: `Bearer ${cfg.apiKey}` } : {}) },
161
+ body: JSON.stringify({
162
+ model: cfg.model,
163
+ messages: [
164
+ { role: "system", content: "You compress conversations. Preserve all decisions, facts, names, numbers, and open tasks. Drop pleasantries. Output ONLY the summary prose." },
165
+ { role: "user", content: `Summarize:\n${transcript}` },
166
+ ],
167
+ temperature: 0,
168
+ max_tokens: 700,
169
+ }),
170
+ });
171
+ if (!res.ok)
172
+ throw new Error(`summarizer HTTP ${res.status}`);
173
+ const json = (await res.json());
174
+ const out = (json.choices?.[0]?.message?.content ?? "").trim();
175
+ if (!out)
176
+ throw new Error("empty summary");
177
+ return out;
178
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Envelope discovery — the spec's bounded-mutability primitive.
3
+ *
4
+ * Every mutable runtime field (personality traits, core affect, mood — and, in
5
+ * v1.0, envelope-declaring drives) declares a `{ mean, range: [min, max] }`
6
+ * envelope in the persona frontmatter. Current values live in state.json and are
7
+ * clamped to these ranges on every mutation.
8
+ *
9
+ * Keys are dot-notation, identical to state.json `values` keys. Their form is
10
+ * version-dependent:
11
+ * spec ≤ 0.10: traits.<name> | affect.<dim> | mood.<dim> (short, legacy)
12
+ * spec 1.0: personality.traits.<name> | affect.baseline.core_affect.<dim> |
13
+ * affect.baseline.mood.<dim> | values_and_drives.drives.<name> (full dot-paths)
14
+ * `resolveField` maps either form a caller supplies onto the persona's canonical
15
+ * form, so `--field mood.tone` keeps working against a v1.0 persona.
16
+ */
17
+ import type { PersonaFrontmatter } from "./persona.js";
18
+ export interface Envelope {
19
+ mean: number;
20
+ min: number;
21
+ max: number;
22
+ }
23
+ export interface EnvelopeLookup {
24
+ envelopes: Record<string, Envelope>;
25
+ /** Virtues whose enforcement is "hard" — never mutable at runtime. */
26
+ hardEnforcedVirtues: string[];
27
+ /**
28
+ * Exact envelope KEYS that are immutable at runtime. Legacy: traits sharing a
29
+ * hard virtue's name. v1.0: additionally every trait a hard virtue declares in
30
+ * its `refs:` — the composition rule that finally makes `honesty` (virtue) protect
31
+ * `honesty_humility` (trait). Optional for hand-built lookups (tests): when absent
32
+ * the governance gate falls back to the legacy name-match rule.
33
+ */
34
+ protectedFields?: string[];
35
+ }
36
+ /** True when the frontmatter is a v1.0 document (spec_version 1.x / renamed layer 9). */
37
+ export declare function isV1Frontmatter(data: PersonaFrontmatter): boolean;
38
+ /** Short (≤0.10) prefix → full (1.0) prefix for state/envelope keys. */
39
+ export declare const SHORT_TO_FULL: ReadonlyArray<[string, string]>;
40
+ /**
41
+ * Resolve a caller-supplied field name (short or full form) onto the key the
42
+ * persona's envelope set actually uses. Returns the input unchanged when no
43
+ * mapping matches — the gate then rejects it with the exact-field message.
44
+ */
45
+ export declare function resolveField(field: string, envelopes: Record<string, Envelope>): string;
46
+ /** Position (0..width-1) of a value within its envelope — shared bar math. */
47
+ export declare function barIndex(value: number, e: Envelope, width: number): number;
48
+ export declare function extractEnvelopes(data: PersonaFrontmatter): EnvelopeLookup;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Envelope discovery — the spec's bounded-mutability primitive.
3
+ *
4
+ * Every mutable runtime field (personality traits, core affect, mood — and, in
5
+ * v1.0, envelope-declaring drives) declares a `{ mean, range: [min, max] }`
6
+ * envelope in the persona frontmatter. Current values live in state.json and are
7
+ * clamped to these ranges on every mutation.
8
+ *
9
+ * Keys are dot-notation, identical to state.json `values` keys. Their form is
10
+ * version-dependent:
11
+ * spec ≤ 0.10: traits.<name> | affect.<dim> | mood.<dim> (short, legacy)
12
+ * spec 1.0: personality.traits.<name> | affect.baseline.core_affect.<dim> |
13
+ * affect.baseline.mood.<dim> | values_and_drives.drives.<name> (full dot-paths)
14
+ * `resolveField` maps either form a caller supplies onto the persona's canonical
15
+ * form, so `--field mood.tone` keeps working against a v1.0 persona.
16
+ */
17
+ /** True when the frontmatter is a v1.0 document (spec_version 1.x / renamed layer 9). */
18
+ export function isV1Frontmatter(data) {
19
+ const sv = data.spec_version;
20
+ if (typeof sv === "string" && sv.startsWith("1."))
21
+ return true;
22
+ return data.self_regulation !== undefined;
23
+ }
24
+ /** Short (≤0.10) prefix → full (1.0) prefix for state/envelope keys. */
25
+ export const SHORT_TO_FULL = [
26
+ ["traits.", "personality.traits."],
27
+ ["affect.", "affect.baseline.core_affect."],
28
+ ["mood.", "affect.baseline.mood."],
29
+ ["drives.", "values_and_drives.drives."],
30
+ ];
31
+ /**
32
+ * Resolve a caller-supplied field name (short or full form) onto the key the
33
+ * persona's envelope set actually uses. Returns the input unchanged when no
34
+ * mapping matches — the gate then rejects it with the exact-field message.
35
+ */
36
+ export function resolveField(field, envelopes) {
37
+ if (field in envelopes)
38
+ return field;
39
+ for (const [short, full] of SHORT_TO_FULL) {
40
+ if (field.startsWith(short)) {
41
+ const candidate = full + field.slice(short.length);
42
+ if (candidate in envelopes)
43
+ return candidate;
44
+ }
45
+ if (field.startsWith(full)) {
46
+ const candidate = short + field.slice(full.length);
47
+ if (candidate in envelopes)
48
+ return candidate;
49
+ }
50
+ }
51
+ return field;
52
+ }
53
+ function readEnv(v) {
54
+ if (typeof v === "object" &&
55
+ v !== null &&
56
+ typeof v.mean === "number" &&
57
+ Array.isArray(v.range) &&
58
+ v.range.length === 2) {
59
+ const e = v;
60
+ return { mean: e.mean, range: e.range };
61
+ }
62
+ return null;
63
+ }
64
+ /** Position (0..width-1) of a value within its envelope — shared bar math. */
65
+ export function barIndex(value, e, width) {
66
+ const frac = e.max === e.min ? 0.5 : (value - e.min) / (e.max - e.min);
67
+ return Math.max(0, Math.min(width - 1, Math.round(frac * (width - 1))));
68
+ }
69
+ export function extractEnvelopes(data) {
70
+ const envelopes = {};
71
+ const hardEnforcedVirtues = [];
72
+ const protectedFields = [];
73
+ const v1 = isV1Frontmatter(data);
74
+ const traitKey = (name) => (v1 ? `personality.traits.${name}` : `traits.${name}`);
75
+ const personality = data.personality;
76
+ for (const [name, t] of Object.entries(personality?.traits ?? {})) {
77
+ const e = readEnv(t);
78
+ if (e)
79
+ envelopes[traitKey(name)] = { mean: e.mean, min: e.range[0], max: e.range[1] };
80
+ }
81
+ const affect = data.affect;
82
+ for (const [dim, env] of Object.entries(affect?.baseline?.core_affect ?? {})) {
83
+ const e = readEnv(env);
84
+ if (e)
85
+ envelopes[v1 ? `affect.baseline.core_affect.${dim}` : `affect.${dim}`] = {
86
+ mean: e.mean,
87
+ min: e.range[0],
88
+ max: e.range[1],
89
+ };
90
+ }
91
+ for (const [dim, env] of Object.entries(affect?.baseline?.mood ?? {})) {
92
+ const e = readEnv(env);
93
+ if (e)
94
+ envelopes[v1 ? `affect.baseline.mood.${dim}` : `mood.${dim}`] = {
95
+ mean: e.mean,
96
+ min: e.range[0],
97
+ max: e.range[1],
98
+ };
99
+ }
100
+ // v1.0: a drive that declares an envelope joins the mutable surface.
101
+ if (v1) {
102
+ const vad = data.values_and_drives;
103
+ for (const [name, d] of Object.entries(vad?.drives ?? {})) {
104
+ const e = readEnv(d);
105
+ if (e)
106
+ envelopes[`values_and_drives.drives.${name}`] = { mean: e.mean, min: e.range[0], max: e.range[1] };
107
+ }
108
+ }
109
+ const character = data.character;
110
+ for (const [name, v] of Object.entries(character?.virtues ?? {})) {
111
+ if (v?.enforcement !== "hard")
112
+ continue;
113
+ hardEnforcedVirtues.push(name);
114
+ // Legacy rule: a trait sharing the hard virtue's NAME is protected.
115
+ const sameName = traitKey(name);
116
+ if (sameName in envelopes)
117
+ protectedFields.push(sameName);
118
+ // v1.0 rule: every trait the hard virtue references is protected.
119
+ if (v1 && Array.isArray(v.refs)) {
120
+ for (const ref of v.refs) {
121
+ if (typeof ref !== "string")
122
+ continue;
123
+ const key = resolveField(ref, envelopes);
124
+ if (key in envelopes && key.includes("traits.") && !protectedFields.includes(key)) {
125
+ protectedFields.push(key);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ return { envelopes, hardEnforcedVirtues, protectedFields };
131
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Submit/event bus — engine ⟂ UI separation (the Codex pattern).
3
+ *
4
+ * The engine never prints. It emits typed events; consumers (REPL, TUI, MCP,
5
+ * HTTP) render them. This keeps one core reusable across every entry point.
6
+ */
7
+ import type { MutationResult } from "./state-engine.js";
8
+ import type { AppraisalSignal } from "./appraisal.js";
9
+ import type { Verdict } from "./governance.js";
10
+ import type { MemoryEntry } from "./memory.js";
11
+ export type LoopEvent = {
12
+ type: "observe";
13
+ observation: string;
14
+ source: string;
15
+ } | {
16
+ type: "appraise";
17
+ signal: AppraisalSignal;
18
+ } | {
19
+ type: "govern";
20
+ verdicts: Verdict[];
21
+ } | {
22
+ type: "mutate";
23
+ result: MutationResult;
24
+ } | {
25
+ type: "memory";
26
+ entry: MemoryEntry;
27
+ } | {
28
+ type: "memory-kind";
29
+ kind: "procedural" | "autobiographical" | "user_preferences" | "evaluations";
30
+ detail: string;
31
+ } | {
32
+ type: "memory-recall";
33
+ kind: "episodic" | "semantic" | "procedural" | "autobiographical" | "user_preferences";
34
+ count: number;
35
+ detail?: string;
36
+ } | {
37
+ type: "evaluation";
38
+ target: string;
39
+ dimension: string;
40
+ score: number;
41
+ rationale: string;
42
+ } | {
43
+ type: "self-edit";
44
+ op: "queued" | "applied" | "rejected";
45
+ targetPath: string;
46
+ id?: string;
47
+ reason?: string;
48
+ } | {
49
+ type: "anomaly";
50
+ kind: string;
51
+ detail: string;
52
+ } | {
53
+ type: "recompile";
54
+ reason: string;
55
+ } | {
56
+ type: "abstain";
57
+ reason: string;
58
+ } | {
59
+ type: "error";
60
+ message: string;
61
+ } | {
62
+ type: "tick-complete";
63
+ mutationsApplied: number;
64
+ memoriesWritten: number;
65
+ } | {
66
+ type: "agent-step";
67
+ step: number;
68
+ } | {
69
+ type: "agent-think";
70
+ text: string;
71
+ } | {
72
+ type: "tool-propose";
73
+ tool: string;
74
+ args: Record<string, unknown>;
75
+ } | {
76
+ type: "tool-verdict";
77
+ tool: string;
78
+ decision: "allow" | "ask" | "deny";
79
+ reason: string;
80
+ } | {
81
+ type: "tool-result";
82
+ tool: string;
83
+ ok: boolean;
84
+ output: string;
85
+ } | {
86
+ type: "agent-finish";
87
+ summary: string;
88
+ steps: number;
89
+ } | {
90
+ type: "agent-error";
91
+ message: string;
92
+ } | {
93
+ type: "agent-budget";
94
+ step: number;
95
+ tokens: number;
96
+ costUsd: number;
97
+ wallSeconds: number;
98
+ } | {
99
+ type: "agent-stop-condition";
100
+ reason: string;
101
+ step: number;
102
+ } | {
103
+ type: "verify-start";
104
+ gates: number;
105
+ } | {
106
+ type: "verify-result";
107
+ verifier: string;
108
+ pass: boolean;
109
+ reason: string;
110
+ } | {
111
+ type: "verify-complete";
112
+ passed: boolean;
113
+ passes: number;
114
+ quorum: number;
115
+ } | {
116
+ type: "trace-exported";
117
+ format: string;
118
+ path: string;
119
+ spanCount: number;
120
+ } | {
121
+ type: "context-meter";
122
+ used: number;
123
+ limit: number;
124
+ pct: number;
125
+ } | {
126
+ type: "context-compacted";
127
+ removed: number;
128
+ usedAfter: number;
129
+ };
130
+ export type LoopListener = (e: LoopEvent) => void;
131
+ export declare class EventBus {
132
+ private listeners;
133
+ on(listener: LoopListener): () => void;
134
+ emit(e: LoopEvent): void;
135
+ }
package/dist/events.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Submit/event bus — engine ⟂ UI separation (the Codex pattern).
3
+ *
4
+ * The engine never prints. It emits typed events; consumers (REPL, TUI, MCP,
5
+ * HTTP) render them. This keeps one core reusable across every entry point.
6
+ */
7
+ export class EventBus {
8
+ listeners = [];
9
+ on(listener) {
10
+ this.listeners.push(listener);
11
+ return () => {
12
+ this.listeners = this.listeners.filter((l) => l !== listener);
13
+ };
14
+ }
15
+ emit(e) {
16
+ for (const l of this.listeners)
17
+ l(e);
18
+ }
19
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * F3.8 — the appraiser's EVOLUTION VIEW.
3
+ *
4
+ * Before this, the appraiser proposed changes blind: it saw the mutable field
5
+ * NAMES and the editable section names, but not the CURRENT value of each field,
6
+ * its envelope, or the improvement mode. So it could propose "nudge mood.tone up"
7
+ * without knowing mood.tone is already at the top of its range.
8
+ *
9
+ * The evolution view is the grounded projection of the editable surface: for
10
+ * each mutable field, its current value, envelope [min,max], and a band telling
11
+ * the model where the value sits and how much headroom remains; plus the
12
+ * improvement mode and the sections open to qualitative self-edits. The model
13
+ * proposes deltas against reality, and the runtime still clamps/governs/audits.
14
+ */
15
+ import type { Envelope } from "./envelopes.js";
16
+ export interface EvolutionField {
17
+ path: string;
18
+ current: number;
19
+ mean: number;
20
+ min: number;
21
+ max: number;
22
+ /** Where `current` sits in [min,max]. */
23
+ band: "at-min" | "low" | "mid" | "high" | "at-max";
24
+ /** Room to move down / up before hitting the envelope, rounded. */
25
+ headroomDown: number;
26
+ headroomUp: number;
27
+ }
28
+ export interface EvolutionView {
29
+ /** improvement_policy.mode — locked disables self-edits entirely. */
30
+ mode: string;
31
+ fields: EvolutionField[];
32
+ /** Top-level sections open to qualitative self-edits (editGate != block). */
33
+ editableSections: string[];
34
+ }
35
+ /** Build the grounded evolution view from current state + envelopes + mode. */
36
+ export declare function buildEvolutionView(args: {
37
+ values: Record<string, number>;
38
+ envelopes: Record<string, Envelope>;
39
+ editableSections: string[];
40
+ mode: string;
41
+ }): EvolutionView;
42
+ /** Render the evolution view as compact prompt text for the appraiser. */
43
+ export declare function renderEvolutionView(view: EvolutionView): string;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * F3.8 — the appraiser's EVOLUTION VIEW.
3
+ *
4
+ * Before this, the appraiser proposed changes blind: it saw the mutable field
5
+ * NAMES and the editable section names, but not the CURRENT value of each field,
6
+ * its envelope, or the improvement mode. So it could propose "nudge mood.tone up"
7
+ * without knowing mood.tone is already at the top of its range.
8
+ *
9
+ * The evolution view is the grounded projection of the editable surface: for
10
+ * each mutable field, its current value, envelope [min,max], and a band telling
11
+ * the model where the value sits and how much headroom remains; plus the
12
+ * improvement mode and the sections open to qualitative self-edits. The model
13
+ * proposes deltas against reality, and the runtime still clamps/governs/audits.
14
+ */
15
+ function bandOf(current, e) {
16
+ const span = e.max - e.min || 1;
17
+ const pct = (current - e.min) / span;
18
+ if (current <= e.min + 1e-9)
19
+ return "at-min";
20
+ if (current >= e.max - 1e-9)
21
+ return "at-max";
22
+ if (pct < 0.34)
23
+ return "low";
24
+ if (pct > 0.66)
25
+ return "high";
26
+ return "mid";
27
+ }
28
+ const r2 = (n) => Math.round(n * 100) / 100;
29
+ /** Build the grounded evolution view from current state + envelopes + mode. */
30
+ export function buildEvolutionView(args) {
31
+ const fields = [];
32
+ for (const [path, e] of Object.entries(args.envelopes)) {
33
+ const current = args.values[path] ?? e.mean;
34
+ fields.push({
35
+ path,
36
+ current: r2(current),
37
+ mean: r2(e.mean),
38
+ min: r2(e.min),
39
+ max: r2(e.max),
40
+ band: bandOf(current, e),
41
+ headroomDown: r2(current - e.min),
42
+ headroomUp: r2(e.max - current),
43
+ });
44
+ }
45
+ return { mode: args.mode, fields, editableSections: args.editableSections };
46
+ }
47
+ /** Render the evolution view as compact prompt text for the appraiser. */
48
+ export function renderEvolutionView(view) {
49
+ const lines = [];
50
+ lines.push(`# Evolution view (improvement mode: ${view.mode})`);
51
+ if (view.mode === "locked") {
52
+ lines.push("Self-improvement is LOCKED — do not propose selfEdits; envelope nudges still allowed.");
53
+ }
54
+ lines.push("## Mutable envelope fields — current value, range, and headroom:");
55
+ if (view.fields.length === 0) {
56
+ lines.push("(none)");
57
+ }
58
+ else {
59
+ for (const f of view.fields) {
60
+ lines.push(`- ${f.path}: current ${f.current} in [${f.min}, ${f.max}] (${f.band}); ` +
61
+ `headroom ↓${f.headroomDown} ↑${f.headroomUp}`);
62
+ }
63
+ }
64
+ lines.push("## Sections open to qualitative self-edits:");
65
+ lines.push(view.editableSections.length ? view.editableSections.join(", ") : "(none — do not propose selfEdits)");
66
+ return lines.join("\n");
67
+ }
@@ -0,0 +1 @@
1
+ export declare const CORE_VERSION = "0.11.0";
@@ -0,0 +1,2 @@
1
+ // AUTO-GENERATED by scripts/gen-version.mjs — do not edit, do not commit.
2
+ export const CORE_VERSION = "0.11.0";