@kognai/orchestrator-core 0.2.1 → 0.2.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.
- package/dist/lib/engine-agents.d.ts +8 -1
- package/dist/lib/engine-agents.js +10 -3
- package/dist/lib/sovereign-agent-factory.d.ts +30 -2
- package/dist/lib/sovereign-agent-factory.js +72 -3
- package/dist/lib/v2-decomposition-gate.types.d.ts +80 -0
- package/dist/lib/v2-decomposition-gate.types.js +2 -0
- package/dist/lib/v2-decomposition-gate.types.test.d.ts +0 -0
- package/dist/lib/v2-decomposition-gate.types.test.js +3 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CitizenOwner } from './citizenship';
|
|
1
|
+
import { type CitizenOwner, type CitizenRecord } from './citizenship';
|
|
2
2
|
import type { AgentTask, ReviewResult, CTOProposal, CTOReport } from './orchestrate-engine';
|
|
3
3
|
export declare class SupervisorAgent {
|
|
4
4
|
private systemPrompt;
|
|
@@ -69,6 +69,13 @@ export interface SpawnGateResult {
|
|
|
69
69
|
* Voxight swarm's a Voxight citizen, etc. The running company is carried by
|
|
70
70
|
* the template-injected gate's requester_did, not hardcoded in the engine. */
|
|
71
71
|
owner?: CitizenOwner;
|
|
72
|
+
/** TICKET-334b: the citizen the gate already minted through the template's
|
|
73
|
+
* canonical issuer (Kognai → SAF.spawnCitizen — governance + owner-lineage +
|
|
74
|
+
* ACP-at-birth). When present the engine adopts it verbatim and SKIPS its own
|
|
75
|
+
* bare mintCitizen, so the engine path no longer loses the ACP seeding. When
|
|
76
|
+
* absent (gate-less templates, or a gate that only governs) the engine falls
|
|
77
|
+
* back to minting itself with the resolved owner. */
|
|
78
|
+
citizen?: CitizenRecord;
|
|
72
79
|
}
|
|
73
80
|
export type SpawnGate = (spec: AgentSpawnSpec) => SpawnGateResult;
|
|
74
81
|
export declare class AgentCreator {
|
|
@@ -736,6 +736,7 @@ class AgentCreator {
|
|
|
736
736
|
// anything on disk. Approval/rejection only; the citizenship logic below is
|
|
737
737
|
// unchanged (its extraction is tracked separately as TICKET-226).
|
|
738
738
|
let spawnOwner;
|
|
739
|
+
let gateCitizen;
|
|
739
740
|
if (this.spawnGate) {
|
|
740
741
|
const decision = this.spawnGate(spec);
|
|
741
742
|
if (!decision.approved) {
|
|
@@ -753,6 +754,10 @@ class AgentCreator {
|
|
|
753
754
|
// The gate (SAF) resolves the lineage from its requester_did — this is the
|
|
754
755
|
// running company's context, plumbed in rather than hardcoded here.
|
|
755
756
|
spawnOwner = decision.owner;
|
|
757
|
+
// TICKET-334b: when the gate minted through the canonical issuer
|
|
758
|
+
// (SAF.spawnCitizen — owner-lineage + ACP-at-birth), adopt that citizen
|
|
759
|
+
// and skip the bare mint below (which would skip ACP seeding).
|
|
760
|
+
gateCitizen = decision.citizen;
|
|
756
761
|
}
|
|
757
762
|
const agentDir = `./agents/${spec.name}`;
|
|
758
763
|
(0, fs_1.mkdirSync)(agentDir, { recursive: true });
|
|
@@ -760,9 +765,11 @@ class AgentCreator {
|
|
|
760
765
|
// citizen — not a bare agent. Mint citizenship (citizen_id + roll
|
|
761
766
|
// number + Kōpus avatar + ACP baseline) BEFORE writing the agent
|
|
762
767
|
// files so the citizen record can be referenced in the prompt.
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
|
|
768
|
+
// Prefer the citizen the gate already minted through the canonical issuer
|
|
769
|
+
// (carries ACP-at-birth); else mint here owner-scoped when the gate supplied
|
|
770
|
+
// a lineage (e.g. invoica/voxight), or via the legacy kognai-internal path
|
|
771
|
+
// for gate-less templates.
|
|
772
|
+
const citizen = gateCitizen ?? (0, citizenship_1.mintCitizen)(spec.name, {
|
|
766
773
|
founding_agent: 'ceo',
|
|
767
774
|
proposing_agent: 'cto',
|
|
768
775
|
citizen_type: 'spawned',
|
|
@@ -119,16 +119,44 @@ export declare function deriveOwner(requester_did: string): CitizenOwner;
|
|
|
119
119
|
*
|
|
120
120
|
* Returns the governance decision plus the minted citizen (when approved). The
|
|
121
121
|
* caller still writes citizen.yaml via renderCitizenYaml(citizen). The initial
|
|
122
|
-
* ACP (
|
|
123
|
-
*
|
|
122
|
+
* ACP is seeded here at birth (writeInitialAcp) so the citizen has a routing
|
|
123
|
+
* profile from the moment it exists — this is the property the engine seam lost
|
|
124
|
+
* when it minted via bare mintCitizen (TICKET-334b).
|
|
125
|
+
*
|
|
126
|
+
* `founding_agent` / `proposing_agent` let a caller preserve its own attribution
|
|
127
|
+
* (e.g. the engine's CEO-founds / CTO-proposes lineage). When omitted, the
|
|
128
|
+
* proposing agent defaults to the requester DID.
|
|
124
129
|
*/
|
|
125
130
|
export declare function spawnCitizen(req: SpawnRequest, opts?: {
|
|
126
131
|
agent_name?: string;
|
|
127
132
|
now?: Date;
|
|
133
|
+
founding_agent?: string;
|
|
134
|
+
proposing_agent?: string;
|
|
128
135
|
}): {
|
|
129
136
|
decision: SpawnDecision;
|
|
130
137
|
citizen?: CitizenRecord;
|
|
131
138
|
};
|
|
139
|
+
/**
|
|
140
|
+
* TICKET-334e — backfill an initial ACP for every citizen in the registry that
|
|
141
|
+
* has no entry in acp/trust-scores.json yet. These are pre-SAF citizens that were
|
|
142
|
+
* born scoreless (minted via bare mintCitizen before the canonical issuer seeded
|
|
143
|
+
* ACP at birth — see TICKET-334b). The router PASS-THROUGHs unscored agents, so
|
|
144
|
+
* this gives them a real routing profile instead of an implicit bypass.
|
|
145
|
+
*
|
|
146
|
+
* Classification reuses the SAF path (classifyRequest → initialAcp) keyed on the
|
|
147
|
+
* citizen's owner lineage + agent_name, so a backfilled score is identical to the
|
|
148
|
+
* one the same agent would receive if spawned today. Idempotent: writeInitialAcp
|
|
149
|
+
* skips any agent_name that already has a score, so re-running only fills gaps.
|
|
150
|
+
*/
|
|
151
|
+
export declare function backfillAcpForRegistry(): {
|
|
152
|
+
scanned: number;
|
|
153
|
+
seeded: {
|
|
154
|
+
agent_name: string;
|
|
155
|
+
classification: SpawnClass;
|
|
156
|
+
score: number;
|
|
157
|
+
}[];
|
|
158
|
+
skipped: number;
|
|
159
|
+
};
|
|
132
160
|
/**
|
|
133
161
|
* Emergency bypass — Founder only. Overrides all gates including health and
|
|
134
162
|
* constitutional conditional findings. Logs prominently to KSL.
|
|
@@ -24,6 +24,7 @@ exports.issueSpawnDecision = issueSpawnDecision;
|
|
|
24
24
|
exports.sovereignSpawn = sovereignSpawn;
|
|
25
25
|
exports.deriveOwner = deriveOwner;
|
|
26
26
|
exports.spawnCitizen = spawnCitizen;
|
|
27
|
+
exports.backfillAcpForRegistry = backfillAcpForRegistry;
|
|
27
28
|
exports.founderEmergencySpawn = founderEmergencySpawn;
|
|
28
29
|
exports.getSpawnRegistry = getSpawnRegistry;
|
|
29
30
|
exports.batchSpawn = batchSpawn;
|
|
@@ -328,8 +329,13 @@ function deriveOwner(requester_did) {
|
|
|
328
329
|
*
|
|
329
330
|
* Returns the governance decision plus the minted citizen (when approved). The
|
|
330
331
|
* caller still writes citizen.yaml via renderCitizenYaml(citizen). The initial
|
|
331
|
-
* ACP (
|
|
332
|
-
*
|
|
332
|
+
* ACP is seeded here at birth (writeInitialAcp) so the citizen has a routing
|
|
333
|
+
* profile from the moment it exists — this is the property the engine seam lost
|
|
334
|
+
* when it minted via bare mintCitizen (TICKET-334b).
|
|
335
|
+
*
|
|
336
|
+
* `founding_agent` / `proposing_agent` let a caller preserve its own attribution
|
|
337
|
+
* (e.g. the engine's CEO-founds / CTO-proposes lineage). When omitted, the
|
|
338
|
+
* proposing agent defaults to the requester DID.
|
|
333
339
|
*/
|
|
334
340
|
function spawnCitizen(req, opts = {}) {
|
|
335
341
|
const decision = sovereignSpawn(req);
|
|
@@ -339,7 +345,8 @@ function spawnCitizen(req, opts = {}) {
|
|
|
339
345
|
const citizen = (0, citizenship_1.mintCitizen)(opts.agent_name ?? req.requested_role, {
|
|
340
346
|
owner,
|
|
341
347
|
citizen_type: 'spawned',
|
|
342
|
-
|
|
348
|
+
founding_agent: opts.founding_agent,
|
|
349
|
+
proposing_agent: opts.proposing_agent ?? req.requester_did,
|
|
343
350
|
now: opts.now,
|
|
344
351
|
});
|
|
345
352
|
decision.citizen_did = citizen.agent_did;
|
|
@@ -347,6 +354,68 @@ function spawnCitizen(req, opts = {}) {
|
|
|
347
354
|
writeInitialAcp(citizen.agent_name, decision.initial_acp_score);
|
|
348
355
|
return { decision, citizen };
|
|
349
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* TICKET-334e — derive the constitutional requester DID for an EXISTING citizen
|
|
359
|
+
* from its owner lineage, so classifyRequest() sorts it the same way a fresh
|
|
360
|
+
* spawn from that owner would (external → EXTERNAL sub-floor; everything else by
|
|
361
|
+
* role). Mirrors the DID shapes deriveOwner() reads.
|
|
362
|
+
*/
|
|
363
|
+
function requesterDidForCitizen(c) {
|
|
364
|
+
const id = c.owner_id || c.company || 'kognai';
|
|
365
|
+
switch (c.owner_kind) {
|
|
366
|
+
case 'external': return `did:external:${id}:${c.agent_name}`;
|
|
367
|
+
case 'user': return `did:kognai:citizen:${id}`;
|
|
368
|
+
case 'scs': return `did:kognai:scs:${id}:${c.agent_name}`;
|
|
369
|
+
default: return `did:kognai:${id}`; // company / legacy
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/** Read the agent_name keys already present in acp/trust-scores.json. */
|
|
373
|
+
function readAcpScoreKeys() {
|
|
374
|
+
try {
|
|
375
|
+
const { readFileSync } = require('fs');
|
|
376
|
+
const { join } = require('path');
|
|
377
|
+
const acpPath = join((0, engine_paths_1.resolveEnginePaths)().root, 'acp', 'trust-scores.json');
|
|
378
|
+
const data = JSON.parse(readFileSync(acpPath, 'utf-8'));
|
|
379
|
+
return new Set(Object.keys(data?.scores ?? {}));
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
return new Set();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* TICKET-334e — backfill an initial ACP for every citizen in the registry that
|
|
387
|
+
* has no entry in acp/trust-scores.json yet. These are pre-SAF citizens that were
|
|
388
|
+
* born scoreless (minted via bare mintCitizen before the canonical issuer seeded
|
|
389
|
+
* ACP at birth — see TICKET-334b). The router PASS-THROUGHs unscored agents, so
|
|
390
|
+
* this gives them a real routing profile instead of an implicit bypass.
|
|
391
|
+
*
|
|
392
|
+
* Classification reuses the SAF path (classifyRequest → initialAcp) keyed on the
|
|
393
|
+
* citizen's owner lineage + agent_name, so a backfilled score is identical to the
|
|
394
|
+
* one the same agent would receive if spawned today. Idempotent: writeInitialAcp
|
|
395
|
+
* skips any agent_name that already has a score, so re-running only fills gaps.
|
|
396
|
+
*/
|
|
397
|
+
function backfillAcpForRegistry() {
|
|
398
|
+
const reg = (0, citizenship_1.readRegistry)();
|
|
399
|
+
const alreadyScored = readAcpScoreKeys();
|
|
400
|
+
const seeded = [];
|
|
401
|
+
let skipped = 0;
|
|
402
|
+
for (const c of reg.citizens) {
|
|
403
|
+
if (!c.agent_name || alreadyScored.has(c.agent_name)) {
|
|
404
|
+
skipped++;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const classification = classifyRequest({
|
|
408
|
+
requester_did: requesterDidForCitizen(c),
|
|
409
|
+
requested_role: c.agent_name,
|
|
410
|
+
requested_capabilities: [],
|
|
411
|
+
purpose: `TICKET-334e ACP backfill for pre-SAF citizen ${c.agent_name}`,
|
|
412
|
+
});
|
|
413
|
+
const score = initialAcp(classification);
|
|
414
|
+
writeInitialAcp(c.agent_name, score);
|
|
415
|
+
seeded.push({ agent_name: c.agent_name, classification, score });
|
|
416
|
+
}
|
|
417
|
+
return { scanned: reg.citizens.length, seeded, skipped };
|
|
418
|
+
}
|
|
350
419
|
/**
|
|
351
420
|
* Emergency bypass — Founder only. Overrides all gates including health and
|
|
352
421
|
* constitutional conditional findings. Logs prominently to KSL.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines the task kind for decomposition gate scoring.
|
|
3
|
+
* @category DecompositionGate
|
|
4
|
+
*/
|
|
5
|
+
export type TaskKind = 'MODIFY' | 'CREATE';
|
|
6
|
+
/**
|
|
7
|
+
* Represents an edit point in the task context.
|
|
8
|
+
* @property kind - The type of edit (e.g., 'import', 'function_body')
|
|
9
|
+
* @property location - The file path or symbol where the edit occurs
|
|
10
|
+
* @property description - Optional description of the edit
|
|
11
|
+
* @category DecompositionGate
|
|
12
|
+
*/
|
|
13
|
+
export interface EditPoint {
|
|
14
|
+
kind: 'import' | 'guard' | 'function_body' | 'export';
|
|
15
|
+
location: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Complexity score for a task before decomposition.
|
|
20
|
+
* @property editPointCount - Total number of edit points
|
|
21
|
+
* @property editPoints - Array of edit points
|
|
22
|
+
* @property estimatedLines - Estimated number of lines to modify
|
|
23
|
+
* @property taskKind - The kind of task (MODIFY or CREATE)
|
|
24
|
+
* @category DecompositionGate
|
|
25
|
+
*/
|
|
26
|
+
export interface ComplexityScore {
|
|
27
|
+
editPointCount: number;
|
|
28
|
+
editPoints: EditPoint[];
|
|
29
|
+
estimatedLines: number;
|
|
30
|
+
taskKind: TaskKind;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Decision outcome from the decomposition gate.
|
|
34
|
+
* @property accepted - Whether the task is accepted for decomposition
|
|
35
|
+
* @property reason - Reason for acceptance or rejection
|
|
36
|
+
* @property score - Complexity score of the task
|
|
37
|
+
* @property redecomposeFeedback - Optional feedback for redecomposing the task (if rejected)
|
|
38
|
+
* @category DecompositionGate
|
|
39
|
+
*/
|
|
40
|
+
export interface GateDecision {
|
|
41
|
+
accepted: boolean;
|
|
42
|
+
reason: string;
|
|
43
|
+
score: ComplexityScore;
|
|
44
|
+
redecomposeFeedback?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Mode of the decomposition gate.
|
|
48
|
+
* @property shadow - Logs only (no hard rejection)
|
|
49
|
+
* @property enforce - Hard rejects tasks that exceed complexity limits
|
|
50
|
+
* @category DecompositionGate
|
|
51
|
+
*/
|
|
52
|
+
export type GateMode = 'shadow' | 'enforce';
|
|
53
|
+
/**
|
|
54
|
+
* Configuration for the decomposition gate.
|
|
55
|
+
* @property mode - Operation mode (shadow or enforce)
|
|
56
|
+
* @property maxEditPointsModify - Maximum edit points for modify tasks (default: 1)
|
|
57
|
+
* @property maxLinesCreate - Maximum lines for create tasks (default: 200)
|
|
58
|
+
* @category DecompositionGate
|
|
59
|
+
*/
|
|
60
|
+
export interface GateConfig {
|
|
61
|
+
mode: GateMode;
|
|
62
|
+
maxEditPointsModify: number;
|
|
63
|
+
maxLinesCreate: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Input for the decomposition gate scoring.
|
|
67
|
+
* @property id - Unique identifier for the task
|
|
68
|
+
* @property taskKind - The kind of task (MODIFY or CREATE)
|
|
69
|
+
* @property description - Description of the task
|
|
70
|
+
* @property targetFiles - List of target files for the task
|
|
71
|
+
* @property estimatedLines - Estimated number of lines (optional)
|
|
72
|
+
* @category DecompositionGate
|
|
73
|
+
*/
|
|
74
|
+
export interface TicketInput {
|
|
75
|
+
id: string;
|
|
76
|
+
taskKind: TaskKind;
|
|
77
|
+
description: string;
|
|
78
|
+
targetFiles: string[];
|
|
79
|
+
estimatedLines?: number;
|
|
80
|
+
}
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kognai/orchestrator-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Kognai sovereign orchestrator — core engine (template-agnostic). Shared by all products (Kognai/coding, Voxight/market-intel, Invoica/fin-compliance); each supplies only its template. Replaces per-repo forks of orchestrate-agents-v2 / sprint-runner / lib.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SkinGem",
|