@kognai/orchestrator-core 0.1.3 → 0.2.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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/lib/build-triage.d.ts +27 -0
- package/dist/lib/build-triage.js +202 -0
- package/dist/lib/citizenship.d.ts +93 -6
- package/dist/lib/citizenship.js +143 -19
- package/dist/lib/engine-agents.d.ts +7 -0
- package/dist/lib/engine-agents.js +44 -52
- package/dist/lib/engine-orchestrator.d.ts +2 -0
- package/dist/lib/engine-orchestrator.js +210 -134
- package/dist/lib/sovereign-agent-factory.d.ts +34 -0
- package/dist/lib/sovereign-agent-factory.js +103 -5
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export * from './lib/trust-score-updater';
|
|
|
23
23
|
export * from './lib/citizenship';
|
|
24
24
|
export * from './lib/agent-registry';
|
|
25
25
|
export * from './lib/sprint-state';
|
|
26
|
+
export * from './lib/build-triage';
|
|
26
27
|
export * as ksl from './lib/ksl';
|
|
27
28
|
export * from './lib/wallet-state';
|
|
28
29
|
export * from './lib/ceo-wallet';
|
package/dist/index.js
CHANGED
|
@@ -82,6 +82,9 @@ __exportStar(require("./lib/citizenship"), exports);
|
|
|
82
82
|
__exportStar(require("./lib/agent-registry"), exports);
|
|
83
83
|
// TICKET-098 sprint runtime-state split (committed defs vs gitignored status).
|
|
84
84
|
__exportStar(require("./lib/sprint-state"), exports);
|
|
85
|
+
// TICKET-234 build triage seam — classifyBuildPath (ceremony-depth axis) +
|
|
86
|
+
// logBuildTriage. Consumed by the orchestrator run loop AND by kognai-build.
|
|
87
|
+
__exportStar(require("./lib/build-triage"), exports);
|
|
85
88
|
// Phase 3b-3 Wave B: KSL capture cluster (session records + error log + tap).
|
|
86
89
|
// Namespaced ('ksl.tapAttempt', 'ksl.writeRecord', 'ksl.record', …) to keep the
|
|
87
90
|
// generic 'record' export off the package's flat public surface.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type BuildPath = 'regulated' | 'fast';
|
|
2
|
+
export interface BuildTriageResult {
|
|
3
|
+
path: BuildPath;
|
|
4
|
+
reason: string;
|
|
5
|
+
triggers: string[];
|
|
6
|
+
sensitive: boolean;
|
|
7
|
+
sovereign: boolean;
|
|
8
|
+
complexity: 'low' | 'medium' | 'high';
|
|
9
|
+
taskCount: number;
|
|
10
|
+
codeFileCount: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Classify a build into a ceremony-depth path. Pure + synchronous: it only reads
|
|
14
|
+
* the sprint JSON + task list, so it adds negligible cost relative to the
|
|
15
|
+
* ceremony it gates. Default-to-regulated on uncertainty.
|
|
16
|
+
*
|
|
17
|
+
* @param sprint the raw sprint object (may carry regulated/strict/fast_track/sovereign flags)
|
|
18
|
+
* @param tasks the loaded task list (each with deliverables/context/type)
|
|
19
|
+
*/
|
|
20
|
+
export declare function classifyBuildPath(sprint: any, tasks: any[]): BuildTriageResult;
|
|
21
|
+
/**
|
|
22
|
+
* Append the triage decision to logs/routing/triage.jsonl (location-independent
|
|
23
|
+
* via engine-paths). Best-effort: never throws into the run loop. This is the
|
|
24
|
+
* audit trail required by the ticket — evidence that triage routes correctly and
|
|
25
|
+
* the means to audit any fast-tracked build later.
|
|
26
|
+
*/
|
|
27
|
+
export declare function logBuildTriage(sprintId: string, result: BuildTriageResult): void;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.classifyBuildPath = classifyBuildPath;
|
|
4
|
+
exports.logBuildTriage = logBuildTriage;
|
|
5
|
+
/**
|
|
6
|
+
* build-triage.ts — TICKET-234: pre-orchestrator build triage (ceremony-depth axis).
|
|
7
|
+
*
|
|
8
|
+
* The orchestrator historically ran the SAME full ceremony for every build
|
|
9
|
+
* regardless of complexity or sensitivity (CEO assessment → execution →
|
|
10
|
+
* dual-supervisor review → CTO data analysis → CEO proposal cycle → post-sprint
|
|
11
|
+
* + agent minting). A 3-line `addTwo` cost 171s + the entire ceremony.
|
|
12
|
+
*
|
|
13
|
+
* This module adds a cheap classifier that runs BEFORE the orchestrator hands
|
|
14
|
+
* off to the swarm and routes the build to one of two execution paths:
|
|
15
|
+
*
|
|
16
|
+
* - REGULATED (full ceremony) — CEO · CTO governance gate · dual-supervisor
|
|
17
|
+
* review · reconciliation · the full CTO/CEO proposal cycle. Templates apply
|
|
18
|
+
* on this path only. For sensitive / sovereign / complex builds.
|
|
19
|
+
*
|
|
20
|
+
* - FAST — coder → single-supervisor review → the minimum safety/security/
|
|
21
|
+
* quality gate (QA gate: typecheck/compile/no-secrets, always on). No
|
|
22
|
+
* dual-supervisor, no CTO/CEO post-sprint ceremony, no templates. For
|
|
23
|
+
* simple, low-stakes, single-file, non-sensitive builds.
|
|
24
|
+
*
|
|
25
|
+
* It is the CEREMONY-DEPTH axis. It composes WITH `assessTaskComplexity`
|
|
26
|
+
* (engine-helpers), which scores the MODEL-ROUTING axis — they are orthogonal:
|
|
27
|
+
* a fast-tracked build can still route to a cloud model, and a regulated build
|
|
28
|
+
* can still run local. This stays a PURE HEURISTIC (no LLM call) so the triage
|
|
29
|
+
* never reintroduces the overhead it removes.
|
|
30
|
+
*
|
|
31
|
+
* Safety invariant: DEFAULT TO REGULATED on any uncertainty. `fast` is returned
|
|
32
|
+
* only when the build is provably simple AND non-sensitive AND non-sovereign.
|
|
33
|
+
* A `fast_track` opt-in never lowers the safety floor — sensitivity/complexity
|
|
34
|
+
* still force REGULATED.
|
|
35
|
+
*/
|
|
36
|
+
const fs_1 = require("fs");
|
|
37
|
+
const path_1 = require("path");
|
|
38
|
+
const engine_paths_1 = require("./engine-paths");
|
|
39
|
+
// Sensitivity keywords — PHI/PII, payments/financial, auth/secrets/credentials,
|
|
40
|
+
// crypto/on-chain/contracts, destructive or privileged operations. A match in a
|
|
41
|
+
// deliverable path, task context, or sprint goal forces REGULATED.
|
|
42
|
+
const SENSITIVE_KEYWORDS = [
|
|
43
|
+
// auth / secrets / credentials
|
|
44
|
+
'auth', 'secret', 'credential', 'password', 'passwd', 'api[_-]?key', 'apikey',
|
|
45
|
+
'access[_-]?token', 'private[_-]?key', 'privatekey', 'seed[_-]?phrase', 'mnemonic',
|
|
46
|
+
'oauth', 'jwt', 'session[_-]?token', 'vault',
|
|
47
|
+
// payments / financial
|
|
48
|
+
'payment', 'billing', 'invoice', 'payout', 'stripe', 'paypal', 'charge', 'refund',
|
|
49
|
+
'kyc', 'ledger', 'wallet', 'pricing',
|
|
50
|
+
// crypto / on-chain / contracts
|
|
51
|
+
'x402', 'crypto', 'on[_-]?chain', 'onchain', 'blockchain', 'smart[_-]?contract',
|
|
52
|
+
'erc20', 'erc-20', 'usdc', '\\$kog', 'viem', 'web3',
|
|
53
|
+
// PHI / PII / regulated data
|
|
54
|
+
'\\bphi\\b', '\\bpii\\b', 'gdpr', 'hipaa', 'personal[_-]?data', 'health[_-]?record',
|
|
55
|
+
// schema / data migrations (high-blast-radius, hard to roll back)
|
|
56
|
+
'migration', 'prisma/migrations', 'drop[_-]?table', 'alter[_-]?table',
|
|
57
|
+
];
|
|
58
|
+
// Destructive / privileged operations — context-level matches force REGULATED.
|
|
59
|
+
const DESTRUCTIVE_KEYWORDS = [
|
|
60
|
+
'rm\\s+-rf', 'drop\\s+table', 'delete\\s+from', 'truncate\\s+table',
|
|
61
|
+
'force[_-]?push', '--force', 'chmod\\s+777', 'sudo\\b', 'kill\\s+-9',
|
|
62
|
+
];
|
|
63
|
+
// Sensitive directories — a deliverable under one of these forces REGULATED.
|
|
64
|
+
const SENSITIVE_DIRS = [
|
|
65
|
+
'banks/', 'wallet', 'ceo-wallet', 'gates/', 'policy/', 'contracts/', 'omel/',
|
|
66
|
+
'x402-base/', 'prisma/', 'credential', '.env', 'auth/', 'secrets/',
|
|
67
|
+
];
|
|
68
|
+
// Architectural / cross-cutting keywords — a match means the change is not a
|
|
69
|
+
// simple single-concern build → REGULATED.
|
|
70
|
+
const ARCHITECTURAL_KEYWORDS = [
|
|
71
|
+
'architect', 'refactor', 'migrate', 'cross[_-]?cutting', 'system[_-]?wide',
|
|
72
|
+
'redesign', 'database', 'schema', 'infrastructure', 'orchestrat', 'pipeline',
|
|
73
|
+
'framework', 'breaking[_-]?change', 'rewrite',
|
|
74
|
+
];
|
|
75
|
+
function compile(words) {
|
|
76
|
+
return new RegExp(`(${words.join('|')})`, 'i');
|
|
77
|
+
}
|
|
78
|
+
const SENSITIVE_RE = compile(SENSITIVE_KEYWORDS);
|
|
79
|
+
const DESTRUCTIVE_RE = compile(DESTRUCTIVE_KEYWORDS);
|
|
80
|
+
const ARCH_RE = compile(ARCHITECTURAL_KEYWORDS);
|
|
81
|
+
function asBool(v) {
|
|
82
|
+
return v === true || v === 'true' || v === 1 || v === '1';
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Classify a build into a ceremony-depth path. Pure + synchronous: it only reads
|
|
86
|
+
* the sprint JSON + task list, so it adds negligible cost relative to the
|
|
87
|
+
* ceremony it gates. Default-to-regulated on uncertainty.
|
|
88
|
+
*
|
|
89
|
+
* @param sprint the raw sprint object (may carry regulated/strict/fast_track/sovereign flags)
|
|
90
|
+
* @param tasks the loaded task list (each with deliverables/context/type)
|
|
91
|
+
*/
|
|
92
|
+
function classifyBuildPath(sprint, tasks) {
|
|
93
|
+
const triggers = [];
|
|
94
|
+
const taskList = Array.isArray(tasks) ? tasks : [];
|
|
95
|
+
const taskCount = taskList.length;
|
|
96
|
+
// Gather all the text we scan for sensitivity/architecture signals.
|
|
97
|
+
const deliverablePaths = [];
|
|
98
|
+
const codeFiles = [];
|
|
99
|
+
for (const t of taskList) {
|
|
100
|
+
const d = (t && t.deliverables) || {};
|
|
101
|
+
const code = Array.isArray(d.code) ? d.code : [];
|
|
102
|
+
const tests = Array.isArray(d.tests) ? d.tests : [];
|
|
103
|
+
const docs = Array.isArray(d.docs) ? d.docs : [];
|
|
104
|
+
codeFiles.push(...code);
|
|
105
|
+
deliverablePaths.push(...code, ...tests, ...docs);
|
|
106
|
+
}
|
|
107
|
+
const codeFileCount = new Set(codeFiles).size;
|
|
108
|
+
const contextText = taskList
|
|
109
|
+
.map((t) => `${(t && (t.context || t.title || t.id)) || ''} ${(t && (t.type || t.task_type)) || ''}`)
|
|
110
|
+
.join(' ');
|
|
111
|
+
const goalText = `${sprint?.name || sprint?.title || ''} ${sprint?.goal || sprint?.description || ''}`;
|
|
112
|
+
const pathText = deliverablePaths.join(' ');
|
|
113
|
+
const haystack = `${goalText} ${contextText} ${pathText}`;
|
|
114
|
+
// ── Explicit flags ────────────────────────────────────────────────────────
|
|
115
|
+
const forcedRegulated = asBool(sprint?.regulated) || asBool(sprint?.strict) || asBool(process.env.KOGNAI_FORCE_REGULATED);
|
|
116
|
+
const requestedFast = asBool(sprint?.fast_track) || asBool(sprint?.fast);
|
|
117
|
+
if (forcedRegulated)
|
|
118
|
+
triggers.push('explicit:regulated');
|
|
119
|
+
// ── Sensitivity ───────────────────────────────────────────────────────────
|
|
120
|
+
let sensitive = false;
|
|
121
|
+
if (SENSITIVE_RE.test(haystack)) {
|
|
122
|
+
sensitive = true;
|
|
123
|
+
triggers.push(`sensitive:keyword(${(haystack.match(SENSITIVE_RE) || [])[1]})`);
|
|
124
|
+
}
|
|
125
|
+
if (DESTRUCTIVE_RE.test(haystack)) {
|
|
126
|
+
sensitive = true;
|
|
127
|
+
triggers.push(`sensitive:destructive(${(haystack.match(DESTRUCTIVE_RE) || [])[1]})`);
|
|
128
|
+
}
|
|
129
|
+
const dirHit = SENSITIVE_DIRS.find((dir) => pathText.toLowerCase().includes(dir.toLowerCase()));
|
|
130
|
+
if (dirHit) {
|
|
131
|
+
sensitive = true;
|
|
132
|
+
triggers.push(`sensitive:path(${dirHit})`);
|
|
133
|
+
}
|
|
134
|
+
// ── Sovereign / regulated domain ──────────────────────────────────────────
|
|
135
|
+
const sovereign = asBool(sprint?.sovereign) || asBool(sprint?.regulated_domain);
|
|
136
|
+
if (sovereign)
|
|
137
|
+
triggers.push('sovereign');
|
|
138
|
+
// ── Complexity (ceremony-depth, NOT model routing) ────────────────────────
|
|
139
|
+
const estimated = String(sprint?.estimated_complexity || '').toLowerCase();
|
|
140
|
+
const archHit = ARCH_RE.test(haystack);
|
|
141
|
+
if (archHit)
|
|
142
|
+
triggers.push(`complex:architectural(${(haystack.match(ARCH_RE) || [])[1]})`);
|
|
143
|
+
if (taskCount > 1)
|
|
144
|
+
triggers.push(`complex:multi-task(${taskCount})`);
|
|
145
|
+
if (codeFileCount > 1)
|
|
146
|
+
triggers.push(`complex:multi-file(${codeFileCount})`);
|
|
147
|
+
if (estimated === 'high')
|
|
148
|
+
triggers.push('complex:estimated-high');
|
|
149
|
+
let complexity = 'low';
|
|
150
|
+
if (archHit || taskCount > 2 || codeFileCount > 2 || estimated === 'high')
|
|
151
|
+
complexity = 'high';
|
|
152
|
+
else if (taskCount > 1 || codeFileCount > 1 || estimated === 'medium')
|
|
153
|
+
complexity = 'medium';
|
|
154
|
+
// ── Decision (default-to-regulated) ───────────────────────────────────────
|
|
155
|
+
// A build is FAST-eligible only if it is provably simple AND nothing sensitive
|
|
156
|
+
// / sovereign / forced fired. Anything ambiguous → REGULATED.
|
|
157
|
+
const isSimple = taskCount === 1 && codeFileCount <= 1 && !archHit && estimated !== 'high';
|
|
158
|
+
const blockFast = forcedRegulated || sensitive || sovereign || !isSimple;
|
|
159
|
+
let path;
|
|
160
|
+
let reason;
|
|
161
|
+
if (blockFast) {
|
|
162
|
+
path = 'regulated';
|
|
163
|
+
reason =
|
|
164
|
+
forcedRegulated ? 'explicit --regulated/--strict flag'
|
|
165
|
+
: sensitive ? `sensitive build (${triggers.filter((t) => t.startsWith('sensitive')).join(', ')})`
|
|
166
|
+
: sovereign ? 'sovereign / regulated-domain build'
|
|
167
|
+
: `complexity above fast-track threshold (${triggers.filter((t) => t.startsWith('complex')).join(', ') || 'multi-concern'})`;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
path = 'fast';
|
|
171
|
+
reason = requestedFast
|
|
172
|
+
? 'fast-track opt-in; simple non-sensitive single-file build'
|
|
173
|
+
: 'simple non-sensitive single-file build → fast track';
|
|
174
|
+
}
|
|
175
|
+
return { path, reason, triggers, sensitive, sovereign, complexity, taskCount, codeFileCount };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Append the triage decision to logs/routing/triage.jsonl (location-independent
|
|
179
|
+
* via engine-paths). Best-effort: never throws into the run loop. This is the
|
|
180
|
+
* audit trail required by the ticket — evidence that triage routes correctly and
|
|
181
|
+
* the means to audit any fast-tracked build later.
|
|
182
|
+
*/
|
|
183
|
+
function logBuildTriage(sprintId, result) {
|
|
184
|
+
try {
|
|
185
|
+
const file = (0, path_1.join)((0, engine_paths_1.resolveEnginePaths)().logs, 'routing', 'triage.jsonl');
|
|
186
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(file), { recursive: true });
|
|
187
|
+
const row = {
|
|
188
|
+
sprint_id: sprintId,
|
|
189
|
+
path: result.path,
|
|
190
|
+
reason: result.reason,
|
|
191
|
+
triggers: result.triggers,
|
|
192
|
+
sensitive: result.sensitive,
|
|
193
|
+
sovereign: result.sovereign,
|
|
194
|
+
complexity: result.complexity,
|
|
195
|
+
task_count: result.taskCount,
|
|
196
|
+
code_file_count: result.codeFileCount,
|
|
197
|
+
logged_at: new Date().toISOString(),
|
|
198
|
+
};
|
|
199
|
+
(0, fs_1.appendFileSync)(file, JSON.stringify(row) + '\n');
|
|
200
|
+
}
|
|
201
|
+
catch { /* non-fatal — audit log must never break a run */ }
|
|
202
|
+
}
|
|
@@ -27,6 +27,79 @@ export interface KopusConfig {
|
|
|
27
27
|
* prefix and its own rollNumber sequence inside the shared registry.
|
|
28
28
|
* Add new companies here as they come online (Achiri, SCS-001, DRI, ...). */
|
|
29
29
|
export declare const COMPANY_PREFIXES: Record<string, string>;
|
|
30
|
+
/**
|
|
31
|
+
* Who a spawned citizen *belongs to* — its lineage. SAF derives this from the
|
|
32
|
+
* spawn requester's DID so a new citizen inherits the requester's identity:
|
|
33
|
+
* - `company` internal product (kognai/voxight/invoica/kreativ/achiri/…). A
|
|
34
|
+
* product CEO's spawn belongs to that company → `did:kognai:{company}:{agent}`.
|
|
35
|
+
* - `external` an outside org (through the AMD-23 Cerberus airlock) →
|
|
36
|
+
* `did:external:{org}:{agent}`.
|
|
37
|
+
* - `user` a human's wallet in the citizen journey. **Exactly ONE agent per
|
|
38
|
+
* user per wallet** → `did:kognai:citizen:{wallet}`.
|
|
39
|
+
* - `scs` a Self-Committed Swarm a user's citizen forms. Sub-agents the
|
|
40
|
+
* citizen spawns inherit the swarm's id → `did:kognai:scs:{scsId}:{agent}`.
|
|
41
|
+
*/
|
|
42
|
+
export type CitizenOwnerKind = 'company' | 'external' | 'user' | 'scs';
|
|
43
|
+
export interface CitizenOwner {
|
|
44
|
+
kind: CitizenOwnerKind;
|
|
45
|
+
/** company name | external org id | user wallet | scs id. */
|
|
46
|
+
id: string;
|
|
47
|
+
}
|
|
48
|
+
/** The Default Instrumentation Posture (TICKET-150 / SOP-GEN-014) carried per
|
|
49
|
+
* citizen: KSL monitoring + failure-log contribution + skill-bank participation. */
|
|
50
|
+
export interface CitizenInstrumentation {
|
|
51
|
+
ksl: boolean;
|
|
52
|
+
failure_log: boolean;
|
|
53
|
+
skill_bank: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Posture by owner kind. **Internal** citizens (kognai + product companies) are
|
|
57
|
+
* born fully instrumented and bound to all constitutional duties — ON, mandatory.
|
|
58
|
+
* **External** orgs/swarms and **user** citizens (+ the SCS they form) get the
|
|
59
|
+
* same machinery baseline-available but OFF; they opt in (see citizenBenefits)
|
|
60
|
+
* for fee discounts and, for users, the improvement loop.
|
|
61
|
+
*/
|
|
62
|
+
export declare function defaultInstrumentation(owner_kind?: CitizenOwnerKind): CitizenInstrumentation;
|
|
63
|
+
/** Kognai-fee discount (%) granted per shared channel an opt-in citizen enables.
|
|
64
|
+
* Tunable Founder policy. */
|
|
65
|
+
export declare const SHARE_DISCOUNT_PCT: {
|
|
66
|
+
failure_log: number;
|
|
67
|
+
skill_bank: number;
|
|
68
|
+
};
|
|
69
|
+
export interface CitizenBenefits {
|
|
70
|
+
/** % off Kognai fees for opting into sharing (external + user + scs). */
|
|
71
|
+
fee_discount_pct: number;
|
|
72
|
+
/** Users (+ their SCS) who share skills may use the skill bank for free. */
|
|
73
|
+
skill_bank_free_use: boolean;
|
|
74
|
+
/** Users (+ their SCS) who share failures get Plumber review of their work,
|
|
75
|
+
* driven by failure-library findings. */
|
|
76
|
+
plumber_review: boolean;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the economic + improvement-loop benefits a citizen unlocks from its
|
|
80
|
+
* current instrumentation. Internal citizens are first-party (mandatory posture,
|
|
81
|
+
* no discount concept). External/user/scs earn a fee discount per shared channel;
|
|
82
|
+
* users (+ their SCS) additionally unlock free skill-bank use + Plumber review.
|
|
83
|
+
*/
|
|
84
|
+
export declare function citizenBenefits(c: Pick<CitizenRecord, 'owner_kind' | 'instrumentation'>): CitizenBenefits;
|
|
85
|
+
/** Apply a citizen's earned discount to a Kognai fee (atomic units). */
|
|
86
|
+
export declare function applyCitizenDiscount(feeAtomic: number, c: Pick<CitizenRecord, 'owner_kind' | 'instrumentation'>): number;
|
|
87
|
+
/** Skill-bank gate: may this citizen draw from the skill bank for free? */
|
|
88
|
+
export declare function canUseSkillBankFree(c: Pick<CitizenRecord, 'owner_kind' | 'instrumentation'>): boolean;
|
|
89
|
+
/** Plumber gate: is this citizen's work eligible for failure-log-driven Plumber review? */
|
|
90
|
+
export declare function eligibleForPlumberReview(c: Pick<CitizenRecord, 'owner_kind' | 'instrumentation'>): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Resolve an agent's canonical *instance* identity (DID + citizen_id) from the
|
|
93
|
+
* registry — the key failure-logging / KSL / SCORE must attribute to, instead of
|
|
94
|
+
* the role string (the TICKET-152 `agent_id="coder"` corruption). Returns null
|
|
95
|
+
* when the agent has no citizen record.
|
|
96
|
+
*/
|
|
97
|
+
export declare function identityFor(agent_name: string, company?: string): {
|
|
98
|
+
agent_did: string;
|
|
99
|
+
citizen_id: string;
|
|
100
|
+
owner_kind?: CitizenOwnerKind;
|
|
101
|
+
owner_id?: string;
|
|
102
|
+
} | null;
|
|
30
103
|
export interface CitizenRecord {
|
|
31
104
|
/** Stable identifier. For company-scoped mints: `{PREFIX}-{rollNumber padded 4}`
|
|
32
105
|
* (e.g. `VXG-0042`). For legacy/Kognai-internal mints (no `company`):
|
|
@@ -39,12 +112,24 @@ export interface CitizenRecord {
|
|
|
39
112
|
/** Agent slug (matches agents/<name>/ dir within the owning company). */
|
|
40
113
|
agent_name: string;
|
|
41
114
|
/** Owning company. Defaults to 'kognai' for legacy records that predate the
|
|
42
|
-
* multi-company extension. Idempotency key is `(agent_name, company)`.
|
|
115
|
+
* multi-company extension. Idempotency key is `(agent_name, company)`.
|
|
116
|
+
* Set only when owner_kind === 'company' (back-compat alias for owner_id). */
|
|
43
117
|
company?: string;
|
|
118
|
+
/** Lineage owner kind — who this citizen belongs to (see CitizenOwner). */
|
|
119
|
+
owner_kind?: CitizenOwnerKind;
|
|
120
|
+
/** Lineage owner id — company name | external org | user wallet | scs id. */
|
|
121
|
+
owner_id?: string;
|
|
122
|
+
/** Default Instrumentation Posture (KSL / failure-log / skill-bank). Internal
|
|
123
|
+
* citizens = all ON (mandatory); external/user/scs = baseline OFF, opt-in. */
|
|
124
|
+
instrumentation?: CitizenInstrumentation;
|
|
44
125
|
/** DID for SCORE / ERC-8004 reputation tracking. Company-scoped form:
|
|
45
126
|
* `did:kognai:{company}:{agent_name}`. Legacy form (no company in path)
|
|
46
127
|
* is `did:kognai:{agent_name}`. */
|
|
47
128
|
agent_did: string;
|
|
129
|
+
/** Prior DID a citizen carried before a company-scoped re-mint (e.g. legacy
|
|
130
|
+
* `did:kognai:{agent}`). Preserved so SCORE / ERC-8004 reputation history
|
|
131
|
+
* keyed on the old DID isn't orphaned by the migration. */
|
|
132
|
+
legacy_did?: string;
|
|
48
133
|
/** ISO-8601 mint timestamp. */
|
|
49
134
|
mintedAt: string;
|
|
50
135
|
/** New citizens start at Tier I; promotion is constitutional. */
|
|
@@ -82,12 +167,14 @@ export declare function mintCitizen(agent_name: string, opts?: {
|
|
|
82
167
|
tier?: CitizenTier;
|
|
83
168
|
citizen_type?: CitizenType;
|
|
84
169
|
now?: Date;
|
|
85
|
-
/**
|
|
86
|
-
*
|
|
87
|
-
* citizen_id format, and the `did:kognai:{company}:{agent_name}` DID
|
|
88
|
-
* form. When omitted, behaves exactly as before (legacy Kognai-internal
|
|
89
|
-
* path: random hex citizen_id, global rollNumber sequence). */
|
|
170
|
+
/** @deprecated prefer `owner: { kind:'company', id }`. Owning company —
|
|
171
|
+
* back-compat alias that maps to owner kind 'company'. */
|
|
90
172
|
company?: string;
|
|
173
|
+
/** Lineage owner — who the citizen belongs to. SAF derives this from the
|
|
174
|
+
* spawn requester's DID so the new citizen inherits the requester's
|
|
175
|
+
* identity (company / external org / user wallet / SCS). When omitted
|
|
176
|
+
* (and no `company`), uses the legacy Kognai-internal path. */
|
|
177
|
+
owner?: CitizenOwner;
|
|
91
178
|
}): CitizenRecord;
|
|
92
179
|
/** Lookup an existing citizen by (agent_name, company). Returns null when
|
|
93
180
|
* no match. Treats undefined company as 'kognai' for legacy compatibility. */
|
package/dist/lib/citizenship.js
CHANGED
|
@@ -17,7 +17,13 @@
|
|
|
17
17
|
* Stdlib only. No LLM, no network. Pure issuance.
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.COMPANY_PREFIXES = void 0;
|
|
20
|
+
exports.SHARE_DISCOUNT_PCT = exports.COMPANY_PREFIXES = void 0;
|
|
21
|
+
exports.defaultInstrumentation = defaultInstrumentation;
|
|
22
|
+
exports.citizenBenefits = citizenBenefits;
|
|
23
|
+
exports.applyCitizenDiscount = applyCitizenDiscount;
|
|
24
|
+
exports.canUseSkillBankFree = canUseSkillBankFree;
|
|
25
|
+
exports.eligibleForPlumberReview = eligibleForPlumberReview;
|
|
26
|
+
exports.identityFor = identityFor;
|
|
21
27
|
exports.mintCitizen = mintCitizen;
|
|
22
28
|
exports.lookupCitizen = lookupCitizen;
|
|
23
29
|
exports.renderCitizenYaml = renderCitizenYaml;
|
|
@@ -41,7 +47,84 @@ exports.COMPANY_PREFIXES = {
|
|
|
41
47
|
kognai: 'KGN',
|
|
42
48
|
voxight: 'VXG',
|
|
43
49
|
invoica: 'INV',
|
|
50
|
+
kreativ: 'KRV', // Kognai Kreativ — SCS001 → creative studio (TICKET-158/306)
|
|
51
|
+
scs001: 'SCS', // legacy content swarm; folds into Kreativ as agents migrate
|
|
52
|
+
achiri: 'ACH', // Tunisia AI companion
|
|
53
|
+
asterpay: 'APY', // payments product
|
|
54
|
+
dri: 'DRI', // Dynamic Research Instrument (Phase 3)
|
|
44
55
|
};
|
|
56
|
+
/** citizen_id prefixes for the non-company owner kinds (company uses COMPANY_PREFIXES).
|
|
57
|
+
* These use a GLOBAL per-kind roll sequence; the specific owner lives in the DID. */
|
|
58
|
+
const OWNER_PREFIX = {
|
|
59
|
+
external: 'EXT',
|
|
60
|
+
user: 'CIT',
|
|
61
|
+
scs: 'SWM',
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Posture by owner kind. **Internal** citizens (kognai + product companies) are
|
|
65
|
+
* born fully instrumented and bound to all constitutional duties — ON, mandatory.
|
|
66
|
+
* **External** orgs/swarms and **user** citizens (+ the SCS they form) get the
|
|
67
|
+
* same machinery baseline-available but OFF; they opt in (see citizenBenefits)
|
|
68
|
+
* for fee discounts and, for users, the improvement loop.
|
|
69
|
+
*/
|
|
70
|
+
function defaultInstrumentation(owner_kind) {
|
|
71
|
+
const internal = !owner_kind || owner_kind === 'company';
|
|
72
|
+
return { ksl: internal, failure_log: internal, skill_bank: internal };
|
|
73
|
+
}
|
|
74
|
+
/** Kognai-fee discount (%) granted per shared channel an opt-in citizen enables.
|
|
75
|
+
* Tunable Founder policy. */
|
|
76
|
+
exports.SHARE_DISCOUNT_PCT = { failure_log: 10, skill_bank: 10 };
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the economic + improvement-loop benefits a citizen unlocks from its
|
|
79
|
+
* current instrumentation. Internal citizens are first-party (mandatory posture,
|
|
80
|
+
* no discount concept). External/user/scs earn a fee discount per shared channel;
|
|
81
|
+
* users (+ their SCS) additionally unlock free skill-bank use + Plumber review.
|
|
82
|
+
*/
|
|
83
|
+
function citizenBenefits(c) {
|
|
84
|
+
const i = c.instrumentation ?? defaultInstrumentation(c.owner_kind);
|
|
85
|
+
const optInEligible = c.owner_kind === 'external' || c.owner_kind === 'user' || c.owner_kind === 'scs';
|
|
86
|
+
const isUser = c.owner_kind === 'user' || c.owner_kind === 'scs';
|
|
87
|
+
let discount = 0;
|
|
88
|
+
if (optInEligible) {
|
|
89
|
+
if (i.failure_log)
|
|
90
|
+
discount += exports.SHARE_DISCOUNT_PCT.failure_log;
|
|
91
|
+
if (i.skill_bank)
|
|
92
|
+
discount += exports.SHARE_DISCOUNT_PCT.skill_bank;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
fee_discount_pct: discount,
|
|
96
|
+
skill_bank_free_use: isUser && i.skill_bank,
|
|
97
|
+
plumber_review: isUser && i.failure_log,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// ─── citizenBenefits consumers (billing / skill-bank / plumber) ─────────────────
|
|
101
|
+
// The integration surface for downstream systems. They call these instead of
|
|
102
|
+
// re-deriving policy, so the discount/free-use/review rules live in one place.
|
|
103
|
+
/** Apply a citizen's earned discount to a Kognai fee (atomic units). */
|
|
104
|
+
function applyCitizenDiscount(feeAtomic, c) {
|
|
105
|
+
const pct = citizenBenefits(c).fee_discount_pct;
|
|
106
|
+
return Math.max(0, Math.round(feeAtomic * (1 - pct / 100)));
|
|
107
|
+
}
|
|
108
|
+
/** Skill-bank gate: may this citizen draw from the skill bank for free? */
|
|
109
|
+
function canUseSkillBankFree(c) {
|
|
110
|
+
return citizenBenefits(c).skill_bank_free_use;
|
|
111
|
+
}
|
|
112
|
+
/** Plumber gate: is this citizen's work eligible for failure-log-driven Plumber review? */
|
|
113
|
+
function eligibleForPlumberReview(c) {
|
|
114
|
+
return citizenBenefits(c).plumber_review;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve an agent's canonical *instance* identity (DID + citizen_id) from the
|
|
118
|
+
* registry — the key failure-logging / KSL / SCORE must attribute to, instead of
|
|
119
|
+
* the role string (the TICKET-152 `agent_id="coder"` corruption). Returns null
|
|
120
|
+
* when the agent has no citizen record.
|
|
121
|
+
*/
|
|
122
|
+
function identityFor(agent_name, company) {
|
|
123
|
+
const c = lookupCitizen({ agent_name, company });
|
|
124
|
+
if (!c)
|
|
125
|
+
return null;
|
|
126
|
+
return { agent_did: c.agent_did, citizen_id: c.citizen_id, owner_kind: c.owner_kind, owner_id: c.owner_id };
|
|
127
|
+
}
|
|
45
128
|
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
46
129
|
/**
|
|
47
130
|
* Mint a new citizen. Assigns ID + roll number, writes the registry
|
|
@@ -65,26 +148,49 @@ function mintCitizen(agent_name, opts = {}) {
|
|
|
65
148
|
// is a denormalized index that should always be derivable from them.
|
|
66
149
|
reconcileFromDisk();
|
|
67
150
|
const registry = readRegistry();
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
151
|
+
// Resolve lineage. `owner` wins; `company` is the back-compat alias; neither
|
|
152
|
+
// → legacy kognai-internal path (random hex id, global roll, did:kognai:{agent}).
|
|
153
|
+
const owner = opts.owner ?? (opts.company ? { kind: 'company', id: opts.company } : null);
|
|
154
|
+
// Idempotency. user → exactly ONE per wallet (key on owner_id only, ignoring
|
|
155
|
+
// agent_name — a wallet has a single citizen). company/external/scs →
|
|
156
|
+
// (agent_name, kind, owner_id). legacy → (agent_name, company='kognai').
|
|
157
|
+
const ownerIdOf = (c) => c.owner_id ?? c.company ?? 'kognai';
|
|
158
|
+
// Legacy records (no owner_kind) are company-scoped (kognai by default).
|
|
159
|
+
const ownerKindOf = (c) => c.owner_kind ?? 'company';
|
|
160
|
+
const existing = registry.citizens.find((c) => {
|
|
161
|
+
if (owner?.kind === 'user')
|
|
162
|
+
return c.owner_kind === 'user' && c.owner_id === owner.id;
|
|
163
|
+
if (owner)
|
|
164
|
+
return c.agent_name === agent_name && ownerKindOf(c) === owner.kind && ownerIdOf(c) === owner.id;
|
|
165
|
+
return c.agent_name === agent_name && (c.company ?? 'kognai') === 'kognai';
|
|
166
|
+
});
|
|
73
167
|
if (existing)
|
|
74
168
|
return existing;
|
|
75
169
|
const now = opts.now ?? new Date();
|
|
76
170
|
let citizen_id;
|
|
77
171
|
let rollNumber;
|
|
78
172
|
let agent_did;
|
|
79
|
-
if (
|
|
80
|
-
//
|
|
81
|
-
const prefix = exports.COMPANY_PREFIXES[
|
|
82
|
-
const
|
|
83
|
-
.filter((c) => (c
|
|
173
|
+
if (owner?.kind === 'company') {
|
|
174
|
+
// Per-company rollNumber, COMPANY_PREFIXES id, did:kognai:{company}:{agent}.
|
|
175
|
+
const prefix = exports.COMPANY_PREFIXES[owner.id] ?? owner.id.slice(0, 3).toUpperCase();
|
|
176
|
+
const rolls = registry.citizens
|
|
177
|
+
.filter((c) => ownerKindOf(c) === 'company' && ownerIdOf(c) === owner.id)
|
|
84
178
|
.map((c) => c.rollNumber);
|
|
85
|
-
rollNumber =
|
|
179
|
+
rollNumber = rolls.length ? Math.max(...rolls) + 1 : 1;
|
|
86
180
|
citizen_id = `${prefix}-${String(rollNumber).padStart(4, '0')}`;
|
|
87
|
-
agent_did = `did:kognai:${
|
|
181
|
+
agent_did = `did:kognai:${owner.id}:${agent_name}`;
|
|
182
|
+
}
|
|
183
|
+
else if (owner) {
|
|
184
|
+
// user / scs / external — GLOBAL per-kind roll; the specific owner lives in
|
|
185
|
+
// the DID. user: 1 per wallet → did:kognai:citizen:{wallet}.
|
|
186
|
+
const prefix = OWNER_PREFIX[owner.kind];
|
|
187
|
+
const rolls = registry.citizens.filter((c) => c.owner_kind === owner.kind).map((c) => c.rollNumber);
|
|
188
|
+
rollNumber = rolls.length ? Math.max(...rolls) + 1 : 1;
|
|
189
|
+
citizen_id = `${prefix}-${String(rollNumber).padStart(4, '0')}`;
|
|
190
|
+
agent_did =
|
|
191
|
+
owner.kind === 'user' ? `did:kognai:citizen:${owner.id}`
|
|
192
|
+
: owner.kind === 'scs' ? `did:kognai:scs:${owner.id}:${agent_name}`
|
|
193
|
+
: `did:external:${owner.id}:${agent_name}`;
|
|
88
194
|
}
|
|
89
195
|
else {
|
|
90
196
|
// Legacy Kognai-internal path: random hex ID + global rollNumber sequence.
|
|
@@ -96,7 +202,11 @@ function mintCitizen(agent_name, opts = {}) {
|
|
|
96
202
|
citizen_id,
|
|
97
203
|
rollNumber,
|
|
98
204
|
agent_name,
|
|
99
|
-
...(
|
|
205
|
+
...(owner?.kind === 'company' ? { company: owner.id } : {}),
|
|
206
|
+
...(owner ? { owner_kind: owner.kind, owner_id: owner.id } : {}),
|
|
207
|
+
// Every minted citizen carries the posture: internal ON, external/user/scs
|
|
208
|
+
// baseline-OFF (opt-in). KSL/failure-log/skill-bank + duties wire from this.
|
|
209
|
+
instrumentation: defaultInstrumentation(owner?.kind),
|
|
100
210
|
agent_did,
|
|
101
211
|
mintedAt: now.toISOString(),
|
|
102
212
|
tier: opts.tier ?? 'I',
|
|
@@ -112,9 +222,9 @@ function mintCitizen(agent_name, opts = {}) {
|
|
|
112
222
|
};
|
|
113
223
|
registry.citizens.push(record);
|
|
114
224
|
registry.total = registry.citizens.length;
|
|
115
|
-
//
|
|
116
|
-
// mints
|
|
117
|
-
if (!
|
|
225
|
+
// Advance the global next_roll_number only on the legacy path; owner-scoped
|
|
226
|
+
// mints compute their own roll at mint time.
|
|
227
|
+
if (!owner)
|
|
118
228
|
registry.next_roll_number = rollNumber + 1;
|
|
119
229
|
writeRegistry(registry);
|
|
120
230
|
return record;
|
|
@@ -138,14 +248,17 @@ function renderCitizenYaml(c) {
|
|
|
138
248
|
citizen_id: ${c.citizen_id}
|
|
139
249
|
rollNumber: ${c.rollNumber}
|
|
140
250
|
agent_name: ${c.agent_name}
|
|
141
|
-
agent_did: ${c.agent_did}
|
|
142
|
-
mintedAt: "${c.mintedAt}"
|
|
251
|
+
${c.owner_kind ? `owner_kind: ${c.owner_kind}\n` : ''}${c.owner_id ? `owner_id: ${c.owner_id}\n` : ''}${c.company ? `company: ${c.company}\n` : ''}agent_did: ${c.agent_did}
|
|
252
|
+
${c.legacy_did ? `legacy_did: ${c.legacy_did}\n` : ''}mintedAt: "${c.mintedAt}"
|
|
143
253
|
tier: ${c.tier}
|
|
144
254
|
citizen_type: ${c.citizen_type}
|
|
145
255
|
mascot:
|
|
146
256
|
hue: ${c.mascot.hue}
|
|
147
257
|
state: ${c.mascot.state}
|
|
148
258
|
reputation: ${c.reputation}
|
|
259
|
+
instrumentation_ksl: ${c.instrumentation?.ksl ?? true}
|
|
260
|
+
instrumentation_failure_log: ${c.instrumentation?.failure_log ?? true}
|
|
261
|
+
instrumentation_skill_bank: ${c.instrumentation?.skill_bank ?? true}
|
|
149
262
|
founding_agent: ${c.founding_agent ?? 'ceo'}
|
|
150
263
|
proposing_agent: ${c.proposing_agent ?? 'cto'}
|
|
151
264
|
${c.origin_proposal_id ? `origin_proposal_id: ${c.origin_proposal_id}\n` : ''}`;
|
|
@@ -256,7 +369,11 @@ function parseCitizenYaml(raw) {
|
|
|
256
369
|
citizen_id: flat.citizen_id,
|
|
257
370
|
rollNumber: parseInt(flat.rollNumber, 10),
|
|
258
371
|
agent_name: flat.agent_name,
|
|
372
|
+
...(flat.company ? { company: flat.company } : {}),
|
|
373
|
+
...(flat.owner_kind ? { owner_kind: flat.owner_kind } : {}),
|
|
374
|
+
...(flat.owner_id ? { owner_id: flat.owner_id } : {}),
|
|
259
375
|
agent_did: flat.agent_did || `did:kognai:${flat.agent_name}`,
|
|
376
|
+
...(flat.legacy_did ? { legacy_did: flat.legacy_did } : {}),
|
|
260
377
|
mintedAt: flat.mintedAt,
|
|
261
378
|
tier: flat.tier || 'I',
|
|
262
379
|
citizen_type: flat.citizen_type || 'spawned',
|
|
@@ -265,6 +382,13 @@ function parseCitizenYaml(raw) {
|
|
|
265
382
|
state: mascot.state || 'idle',
|
|
266
383
|
},
|
|
267
384
|
reputation: flat.reputation ? parseInt(flat.reputation, 10) : 50,
|
|
385
|
+
instrumentation: flat.instrumentation_ksl !== undefined
|
|
386
|
+
? {
|
|
387
|
+
ksl: flat.instrumentation_ksl === 'true',
|
|
388
|
+
failure_log: flat.instrumentation_failure_log === 'true',
|
|
389
|
+
skill_bank: flat.instrumentation_skill_bank === 'true',
|
|
390
|
+
}
|
|
391
|
+
: defaultInstrumentation(flat.owner_kind),
|
|
268
392
|
founding_agent: flat.founding_agent,
|
|
269
393
|
proposing_agent: flat.proposing_agent,
|
|
270
394
|
origin_proposal_id: flat.origin_proposal_id,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CitizenOwner } from './citizenship';
|
|
1
2
|
import type { AgentTask, ReviewResult, CTOProposal, CTOReport } from './orchestrate-engine';
|
|
2
3
|
export declare class SupervisorAgent {
|
|
3
4
|
private systemPrompt;
|
|
@@ -62,6 +63,12 @@ export interface SpawnGateResult {
|
|
|
62
63
|
pending_approval?: boolean;
|
|
63
64
|
/** Optional one-line audit string logged on an approved decision. */
|
|
64
65
|
audit?: string;
|
|
66
|
+
/** Lineage owner the gate resolved from the spawn requester (via SAF /
|
|
67
|
+
* deriveOwner). When set, the engine mints the citizen owner-scoped instead
|
|
68
|
+
* of legacy — so an Invoica swarm's spawn becomes an Invoica citizen, a
|
|
69
|
+
* Voxight swarm's a Voxight citizen, etc. The running company is carried by
|
|
70
|
+
* the template-injected gate's requester_did, not hardcoded in the engine. */
|
|
71
|
+
owner?: CitizenOwner;
|
|
65
72
|
}
|
|
66
73
|
export type SpawnGate = (spec: AgentSpawnSpec) => SpawnGateResult;
|
|
67
74
|
export declare class AgentCreator {
|