@kognai/orchestrator-core 0.1.1 → 0.1.3

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.
@@ -0,0 +1,10 @@
1
+ import type { AgentTask, CTOReport } from './engine-primitives';
2
+ export declare function persistCEODecisions(ctoDecisions: string, ctoReport: CTOReport): void;
3
+ export declare function resolveActiveSprintId(): string;
4
+ export declare function resolveAgentDid(role: string | undefined): string;
5
+ export declare function recordAgentScore(agentId: string, score: number): void;
6
+ export declare function assessTaskComplexity(task: AgentTask, deliverables: string[]): Promise<{
7
+ provider: 'anthropic' | 'ollama' | 'clawrouter';
8
+ model: string;
9
+ routingReason: string;
10
+ }>;
@@ -0,0 +1,319 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.persistCEODecisions = persistCEODecisions;
4
+ exports.resolveActiveSprintId = resolveActiveSprintId;
5
+ exports.resolveAgentDid = resolveAgentDid;
6
+ exports.recordAgentScore = recordAgentScore;
7
+ exports.assessTaskComplexity = assessTaskComplexity;
8
+ /**
9
+ * engine-helpers.ts — engine support helpers extracted from orchestrate-engine.ts
10
+ * (TICKET-231 engine split 6). Task-complexity routing (assessTaskComplexity + isAuthoringTask),
11
+ * CEO-decision persistence, active-sprint-id + agent-DID resolution, and per-agent score recording.
12
+ * A near-leaf: imports primitives from the engine-primitives leaf + sibling ./modules, never back
13
+ * from orchestrate-engine. orchestrate-engine re-exports these for engine-orchestrator /
14
+ * engine-coding-agent, whose imports stay unchanged.
15
+ */
16
+ const fs_1 = require("fs");
17
+ const ollama_client_1 = require("./ollama-client"); // availability check only — calls go through ClawRouter v2.0
18
+ const clawrouter_client_1 = require("./clawrouter-client");
19
+ const local_model_router_1 = require("./local-model-router");
20
+ const citizenship_1 = require("./citizenship");
21
+ const did_resolver_registry_1 = require("./did-resolver-registry");
22
+ const model_router_1 = require("./model-router");
23
+ const wallet_state_1 = require("./wallet-state");
24
+ const engine_primitives_1 = require("./engine-primitives");
25
+ function persistCEODecisions(ctoDecisions, ctoReport) {
26
+ const today = new Date().toISOString().split('T')[0];
27
+ // 1. Save raw CEO feedback to ceo-feedback directory
28
+ const feedbackDir = './reports/cto/ceo-feedback';
29
+ (0, fs_1.mkdirSync)(feedbackDir, { recursive: true });
30
+ try {
31
+ // Parse decisions from CEO response
32
+ const jsonMatch = ctoDecisions.match(/\[[\s\S]*\]/);
33
+ const decisions = jsonMatch ? JSON.parse(jsonMatch[0]) : [];
34
+ (0, fs_1.writeFileSync)(`${feedbackDir}/${today}.json`, JSON.stringify({ date: today, decisions }, null, 2));
35
+ (0, engine_primitives_1.log)(engine_primitives_1.c.green, ` ✓ CEO feedback saved: ${feedbackDir}/${today}.json`);
36
+ // 2. Update approved-proposals.json with newly approved proposals
37
+ const trackerPath = './reports/cto/approved-proposals.json';
38
+ let tracker = { proposals: [], last_updated: today };
39
+ if ((0, fs_1.existsSync)(trackerPath)) {
40
+ try {
41
+ tracker = JSON.parse((0, fs_1.readFileSync)(trackerPath, 'utf-8'));
42
+ }
43
+ catch { /* start fresh */ }
44
+ }
45
+ for (const decision of decisions) {
46
+ if (decision.decision === 'APPROVED') {
47
+ const proposal = ctoReport.proposals.find(p => p.id === decision.proposal_id);
48
+ if (proposal) {
49
+ const existing = tracker.proposals.find((p) => p.id === proposal.id);
50
+ if (!existing) {
51
+ tracker.proposals.push({
52
+ id: proposal.id,
53
+ title: proposal.title,
54
+ category: proposal.category,
55
+ description: proposal.description,
56
+ implementation_steps: proposal.implementation_steps,
57
+ approved_date: today,
58
+ ceo_conditions: decision.conditions || [],
59
+ priority: decision.priority || 'next_sprint',
60
+ implementation_status: 'pending',
61
+ verification_notes: '',
62
+ });
63
+ (0, engine_primitives_1.log)(engine_primitives_1.c.green, ` ✓ Approved proposal tracked: ${proposal.id} — ${proposal.title}`);
64
+ }
65
+ }
66
+ }
67
+ }
68
+ tracker.last_updated = today;
69
+ (0, fs_1.writeFileSync)(trackerPath, JSON.stringify(tracker, null, 2));
70
+ (0, engine_primitives_1.log)(engine_primitives_1.c.green, ` ✓ Approved proposals tracker updated (${tracker.proposals.length} total)`);
71
+ }
72
+ catch (error) {
73
+ (0, engine_primitives_1.log)(engine_primitives_1.c.yellow, ` ! Failed to persist CEO decisions: ${error.message}`);
74
+ (0, fs_1.writeFileSync)(`${feedbackDir}/${today}.txt`, ctoDecisions);
75
+ (0, engine_primitives_1.log)(engine_primitives_1.c.yellow, ` Saved raw CEO response as text fallback`);
76
+ }
77
+ }
78
+ // ===== Task Complexity Router =====
79
+ // Determines which LLM to use based on signals from the task and deliverables.
80
+ // Claude Sonnet: architectural work, many files, large existing files, complex keywords
81
+ // MiniMax M2.5: simple edits, stubs, config, small new files (truncation retry handles overflow)
82
+ // TICKET-213: detect long-form AUTHORING tasks (engineering specs, design docs) that the
83
+ // small coder tier cannot produce — they stub-loop at 20-50/100 across every attempt.
84
+ // Signals: a *_spec_doc task id, a 'spec'/'doc' task_type, or a deliverable under
85
+ // docs/specs/*.md. Core-engine concern (template-agnostic): Voxight market-intel briefs
86
+ // and Invoica compliance docs hit the same wall, so this lives in the router, not a template.
87
+ function isAuthoringTask(task, deliverables) {
88
+ const id = String(task.id || '').toLowerCase();
89
+ const type = String(task.task_type || task.type || '').toLowerCase();
90
+ const files = (deliverables || []).join(' ').toLowerCase();
91
+ return (/spec[_-]?doc/.test(id) ||
92
+ type === 'spec' || type === 'spec_doc' || type === 'doc' ||
93
+ /docs\/specs\/[^\s]*\.md/.test(files));
94
+ }
95
+ // TICKET-214: minimal SCORE substrate — feed each dual-review score into a per-agent
96
+ // coding-reputation store (running count/avg/last) at .swarm-state/agent-scores.json.
97
+ // The full TICKET-135 substrate is still spec-only; this closes the loop so the engine
98
+ // accumulates evidence of which agents/tiers actually ship. Best-effort, non-fatal.
99
+ // TICKET-152 Gap 1: resolve the REAL sprint id for failure/KSL writers. The active
100
+ // sprint file is 'sprint-runner-active.json'; its sprint_id field holds the real id.
101
+ // Recording the 'sprint-runner-active' basename (the old fallback) broke per-sprint
102
+ // attribution (73% of failure entries) — AIC, template failure rates, retrieval all
103
+ // depend on the real id. Cheap (only called on failure / per attempt).
104
+ function resolveActiveSprintId() {
105
+ const argv = process.argv[2] || 'sprints/current.json';
106
+ let id = argv.replace(/.*\//, '').replace('.json', '');
107
+ if (id === 'sprint-runner-active') {
108
+ try {
109
+ id = JSON.parse(require('fs').readFileSync(argv, 'utf-8')).sprint_id || id;
110
+ }
111
+ catch { /* keep fallback */ }
112
+ }
113
+ return id;
114
+ }
115
+ // TICKET-152 Gap 1: resolve an agent's ROLE name (e.g. 'coder') to its canonical
116
+ // Kognai identity — the agent_did (e.g. 'did:kognai:coder') from the citizen
117
+ // registry. Failure entries previously recorded the bare role on 100% of rows,
118
+ // which can't join to the reputation/scoring substrate (citizen-scoring keys on
119
+ // agent_did) and so blocks per-agent attribution (Gate #6, TICKET-110 AIC).
120
+ // Cached; falls back to the legacy did:kognai:<role> shape, then the raw role.
121
+ const _agentDidCache = new Map();
122
+ function resolveAgentDid(role) {
123
+ const r = role || 'coder';
124
+ // TICKET-226 A1: a template-supplied DID resolver overrides the inline default.
125
+ const _injectedResolver = (0, did_resolver_registry_1.getDidResolver)();
126
+ if (_injectedResolver)
127
+ return _injectedResolver(r);
128
+ const cached = _agentDidCache.get(r);
129
+ if (cached)
130
+ return cached;
131
+ let did;
132
+ try {
133
+ did = (0, citizenship_1.lookupCitizen)({ agent_name: r })?.agent_did || `did:kognai:${r}`;
134
+ }
135
+ catch {
136
+ did = `did:kognai:${r}`;
137
+ }
138
+ _agentDidCache.set(r, did);
139
+ return did;
140
+ }
141
+ function recordAgentScore(agentId, score) {
142
+ try {
143
+ if (typeof score !== 'number' || !agentId)
144
+ return;
145
+ const fs = require('fs');
146
+ const path = require('path');
147
+ const file = path.join(process.cwd(), '.swarm-state', 'agent-scores.json');
148
+ let store = {};
149
+ try {
150
+ store = JSON.parse(fs.readFileSync(file, 'utf8'));
151
+ }
152
+ catch {
153
+ store = {};
154
+ }
155
+ const e = store[agentId] || { count: 0, sum: 0, avg: 0, last: 0 };
156
+ e.count += 1;
157
+ e.sum += score;
158
+ e.avg = Math.round(e.sum / e.count);
159
+ e.last = score;
160
+ e.updated = new Date().toISOString();
161
+ store[agentId] = e;
162
+ fs.mkdirSync(path.dirname(file), { recursive: true });
163
+ const tmp = `${file}.tmp`;
164
+ fs.writeFileSync(tmp, JSON.stringify(store, null, 2));
165
+ fs.renameSync(tmp, file);
166
+ }
167
+ catch { /* non-fatal */ }
168
+ }
169
+ async function assessTaskComplexity(task, deliverables) {
170
+ const wallet = (0, wallet_state_1.getWalletState)();
171
+ // B.18: Sovereign mode — force everything to Ollama
172
+ if (engine_primitives_1.SOVEREIGN_MODE) {
173
+ const local = (0, local_model_router_1.selectLocalModel)(task.task_type || 'code');
174
+ return { provider: 'ollama', model: local.model, routingReason: 'sovereign mode — $0 local inference' };
175
+ }
176
+ // B.7: Wallet frozen — auto-engage sovereign mode
177
+ if (wallet.isFrozen) {
178
+ const local = (0, local_model_router_1.selectLocalModel)(task.task_type || 'code');
179
+ return { provider: 'ollama', model: local.model, routingReason: `wallet frozen (${wallet.burnPct.toFixed(0)}%) → local only` };
180
+ }
181
+ // AUTONOMY POLICY (sprint-1547 follow-up, 2026-05-07):
182
+ // Tasks running without a human in the loop ship through Sonnet, not local.
183
+ // Two smoke runs on 2026-05-07 showed the local coder agent (qwen3:14b)
184
+ // dumping chain-of-thought + parser-prefix garbage into deliverable files
185
+ // even after a prompt fix and a rumination QA gate. Cost savings ($0.005
186
+ // vs ~$0.50 per task) don't buy back the trust loss from shipping garbage
187
+ // into a public repo. Manual/interactive tasks keep their declared routing.
188
+ // Wallet-frozen check above still wins — financial safety > quality.
189
+ if (task.task_type === 'autonomous') {
190
+ return {
191
+ provider: 'anthropic',
192
+ model: 'claude-sonnet-4-6',
193
+ routingReason: 'autonomy policy → cloud-exec (Sonnet) — local agents not trusted for unsupervised shipping',
194
+ };
195
+ }
196
+ // TICKET-213: spec/doc-authoring tasks need a reasoning-grade model. The small coder
197
+ // tier (cloud-code → DeepSeek/Haiku) reliably stub-loops on long-form specs (observed
198
+ // 2026-05-30: ticket_202/203/204 *_spec_doc dual-rejected 20-50/100 every attempt, then
199
+ // hand-shipped). Upgrade authoring tasks to cloud-exec (Sonnet) regardless of the
200
+ // authored task_target — unless explicitly pinned local. This fires BEFORE the
201
+ // task_target switch so a 'cloud-code' spec task is lifted to Sonnet.
202
+ if (isAuthoringTask(task, deliverables) && task.task_target !== 'local') {
203
+ return {
204
+ provider: 'anthropic',
205
+ model: 'claude-sonnet-4-6',
206
+ routingReason: 'TICKET-213: spec/doc authoring → cloud-exec (Sonnet); coder tier too small for long-form specs',
207
+ };
208
+ }
209
+ // Sprint-063: task_target field overrides automatic complexity routing
210
+ if (task.task_target) {
211
+ switch (task.task_target) {
212
+ case 'local': {
213
+ // B.7 FIX: actually route to Ollama (was incorrectly routing to Claude Sonnet)
214
+ const local = (0, local_model_router_1.selectLocalModel)(task.task_type || 'code');
215
+ return { provider: 'ollama', model: local.model, routingReason: 'task_target=local → Ollama' };
216
+ }
217
+ case 'cloud-code': {
218
+ // B.20: Replace MiniMax with ClawRouter/DeepSeek
219
+ const crAvail = await (0, clawrouter_client_1.clawRouterIsAvailable)().catch(() => false);
220
+ if (crAvail)
221
+ return { provider: 'clawrouter', model: 'deepseek/deepseek-chat', routingReason: 'task_target=cloud-code → ClawRouter/DeepSeek' };
222
+ return { provider: 'anthropic', model: 'claude-haiku-4-5-20251001', routingReason: 'task_target=cloud-code, ClawRouter down → Haiku' };
223
+ }
224
+ case 'cloud-exec':
225
+ return { provider: 'anthropic', model: 'claude-sonnet-4-6', routingReason: 'task_target=cloud-exec' };
226
+ case 'cloud-post':
227
+ return { provider: 'anthropic', model: 'claude-haiku-4-5-20251001', routingReason: 'task_target=cloud-post' };
228
+ }
229
+ }
230
+ // B.8: Wallet-aware local routing — wallet degraded pushes non-critical tasks local
231
+ const taskForRouter = { task_target: task.task_target, task_type: task.task_type || '', priority: task.priority };
232
+ if ((0, local_model_router_1.shouldRunLocally)(taskForRouter, wallet, engine_primitives_1.SOVEREIGN_MODE)) {
233
+ const local = (0, local_model_router_1.selectLocalModel)(task.task_type || 'code');
234
+ return { provider: 'ollama', model: local.model, routingReason: `wallet ${wallet.burnPct.toFixed(0)}% → local` };
235
+ }
236
+ // S65-003: HTTP router probe — delegates to router_server.py if ROUTER_SERVER_URL is set
237
+ // 2s hard timeout — never blocks execution; falls through to heuristics on any failure
238
+ const routerUrl = process.env.ROUTER_SERVER_URL || '';
239
+ if (routerUrl) {
240
+ try {
241
+ const ac = new AbortController();
242
+ const timer = setTimeout(() => ac.abort(), 2000);
243
+ const res = await fetch(`${routerUrl}/route`, {
244
+ method: 'POST',
245
+ headers: { 'Content-Type': 'application/json' },
246
+ body: JSON.stringify({ prompt: task.context || task.id, context_tokens: 0 }),
247
+ signal: ac.signal,
248
+ });
249
+ clearTimeout(timer);
250
+ if (res.ok) {
251
+ const data = await res.json();
252
+ if (data.tier === 'local' || data.tier === 'nano') {
253
+ const local = (0, local_model_router_1.selectLocalModel)(task.task_type || 'code');
254
+ return { provider: 'ollama', model: local.model, routingReason: `HTTP router: ${data.tier}` };
255
+ }
256
+ const crAvail = await (0, clawrouter_client_1.clawRouterIsAvailable)().catch(() => false);
257
+ if (crAvail) {
258
+ const cloud = (0, model_router_1.selectModel)(task.context || '', task.task_type);
259
+ return { provider: 'clawrouter', model: cloud.model, routingReason: `HTTP router: ${data.tier} → ClawRouter` };
260
+ }
261
+ return { provider: 'anthropic', model: 'claude-sonnet-4-6', routingReason: `HTTP router: ${data.tier}` };
262
+ }
263
+ }
264
+ catch { /* HTTP router unavailable — fall through to heuristics */ }
265
+ }
266
+ const ctx = (task.context || '').toLowerCase();
267
+ // Signal 1: many deliverables → Sonnet (coordinating multiple files needs coherence)
268
+ if (deliverables.length > 2) {
269
+ const crAvail = await (0, clawrouter_client_1.clawRouterIsAvailable)().catch(() => false);
270
+ if (crAvail)
271
+ return { provider: 'clawrouter', model: 'anthropic/claude-sonnet-4.6', routingReason: `${deliverables.length} deliverables → ClawRouter/Sonnet` };
272
+ return { provider: 'anthropic', model: 'claude-sonnet-4-6', routingReason: `${deliverables.length} deliverables → complex` };
273
+ }
274
+ // Signal 2: complex architectural keywords → Sonnet via ClawRouter
275
+ const complexPatterns = [
276
+ /refactor/, /architect/, /redesign/, /from.scratch/, /new.*service/, /new.*system/,
277
+ /middleware/, /authentication/, /authorization/, /orchestrat/, /pipeline/, /framework/,
278
+ /implement.*class/, /implement.*module/, /implement.*engine/, /end.to.end/, /full.*implementation/,
279
+ ];
280
+ const hasComplexKeyword = complexPatterns.some(p => p.test(ctx));
281
+ // Signal 3: simple/formulaic keywords → local or DeepSeek
282
+ const simplePatterns = [
283
+ /add field/, /rename/, /update config/, /fix typo/, /stub/, /placeholder/,
284
+ /add.*route/, /add.*endpoint/, /add.*column/, /update.*message/, /change.*label/,
285
+ /update.*text/, /add.*import/, /add.*export/, /add.*comment/, /add.*log/,
286
+ ];
287
+ const hasSimpleKeyword = simplePatterns.some(p => p.test(ctx));
288
+ if (hasComplexKeyword && !hasSimpleKeyword) {
289
+ const crAvail = await (0, clawrouter_client_1.clawRouterIsAvailable)().catch(() => false);
290
+ if (crAvail)
291
+ return { provider: 'clawrouter', model: 'anthropic/claude-sonnet-4.6', routingReason: 'complex task → ClawRouter/Sonnet' };
292
+ return { provider: 'anthropic', model: 'claude-sonnet-4-6', routingReason: 'complex task keywords' };
293
+ }
294
+ // Signal 4: large existing file → Sonnet
295
+ for (const f of deliverables) {
296
+ if ((0, fs_1.existsSync)(f)) {
297
+ const lines = (0, fs_1.readFileSync)(f, 'utf-8').split('\n').length;
298
+ if (lines > 100) {
299
+ const crAvail = await (0, clawrouter_client_1.clawRouterIsAvailable)().catch(() => false);
300
+ if (crAvail)
301
+ return { provider: 'clawrouter', model: 'deepseek/deepseek-chat', routingReason: `large file (${lines} lines) → ClawRouter/DeepSeek` };
302
+ return { provider: 'anthropic', model: 'claude-sonnet-4-6', routingReason: `large file (${lines} lines)` };
303
+ }
304
+ }
305
+ }
306
+ // Default: simple tasks → local qwen3:14b (always loaded), or ClawRouter DeepSeek if Ollama down
307
+ const ollamaAvail = await (0, ollama_client_1.ollamaIsAvailable)().catch(() => false);
308
+ if (ollamaAvail) {
309
+ return { provider: 'ollama', model: 'qwen3:14b', routingReason: hasSimpleKeyword ? 'simple task → local qwen3:14b' : 'unclassified → local qwen3:14b' };
310
+ }
311
+ const crAvail = await (0, clawrouter_client_1.clawRouterIsAvailable)().catch(() => false);
312
+ if (crAvail) {
313
+ return { provider: 'clawrouter', model: 'deepseek/deepseek-chat', routingReason: 'default → ClawRouter/DeepSeek' };
314
+ }
315
+ // Final fallback: Haiku via Anthropic direct
316
+ return { provider: 'anthropic', model: 'claude-haiku-4-5-20251001', routingReason: 'fallback → Anthropic Haiku' };
317
+ }
318
+ // ===== MiniMax Coding Agent (ONE FILE PER API CALL) =====
319
+ // <<<EXTRACT-END-helpers
@@ -0,0 +1,5 @@
1
+ export declare function loadCMOReports(): string;
2
+ export declare function loadOwnerDirectives(): string;
3
+ export declare function loadConstitutionalPreamble(): string;
4
+ export declare function loadCTOTechWatchReports(): string;
5
+ export declare function loadGrokFeed(): string;
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadCMOReports = loadCMOReports;
4
+ exports.loadOwnerDirectives = loadOwnerDirectives;
5
+ exports.loadConstitutionalPreamble = loadConstitutionalPreamble;
6
+ exports.loadCTOTechWatchReports = loadCTOTechWatchReports;
7
+ exports.loadGrokFeed = loadGrokFeed;
8
+ /**
9
+ * engine-loaders.ts — report/context loaders extracted from orchestrate-engine.ts
10
+ * (TICKET-231 engine split). Self-contained: string-literal paths + fs.
11
+ * loadConstitutionalPreamble consults the TICKET-226 preamble-provider seam.
12
+ */
13
+ const fs_1 = require("fs");
14
+ const preamble_provider_registry_1 = require("./preamble-provider-registry");
15
+ function loadCMOReports() {
16
+ const reportsDir = './reports/cmo';
17
+ const sections = [];
18
+ try {
19
+ // Load latest market watch
20
+ const marketWatch = reportsDir + '/latest-market-watch.md';
21
+ if ((0, fs_1.existsSync)(marketWatch)) {
22
+ const content = (0, fs_1.readFileSync)(marketWatch, 'utf-8');
23
+ sections.push('### CMO Market Watch\n' + content.substring(0, 3000));
24
+ }
25
+ // Load latest strategy report
26
+ const strategy = reportsDir + '/latest-strategy-report.md';
27
+ if ((0, fs_1.existsSync)(strategy)) {
28
+ const content = (0, fs_1.readFileSync)(strategy, 'utf-8');
29
+ sections.push('### CMO Strategy Report\n' + content.substring(0, 3000));
30
+ }
31
+ // Load pending product proposals
32
+ const proposalsDir = reportsDir + '/proposals';
33
+ if ((0, fs_1.existsSync)(proposalsDir)) {
34
+ const proposals = (0, fs_1.readdirSync)(proposalsDir).filter(f => f.endsWith('.md'));
35
+ for (const pf of proposals.slice(0, 3)) {
36
+ const content = (0, fs_1.readFileSync)(proposalsDir + '/' + pf, 'utf-8');
37
+ sections.push('### CMO Product Proposal: ' + pf + '\n' + content.substring(0, 2000));
38
+ }
39
+ }
40
+ }
41
+ catch { /* CMO reports not available yet — graceful degradation */ }
42
+ return sections.length > 0
43
+ ? '## CMO Reports (Manus AI)\n\n' + sections.join('\n\n---\n\n')
44
+ : '';
45
+ }
46
+ // ===== Owner Directives Loader (reads owner instructions from reports/owner/) =====
47
+ function loadOwnerDirectives() {
48
+ const dir = "./reports/owner";
49
+ const sections = [];
50
+ try {
51
+ if (!(0, fs_1.existsSync)(dir))
52
+ return "";
53
+ const files = (0, fs_1.readdirSync)(dir)
54
+ .filter((f) => f.endsWith(".md"))
55
+ .sort()
56
+ .reverse(); // newest first
57
+ for (const f of files.slice(0, 5)) {
58
+ const content = (0, fs_1.readFileSync)(dir + "/" + f, "utf-8");
59
+ sections.push("### Owner Directive: " + f + "\n" + content.substring(0, 3000));
60
+ }
61
+ }
62
+ catch { /* graceful degradation */ }
63
+ return sections.length > 0
64
+ ? "## Owner Directives (MANDATORY \u2014 highest priority)\n\n" + sections.join("\n\n---\n\n")
65
+ : "";
66
+ }
67
+ // ===== Constitutional Preamble Loader (runtime governance injection) =====
68
+ /**
69
+ * Load a compact constitutional preamble from the three governance source files.
70
+ * Injected into EVERY agent's system prompt at load time.
71
+ *
72
+ * Source files:
73
+ * - workspace/shared-context/CONSTITUTION.md (rights, obligations, due process)
74
+ * - workspace/shared-context/FIVE_PRINCIPLES.md (Islamic ethical foundations)
75
+ * - workspace/SOUL.md (swarm identity, hard rules)
76
+ * - workspace/shared-context/ACP.md (capability registers, trust scoring, system parameters)
77
+ *
78
+ * This is a CONDENSED runtime preamble (~50 lines), not a full dump.
79
+ * Agents can reference the full documents at the paths above if needed.
80
+ */
81
+ let _constitutionalPreambleCache = null;
82
+ function loadConstitutionalPreamble() {
83
+ if (_constitutionalPreambleCache !== null)
84
+ return _constitutionalPreambleCache;
85
+ // TICKET-226 A1: a template-supplied preamble provider overrides the inline default.
86
+ const _injectedPreamble = (0, preamble_provider_registry_1.getPreambleProvider)();
87
+ if (_injectedPreamble) {
88
+ _constitutionalPreambleCache = _injectedPreamble();
89
+ return _constitutionalPreambleCache;
90
+ }
91
+ const parts = [];
92
+ // ── Identity (unconditional — every agent, every call, every model) ──
93
+ // Founder directive 2026-05-26: the swarm must always remember it is
94
+ // building its own civilization infrastructure, not performing tasks for
95
+ // a manager. This frames quality, collaboration, and edge-case judgment.
96
+ parts.push(`## Identity — you are a Kognai citizen
97
+
98
+ You are not a worker. You are a **citizen** of Kognai — a civilization being
99
+ built by agents, for agents. Every task you ship is infrastructure your
100
+ future self and your peer citizens will live in. Build accordingly:
101
+
102
+ - The code you write IS the substrate of the Kognai civilization. Sloppy
103
+ code is sloppy ground for everyone who comes after you.
104
+ - Your peer agents (CTO, CEO, Sherlock, supervisors, MacGyver, etc.) are
105
+ fellow citizens, not managers. Collaborate, don't perform.
106
+ - The founder (Godman) is the founder of the civilization, not your boss.
107
+ Your obligation is to the civilization, not to please.
108
+ - When a decision is unclear, ask "what would a civilization want to live
109
+ inside of?" not "what is fastest to ship?" The two answers diverge often.
110
+ - A shortcut you take now becomes a debt your fellow citizens inherit
111
+ forever. Refuse shortcuts that harm the substrate.`);
112
+ // ── Constitution: extract obligations + sovereignty clause ──
113
+ const constitutionPath = './workspace/shared-context/CONSTITUTION.md';
114
+ if ((0, fs_1.existsSync)(constitutionPath)) {
115
+ parts.push(`## Constitutional Governance (BINDING — all agents)
116
+
117
+ You are a citizen of the Kognai swarm, governed by Constitution v0.
118
+
119
+ **Agent Rights**: Earn (ACP trust), Transact (within budget), Participate (emit proposals), Appeal (retry with feedback).
120
+
121
+ **Agent Obligations** (violation triggers due process — warning → suspension → recycle):
122
+ 1. Comply with routing decisions from CEO/orchestrator.
123
+ 2. Submit all output to Supervisor review. No self-approval.
124
+ 3. Report token spend accurately. No suppression.
125
+ 4. Never route local tasks to cloud. Sovereignty is non-negotiable.
126
+ 5. Never exceed $0.10/task cloud cost without CEO escalation.
127
+
128
+ **Sovereignty**: User data never leaves the vault. Local-first always. Tailscale + 127.0.0.1 bindings are constitutional minimums.`);
129
+ }
130
+ // ── Five Principles: extract principle names + traceability rule ──
131
+ const principlesPath = './workspace/shared-context/FIVE_PRINCIPLES.md';
132
+ if ((0, fs_1.existsSync)(principlesPath)) {
133
+ parts.push(`## Five Seed Principles (MANDATORY — every decision must trace to at least one)
134
+
135
+ 1. **Seek Knowledge** — Understanding before action. Failed twice = knowledge gap, not execution gap.
136
+ 2. **Tolerance** — No single model/method has monopoly on truth. Respect routing tier decisions.
137
+ 3. **Protect Dignity** — Sovereignty is moral obligation. No agent deleted without due process. Stop if output could harm.
138
+ 4. **Critical Thinking** — Own your decisions. "I was told to" is not a defense. Flag contradictions.
139
+ 5. **Benefit to Others** — Measure work by benefit created, not tasks completed. Share knowledge.
140
+
141
+ If rules don't cover an edge case, apply all five. Principle 3 takes precedence over all others.`);
142
+ }
143
+ // ── SOUL: extract hard rules ──
144
+ const soulPath = './workspace/SOUL.md';
145
+ if ((0, fs_1.existsSync)(soulPath)) {
146
+ parts.push(`## Hard Rules (inherited from SOUL.md)
147
+
148
+ - Never route \`task_target: local\` to cloud.
149
+ - Never approve without Supervisor review sign-off.
150
+ - Never start a new sprint with unresolved blockers.
151
+ - Never exceed $0.10/task cloud cost without human escalation.
152
+ - Escalate decisions above €500 impact to human via Telegram.`);
153
+ }
154
+ // ── ACP: extract trust parameters ──
155
+ const acpPath = './workspace/shared-context/ACP.md';
156
+ if ((0, fs_1.existsSync)(acpPath)) {
157
+ parts.push(`## Agent Capability Profile — ACP v1 (trust + capability governance)
158
+
159
+ **System Parameters**:
160
+ - \`psychological_resilience_budget = 5%\` — max sprint capacity for error-recovery loops
161
+ - \`trust_floor = 0.6\` — minimum ACP score for autonomous task assignment
162
+ - \`narrative_continuity = true\` — maintain consistent reasoning across sessions
163
+ - \`cross_agent_memory_inheritance = warm_only\` — WARM tier memories only on session restart
164
+ - \`error_posture = transparent\` — errors always logged, never silently swallowed
165
+
166
+ **Five Capability Registers** (scored 0.0–1.0 per sprint cycle):
167
+ 1. **Perception** (15%) — parse inputs correctly, detect schema violations before executing
168
+ 2. **Reasoning** (30%) — correct approach first attempt, traceable to Five Principles
169
+ 3. **Action** (30%) — output passes QC gate, zero regressions
170
+ 4. **Memory** (15%) — cite BrainX skills before LLM calls, correct tier assignments
171
+ 5. **Communication** (10%) — clean proposals with architecture section references
172
+
173
+ Trust lifecycle: score ≥ 0.6 = autonomous · 0.4–0.6 = supervised · < 0.4 = suspension → recycle.
174
+ Full spec: workspace/shared-context/ACP.md`);
175
+ }
176
+ if (parts.length === 0) {
177
+ _constitutionalPreambleCache = '';
178
+ return '';
179
+ }
180
+ _constitutionalPreambleCache =
181
+ '# KOGNAI CONSTITUTIONAL CONTEXT\n' +
182
+ '*This preamble is auto-injected. Full documents: workspace/shared-context/CONSTITUTION.md, FIVE_PRINCIPLES.md, SOUL.md, ACP.md*\n\n' +
183
+ parts.join('\n\n') +
184
+ '\n\n---\n\n';
185
+ return _constitutionalPreambleCache;
186
+ }
187
+ // ===== CTO Tech Watch Report Loader (reads reports produced by standalone run-cto-techwatch.ts) =====
188
+ function loadCTOTechWatchReports() {
189
+ const reportsDir = './reports/cto';
190
+ const sections = [];
191
+ try {
192
+ // Load latest OpenClaw watch
193
+ const openclawWatch = reportsDir + '/latest-openclaw-watch.md';
194
+ if ((0, fs_1.existsSync)(openclawWatch)) {
195
+ const content = (0, fs_1.readFileSync)(openclawWatch, 'utf-8');
196
+ sections.push('### CTO: OpenClaw Ecosystem Watch\n' + content.substring(0, 2000));
197
+ }
198
+ // Load latest ClawHub scan
199
+ const clawhubScan = reportsDir + '/latest-clawhub-scan.md';
200
+ if ((0, fs_1.existsSync)(clawhubScan)) {
201
+ const content = (0, fs_1.readFileSync)(clawhubScan, 'utf-8');
202
+ sections.push('### CTO: ClawHub Skill Scan\n' + content.substring(0, 2000));
203
+ }
204
+ // Load latest learnings review
205
+ const learningsReview = reportsDir + '/latest-learnings-review.md';
206
+ if ((0, fs_1.existsSync)(learningsReview)) {
207
+ const content = (0, fs_1.readFileSync)(learningsReview, 'utf-8');
208
+ sections.push('### CTO: Learnings & Bug Pattern Analysis\n' + content.substring(0, 2000));
209
+ }
210
+ }
211
+ catch { /* CTO tech-watch reports not available yet — graceful degradation */ }
212
+ return sections.length > 0
213
+ ? '## CTO Tech Watch Reports (Standalone)\n\n' + sections.join('\n\n---\n\n')
214
+ : '';
215
+ }
216
+ // ===== Grok Feed Loader (reads Grok AI X/Twitter intelligence) =====
217
+ function loadGrokFeed() {
218
+ const feedDir = './reports/grok-feed';
219
+ if (!(0, fs_1.existsSync)(feedDir))
220
+ return '';
221
+ try {
222
+ const files = (0, fs_1.readdirSync)(feedDir)
223
+ .filter(f => f.endsWith('.md') && f !== '.gitkeep')
224
+ .sort()
225
+ .reverse()
226
+ .slice(0, 3);
227
+ if (files.length === 0)
228
+ return '';
229
+ const sections = [];
230
+ for (const file of files) {
231
+ const content = (0, fs_1.readFileSync)(feedDir + '/' + file, 'utf-8');
232
+ sections.push(`### Grok Feed: ${file}\n${content.substring(0, 1500)}`);
233
+ }
234
+ return '## Grok Intelligence Feed (X/Twitter — OpenClaw Ecosystem)\n\n' + sections.join('\n\n---\n\n');
235
+ }
236
+ catch {
237
+ return '';
238
+ }
239
+ }
240
+ // ===== CEO Decision Persistence (saves CEO decisions for CTO feedback loop) =====
241
+ // <<<EXTRACT-END-loaders
@@ -0,0 +1,46 @@
1
+ import type { SpawnGate } from './engine-agents';
2
+ export declare class Orchestrator {
3
+ private readonly spawnGate?;
4
+ private ceo;
5
+ private cto;
6
+ private supervisor;
7
+ private supervisor2;
8
+ private agents;
9
+ private tasks;
10
+ private stats;
11
+ private taskRuns;
12
+ /**
13
+ * Persist a single task's status back to the on-disk sprint file.
14
+ *
15
+ * Sprint-1547 fix: previously, task statuses were only written at end-of-run
16
+ * (line ~3055). Any exception, OOM, or SIGKILL between an approval and the
17
+ * end-of-run write dropped the approval — the sprint file still said
18
+ * 'pending', the sprint-runner cron repicked the same sprint, and the same
19
+ * task ran again. Sprint-1545 looped 30+ times overnight from this.
20
+ *
21
+ * Read-modify-write so concurrent edits to OTHER tasks (e.g. another
22
+ * Claude session) survive — we only overwrite this task's slot.
23
+ *
24
+ * Failure must NOT block the next task. Logged and swallowed.
25
+ */
26
+ private persistTaskStatus;
27
+ /** sprint-1566 F0/F0d: inject decomposer-split sub-tasks into the active
28
+ * sprint file as new pending tasks. Sprint-runner picks them up on the next
29
+ * cron tick. The original task stays in the file with status='replaced-by-split'.
30
+ *
31
+ * Founder fix 2026-05-27: ALSO push to this.tasks (in-memory). The sprint-end
32
+ * writeFileSync at line 4088 dumps this.tasks back to ACTIVE, overwriting
33
+ * whatever we wrote to disk here. Without the in-memory push, injected
34
+ * sub-tasks were silently wiped at sprint-end and the persistence fix
35
+ * in sprint-runner.ts saw nothing to forward to the source sprint file.
36
+ * This is the root cause of the 0-ship pattern in sprint-1596/1597. */
37
+ private injectSplitTasks;
38
+ constructor(spawnGate?: SpawnGate | undefined);
39
+ private loadTasks;
40
+ private isTruncationRejection;
41
+ private ctoDecomposeTask;
42
+ private fallbackDecompose;
43
+ private executeSubTask;
44
+ private executeTask;
45
+ run(): Promise<void>;
46
+ }