role-os 2.2.1 → 2.3.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/CHANGELOG.md +23 -0
- package/README.md +48 -13
- package/bin/roleos.mjs +11 -0
- package/package.json +2 -2
- package/src/artifacts.mjs +70 -0
- package/src/dispatch.mjs +8 -4
- package/src/knowledge/analyze-artifact-evidence.mjs +420 -0
- package/src/knowledge/attach-bundle-to-evidence.mjs +62 -0
- package/src/knowledge/attach-bundle-to-packet.mjs +42 -0
- package/src/knowledge/fallback-policy.mjs +79 -0
- package/src/knowledge/index.mjs +14 -0
- package/src/knowledge/render-knowledge-block.mjs +215 -0
- package/src/knowledge/resolve-overlay.mjs +66 -0
- package/src/knowledge/retrieve-for-dispatch.mjs +150 -0
- package/src/mission-run.mjs +114 -2
- package/src/mission.mjs +130 -0
- package/src/packs.mjs +37 -0
- package/src/route.mjs +51 -0
- package/src/run-cmd.mjs +4 -1
- package/src/run.mjs +42 -1
- package/src/swarm/build-gate.mjs +127 -0
- package/src/swarm/domain-detect.mjs +230 -0
- package/src/swarm/persist-bridge.mjs +174 -0
- package/src/swarm-cmd.mjs +424 -0
- package/src/tool-profiles.mjs +9 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Block Renderer — transforms a retrieval bundle into a prompt fragment.
|
|
3
|
+
*
|
|
4
|
+
* This is a pure function: same input → same output. No retrieval logic.
|
|
5
|
+
* No raw JSON dump. No ad hoc search. Just governed prompt construction.
|
|
6
|
+
*
|
|
7
|
+
* The block has 4 sections:
|
|
8
|
+
* 1. Knowledge posture (one line)
|
|
9
|
+
* 2. Retrieved evidence (top N excerpts with citations)
|
|
10
|
+
* 3. Warnings / constraints
|
|
11
|
+
* 4. Usage law (status-specific behavioral rules)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ── Configuration ───────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const MAX_EVIDENCE_CHUNKS = 6;
|
|
17
|
+
const MAX_EXCERPT_LENGTH = 200;
|
|
18
|
+
|
|
19
|
+
// ── Status-Specific Usage Law ───────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const USAGE_LAW = {
|
|
22
|
+
strong: [
|
|
23
|
+
"Prioritize retrieved evidence when making claims in your domain.",
|
|
24
|
+
"Cite retrieved references when making specific substantive claims.",
|
|
25
|
+
"Do not invent sources that were not retrieved.",
|
|
26
|
+
],
|
|
27
|
+
weak: [
|
|
28
|
+
"Treat retrieved evidence as partial — it may not cover the full picture.",
|
|
29
|
+
"Avoid overclaiming based on limited evidence.",
|
|
30
|
+
"Escalate uncertainty where it affects your deliverable.",
|
|
31
|
+
"Do not invent sources that were not retrieved.",
|
|
32
|
+
],
|
|
33
|
+
stale: [
|
|
34
|
+
"Retrieved evidence may be outdated — note possible staleness in your analysis.",
|
|
35
|
+
"Do not present stale evidence as current truth without qualification.",
|
|
36
|
+
"Flag any claims that depend on time-sensitive data.",
|
|
37
|
+
"Do not invent sources that were not retrieved.",
|
|
38
|
+
],
|
|
39
|
+
conflicted: [
|
|
40
|
+
"Retrieved sources contain conflicting evidence.",
|
|
41
|
+
"Surface the conflict explicitly — do not flatten disagreement into fake consensus.",
|
|
42
|
+
"Acknowledge which claims are contested and by what sources.",
|
|
43
|
+
"Do not invent sources that were not retrieved.",
|
|
44
|
+
],
|
|
45
|
+
none: null, // No knowledge block rendered
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ── Main Renderer ───────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Render a knowledge prompt block from packet.knowledge.
|
|
52
|
+
*
|
|
53
|
+
* @param {Object|null} packetKnowledge - packet.knowledge (with retrieval_bundle and status)
|
|
54
|
+
* @returns {string|null} Prompt block string, or null if no knowledge to render
|
|
55
|
+
*/
|
|
56
|
+
export function renderKnowledgeBlock(packetKnowledge) {
|
|
57
|
+
if (!packetKnowledge || packetKnowledge.status === "none") {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { retrieval_bundle: bundle, status } = packetKnowledge;
|
|
62
|
+
if (!bundle || !bundle.selected?.length) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const sections = [];
|
|
67
|
+
|
|
68
|
+
// 1. Posture line
|
|
69
|
+
sections.push(`## Retrieved Knowledge\n\nKnowledge status: **${status}**`);
|
|
70
|
+
|
|
71
|
+
// 2. Evidence excerpts
|
|
72
|
+
const evidenceBlock = renderEvidenceBlock(bundle.selected);
|
|
73
|
+
if (evidenceBlock) {
|
|
74
|
+
sections.push(evidenceBlock);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 3. Warnings
|
|
78
|
+
const warningsBlock = renderWarningsBlock(bundle.warnings);
|
|
79
|
+
if (warningsBlock) {
|
|
80
|
+
sections.push(warningsBlock);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 4. Usage law
|
|
84
|
+
const lawBlock = renderUsageLaw(status);
|
|
85
|
+
if (lawBlock) {
|
|
86
|
+
sections.push(lawBlock);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return sections.join("\n\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Evidence Renderer ───────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Render the top N evidence excerpts.
|
|
96
|
+
*/
|
|
97
|
+
function renderEvidenceBlock(selected) {
|
|
98
|
+
if (!selected?.length) return null;
|
|
99
|
+
|
|
100
|
+
const top = selected.slice(0, MAX_EVIDENCE_CHUNKS);
|
|
101
|
+
const lines = ["### Evidence"];
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < top.length; i++) {
|
|
104
|
+
const chunk = top[i];
|
|
105
|
+
const trustLabel = chunk.metadata?.trust_tier ?? "general";
|
|
106
|
+
const freshnessLabel = chunk.metadata?.freshness?.status ?? "undated";
|
|
107
|
+
const citation = chunk.citation?.reference ?? chunk.chunk_id;
|
|
108
|
+
const excerpt = truncateExcerpt(chunk.content);
|
|
109
|
+
|
|
110
|
+
lines.push(`${i + 1}. [${trustLabel} | ${freshnessLabel}] ${citation}`);
|
|
111
|
+
lines.push(` "${excerpt}"`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return lines.join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Warnings Renderer ───────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Render retrieval warnings as constraints.
|
|
121
|
+
*/
|
|
122
|
+
function renderWarningsBlock(warnings) {
|
|
123
|
+
if (!warnings?.length) return null;
|
|
124
|
+
|
|
125
|
+
const meaningful = warnings.filter(
|
|
126
|
+
(w) => w.code !== "FORBIDDEN_SOURCE_HIT" // governance working, not a user-facing warning
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (!meaningful.length) return null;
|
|
130
|
+
|
|
131
|
+
const lines = ["### Warnings"];
|
|
132
|
+
for (const warning of meaningful) {
|
|
133
|
+
lines.push(`- ${formatWarning(warning)}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Format a warning into human-readable text.
|
|
141
|
+
*/
|
|
142
|
+
function formatWarning(warning) {
|
|
143
|
+
switch (warning.code) {
|
|
144
|
+
case "NO_HIGH_TRUST_MATCH":
|
|
145
|
+
return "No authoritative sources matched — evidence quality is limited.";
|
|
146
|
+
case "ONLY_SHARED_CORPUS":
|
|
147
|
+
return "No role-specific evidence found — results are from shared corpus only.";
|
|
148
|
+
case "STALE_DOMINANT":
|
|
149
|
+
return "Most retrieved evidence is outdated — treat with caution.";
|
|
150
|
+
case "LOW_DIVERSITY":
|
|
151
|
+
return "Evidence comes from a single source — limited perspective.";
|
|
152
|
+
case "CONFLICTING_EVIDENCE":
|
|
153
|
+
return `Conflicting evidence: ${warning.message}`;
|
|
154
|
+
default:
|
|
155
|
+
return warning.message;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── Usage Law Renderer ──────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Render status-specific usage law.
|
|
163
|
+
*/
|
|
164
|
+
function renderUsageLaw(status) {
|
|
165
|
+
const laws = USAGE_LAW[status];
|
|
166
|
+
if (!laws) return null;
|
|
167
|
+
|
|
168
|
+
const lines = ["### Knowledge Use Rules"];
|
|
169
|
+
for (const law of laws) {
|
|
170
|
+
lines.push(`- ${law}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return lines.join("\n");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Truncate content to MAX_EXCERPT_LENGTH, preserving word boundaries.
|
|
180
|
+
*/
|
|
181
|
+
function truncateExcerpt(content) {
|
|
182
|
+
if (!content) return "";
|
|
183
|
+
if (content.length <= MAX_EXCERPT_LENGTH) return content;
|
|
184
|
+
|
|
185
|
+
const truncated = content.slice(0, MAX_EXCERPT_LENGTH);
|
|
186
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
187
|
+
return (lastSpace > MAX_EXCERPT_LENGTH * 0.7 ? truncated.slice(0, lastSpace) : truncated) + "...";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Manifest Summary ────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Generate a compact knowledge summary for the dispatch manifest.
|
|
194
|
+
* Not full excerpts — just posture truth.
|
|
195
|
+
*
|
|
196
|
+
* @param {Object|null} packetKnowledge
|
|
197
|
+
* @returns {Object|null} Compact summary for manifest, or null
|
|
198
|
+
*/
|
|
199
|
+
export function knowledgeManifestSummary(packetKnowledge) {
|
|
200
|
+
if (!packetKnowledge || packetKnowledge.status === "none") {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const { retrieval_bundle: bundle, status } = packetKnowledge;
|
|
205
|
+
if (!bundle) return null;
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
status,
|
|
209
|
+
selected_count: bundle.selected?.length ?? 0,
|
|
210
|
+
trust_posture: bundle.provenance?.trust_posture ?? "weak",
|
|
211
|
+
freshness_posture: bundle.provenance?.freshness_posture ?? "stale",
|
|
212
|
+
warning_codes: (bundle.warnings ?? []).map((w) => w.code),
|
|
213
|
+
rerank_strategy: bundle.summary?.rerank_strategy ?? "unknown",
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a role overlay from knowledge/roles/*.json.
|
|
3
|
+
*
|
|
4
|
+
* Returns the overlay config for a given role, or null if no overlay exists.
|
|
5
|
+
* Roles without overlays fall back to shared-corpus-only retrieval.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join, resolve, dirname } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
// Default overlay search paths (relative to knowledge-core root)
|
|
15
|
+
const OVERLAY_PATHS = [
|
|
16
|
+
// Local knowledge-core checkout (development)
|
|
17
|
+
join(resolve(__dirname, "..", ".."), "knowledge-core", "knowledge", "roles"),
|
|
18
|
+
// Fallback: role-os local knowledge dir
|
|
19
|
+
join(resolve(__dirname, ".."), "knowledge", "roles"),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the overlay for a role.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} roleId - Role identifier (e.g. "security-reviewer")
|
|
26
|
+
* @param {Object} [options]
|
|
27
|
+
* @param {string[]} [options.searchPaths] - Override overlay search paths
|
|
28
|
+
* @returns {{ overlay: object, path: string } | null}
|
|
29
|
+
*/
|
|
30
|
+
export function resolveOverlay(roleId, options = {}) {
|
|
31
|
+
const paths = options.searchPaths || OVERLAY_PATHS;
|
|
32
|
+
|
|
33
|
+
for (const dir of paths) {
|
|
34
|
+
const filePath = join(dir, `${roleId}.json`);
|
|
35
|
+
if (existsSync(filePath)) {
|
|
36
|
+
try {
|
|
37
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
38
|
+
if (data.version !== "1.0") {
|
|
39
|
+
console.warn(`[knowledge] Overlay ${roleId}: unsupported version ${data.version}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (data.role_id !== roleId) {
|
|
43
|
+
console.warn(`[knowledge] Overlay file ${roleId}.json has mismatched role_id: ${data.role_id}`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return { overlay: data, path: filePath };
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.warn(`[knowledge] Failed to parse overlay for ${roleId}:`, e.message);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if a role has an overlay available.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} roleId
|
|
61
|
+
* @param {Object} [options]
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
export function hasOverlay(roleId, options = {}) {
|
|
65
|
+
return resolveOverlay(roleId, options) !== null;
|
|
66
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieve knowledge for a dispatch step.
|
|
3
|
+
*
|
|
4
|
+
* This is the integration seam between Role OS dispatch and knowledge-core.
|
|
5
|
+
* Phase 2: wired to the real retrieval pipeline.
|
|
6
|
+
*
|
|
7
|
+
* When a corpus store is available, runs the full pipeline:
|
|
8
|
+
* task + overlay + corpus → candidates → filter → rerank → bundle
|
|
9
|
+
*
|
|
10
|
+
* When no corpus is available, returns a governed stub (graceful degradation).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { resolveOverlay } from "./resolve-overlay.mjs";
|
|
14
|
+
import { applyFallbackPolicy } from "./fallback-policy.mjs";
|
|
15
|
+
|
|
16
|
+
// ── Corpus Store Singleton ──────────────────────────────────────────
|
|
17
|
+
// Lazy-loaded. Role OS sets this via configureKnowledge().
|
|
18
|
+
let _store = null;
|
|
19
|
+
let _retrieveFn = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configure the knowledge subsystem with a live corpus store.
|
|
23
|
+
* Call once at startup (e.g., in session init or dispatch init).
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} options
|
|
26
|
+
* @param {Object} options.store - CorpusStore instance from knowledge-core
|
|
27
|
+
* @param {Function} options.retrieve - retrieve() function from knowledge-core
|
|
28
|
+
*/
|
|
29
|
+
export function configureKnowledge({ store, retrieve }) {
|
|
30
|
+
_store = store;
|
|
31
|
+
_retrieveFn = retrieve;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if knowledge subsystem is configured with a live corpus.
|
|
36
|
+
*/
|
|
37
|
+
export function isKnowledgeConfigured() {
|
|
38
|
+
return _store !== null && _retrieveFn !== null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {Object} RetrieveOptions
|
|
43
|
+
* @property {string} roleId - Role identifier
|
|
44
|
+
* @property {string} taskText - Task description from packet
|
|
45
|
+
* @property {string} [packetContextSummary] - Optional packet context
|
|
46
|
+
* @property {Object} [routeSignals] - Route scoring signals
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Retrieve knowledge for a role's dispatch step.
|
|
51
|
+
*
|
|
52
|
+
* @param {RetrieveOptions} options
|
|
53
|
+
* @returns {Promise<{ bundle: object, status: string, fallback: object }>}
|
|
54
|
+
*/
|
|
55
|
+
export async function retrieveForDispatch({ roleId, taskText, packetContextSummary, routeSignals }) {
|
|
56
|
+
const overlayResult = resolveOverlay(roleId);
|
|
57
|
+
const overlay = overlayResult?.overlay ?? null;
|
|
58
|
+
|
|
59
|
+
// ── Live retrieval (Phase 2) ────────────────────────────────────
|
|
60
|
+
if (_store && _retrieveFn) {
|
|
61
|
+
const bundle = await _retrieveFn({
|
|
62
|
+
store: _store,
|
|
63
|
+
roleId,
|
|
64
|
+
taskText,
|
|
65
|
+
overlay,
|
|
66
|
+
packetContextSummary,
|
|
67
|
+
lexicalOnly: true, // Phase 2: lexical-only until embeddings are populated
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const fallback = applyFallbackPolicy(bundle, overlay);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
bundle,
|
|
74
|
+
status: deriveStatus(fallback),
|
|
75
|
+
fallback,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Stub fallback (no corpus configured) ────────────────────────
|
|
80
|
+
const bundle = buildStubBundle(roleId, taskText, overlayResult);
|
|
81
|
+
const fallback = applyFallbackPolicy(bundle, overlay);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
bundle,
|
|
85
|
+
status: deriveStatus(fallback),
|
|
86
|
+
fallback,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Derive packet knowledge status from fallback state.
|
|
92
|
+
*/
|
|
93
|
+
function deriveStatus(fallback) {
|
|
94
|
+
switch (fallback.state) {
|
|
95
|
+
case "healthy": return "strong";
|
|
96
|
+
case "no_overlay": return "none";
|
|
97
|
+
case "no_strong_match": return "weak";
|
|
98
|
+
case "stale_dominant": return "stale";
|
|
99
|
+
case "conflicting": return "conflicted";
|
|
100
|
+
case "forbidden_hit": return "strong"; // forbidden sources removed = governance working
|
|
101
|
+
default: return "weak";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build a stub retrieval bundle when no corpus is available.
|
|
107
|
+
*/
|
|
108
|
+
function buildStubBundle(roleId, taskText, overlayResult) {
|
|
109
|
+
const hasOverlayData = overlayResult !== null;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
version: "1.0",
|
|
113
|
+
role_id: roleId,
|
|
114
|
+
query: {
|
|
115
|
+
task_text: taskText,
|
|
116
|
+
lexical_query: [],
|
|
117
|
+
semantic_query: [],
|
|
118
|
+
applied_overlay_rules: hasOverlayData ? [`overlay:${roleId}`] : [],
|
|
119
|
+
applied_filters: [],
|
|
120
|
+
generated_at: new Date().toISOString(),
|
|
121
|
+
},
|
|
122
|
+
summary: {
|
|
123
|
+
total_candidates: 0,
|
|
124
|
+
selected_count: 0,
|
|
125
|
+
stale_count: 0,
|
|
126
|
+
forbidden_hits: 0,
|
|
127
|
+
trust_tier_breakdown: { authoritative: 0, preferred: 0, general: 0, untrusted: 0 },
|
|
128
|
+
source_breakdown: {},
|
|
129
|
+
rerank_strategy: "stub-no-corpus",
|
|
130
|
+
},
|
|
131
|
+
selected: [],
|
|
132
|
+
rejected: [],
|
|
133
|
+
provenance: {
|
|
134
|
+
source_ids: [],
|
|
135
|
+
document_ids: [],
|
|
136
|
+
trust_posture: "weak",
|
|
137
|
+
freshness_posture: "stale",
|
|
138
|
+
},
|
|
139
|
+
warnings: [
|
|
140
|
+
{ code: "ONLY_SHARED_CORPUS", message: "No corpus configured — stub bundle returned" },
|
|
141
|
+
],
|
|
142
|
+
diagnostics: {
|
|
143
|
+
latency_ms: 0,
|
|
144
|
+
candidate_pool_size: 0,
|
|
145
|
+
deduped_count: 0,
|
|
146
|
+
dropped_forbidden_count: 0,
|
|
147
|
+
dropped_stale_count: 0,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
package/src/mission-run.mjs
CHANGED
|
@@ -75,7 +75,10 @@ export function createRun(missionKey, taskDescription, options = {}) {
|
|
|
75
75
|
let steps;
|
|
76
76
|
const dd = mission.dynamicDispatch;
|
|
77
77
|
|
|
78
|
-
if (dd && options.manifest) {
|
|
78
|
+
if (missionKey === "dogfood-swarm" && dd && options.manifest) {
|
|
79
|
+
// Swarm dispatch — build staged domain steps from swarm manifest
|
|
80
|
+
steps = buildSwarmSteps(mission, options.manifest);
|
|
81
|
+
} else if (dd && options.manifest) {
|
|
79
82
|
// Dynamic dispatch — build steps from manifest
|
|
80
83
|
steps = buildDynamicSteps(mission, options.manifest);
|
|
81
84
|
} else {
|
|
@@ -105,6 +108,7 @@ export function createRun(missionKey, taskDescription, options = {}) {
|
|
|
105
108
|
completionReport: null,
|
|
106
109
|
dynamicDispatch: dd && options.manifest ? true : false,
|
|
107
110
|
manifest: options.manifest || null,
|
|
111
|
+
knowledge: options.knowledge || null, // Phase 5: PacketKnowledge from retrieval
|
|
108
112
|
};
|
|
109
113
|
}
|
|
110
114
|
|
|
@@ -191,6 +195,93 @@ function buildDynamicSteps(mission, manifest) {
|
|
|
191
195
|
return steps;
|
|
192
196
|
}
|
|
193
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Build steps from swarm manifest for dogfood-swarm missions.
|
|
200
|
+
* Creates domain agent steps per stage with coordinator gates.
|
|
201
|
+
* @param {Object} mission
|
|
202
|
+
* @param {Object} manifest - The swarm-manifest.json content
|
|
203
|
+
* @returns {MissionStep[]}
|
|
204
|
+
*/
|
|
205
|
+
function buildSwarmSteps(mission, manifest) {
|
|
206
|
+
const steps = [];
|
|
207
|
+
const domains = manifest.domains || [];
|
|
208
|
+
const stages = manifest.stages || ["health-a", "health-b", "health-c", "feature"];
|
|
209
|
+
const waveLoops = mission.waveLoops || [];
|
|
210
|
+
|
|
211
|
+
// For each stage, create domain agent steps + coordinator gate
|
|
212
|
+
for (const stage of stages) {
|
|
213
|
+
const loopDef = waveLoops.find(w => w.stage === stage);
|
|
214
|
+
|
|
215
|
+
// One step per domain agent
|
|
216
|
+
for (const domain of domains) {
|
|
217
|
+
steps.push({
|
|
218
|
+
role: domain.role,
|
|
219
|
+
produces: "wave-report",
|
|
220
|
+
consumedBy: "Swarm Coordinator",
|
|
221
|
+
domain: domain.id,
|
|
222
|
+
stage,
|
|
223
|
+
waveIteration: 0,
|
|
224
|
+
patterns: domain.patterns,
|
|
225
|
+
status: "pending",
|
|
226
|
+
artifact: null,
|
|
227
|
+
artifactValidation: null,
|
|
228
|
+
note: null,
|
|
229
|
+
startedAt: null,
|
|
230
|
+
completedAt: null,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Coordinator gate step after domain agents
|
|
235
|
+
steps.push({
|
|
236
|
+
role: "Swarm Coordinator",
|
|
237
|
+
produces: "swarm-gate",
|
|
238
|
+
consumedBy: stage === stages[stages.length - 1] ? "Swarm Synthesizer" : domains[0]?.role || null,
|
|
239
|
+
stage,
|
|
240
|
+
isGate: true,
|
|
241
|
+
exitCondition: loopDef?.exitCondition || null,
|
|
242
|
+
maxIterations: loopDef?.maxIterations || 1,
|
|
243
|
+
buildGate: loopDef?.buildGate ?? true,
|
|
244
|
+
userApproval: loopDef?.userApproval ?? false,
|
|
245
|
+
lens: loopDef?.lens || null,
|
|
246
|
+
status: "pending",
|
|
247
|
+
artifact: null,
|
|
248
|
+
artifactValidation: null,
|
|
249
|
+
note: null,
|
|
250
|
+
startedAt: null,
|
|
251
|
+
completedAt: null,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Final stage: Synthesizer + Critic
|
|
256
|
+
steps.push({
|
|
257
|
+
role: "Swarm Synthesizer",
|
|
258
|
+
produces: "swarm-final-report",
|
|
259
|
+
consumedBy: "Critic Reviewer",
|
|
260
|
+
stage: "final",
|
|
261
|
+
status: "pending",
|
|
262
|
+
artifact: null,
|
|
263
|
+
artifactValidation: null,
|
|
264
|
+
note: null,
|
|
265
|
+
startedAt: null,
|
|
266
|
+
completedAt: null,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
steps.push({
|
|
270
|
+
role: "Critic Reviewer",
|
|
271
|
+
produces: "review-verdict",
|
|
272
|
+
consumedBy: null,
|
|
273
|
+
stage: "final",
|
|
274
|
+
status: "pending",
|
|
275
|
+
artifact: null,
|
|
276
|
+
artifactValidation: null,
|
|
277
|
+
note: null,
|
|
278
|
+
startedAt: null,
|
|
279
|
+
completedAt: null,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
return steps;
|
|
283
|
+
}
|
|
284
|
+
|
|
194
285
|
// ── Step through a run ──────────────────────────────────────────────────────
|
|
195
286
|
|
|
196
287
|
/**
|
|
@@ -391,6 +482,7 @@ export function generateCompletionReport(run) {
|
|
|
391
482
|
status: s.status,
|
|
392
483
|
hasArtifact: !!s.artifact,
|
|
393
484
|
note: s.note,
|
|
485
|
+
knowledge: s.knowledge || null, // Phase 5: per-step knowledge posture
|
|
394
486
|
}));
|
|
395
487
|
|
|
396
488
|
const isComplete = run.status === "completed";
|
|
@@ -413,6 +505,13 @@ export function generateCompletionReport(run) {
|
|
|
413
505
|
artifactChain,
|
|
414
506
|
escalationCount: run.escalations.length,
|
|
415
507
|
escalations: run.escalations,
|
|
508
|
+
knowledge: run.knowledge ? {
|
|
509
|
+
status: run.knowledge.status,
|
|
510
|
+
selected_count: run.knowledge.retrieval_bundle?.selected?.length ?? 0,
|
|
511
|
+
trust_posture: run.knowledge.retrieval_bundle?.provenance?.trust_posture ?? "unknown",
|
|
512
|
+
freshness_posture: run.knowledge.retrieval_bundle?.provenance?.freshness_posture ?? "unknown",
|
|
513
|
+
warning_codes: (run.knowledge.retrieval_bundle?.warnings ?? []).map((w) => w.code),
|
|
514
|
+
} : null,
|
|
416
515
|
honestPartial: isPartial || isFailed ? mission.honestPartial : null,
|
|
417
516
|
verdict: isComplete
|
|
418
517
|
? "Mission completed — all artifacts produced, all steps passed."
|
|
@@ -458,7 +557,20 @@ export function formatCompletionReport(report) {
|
|
|
458
557
|
step.status === "blocked" ? "[-]" : "[ ]";
|
|
459
558
|
const artifact = step.hasArtifact ? ` → ${step.produces}` : "";
|
|
460
559
|
const note = step.note ? ` (${step.note})` : "";
|
|
461
|
-
|
|
560
|
+
const kStatus = step.knowledge ? ` [knowledge: ${step.knowledge.status}]` : "";
|
|
561
|
+
lines.push(` ${icon} ${step.role}${artifact}${kStatus}${note}`);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Knowledge posture (Phase 5)
|
|
565
|
+
if (report.knowledge) {
|
|
566
|
+
lines.push("");
|
|
567
|
+
lines.push("## Knowledge");
|
|
568
|
+
lines.push(` Status: ${report.knowledge.status}`);
|
|
569
|
+
lines.push(` Evidence: ${report.knowledge.selected_count} chunks selected`);
|
|
570
|
+
lines.push(` Trust: ${report.knowledge.trust_posture} | Freshness: ${report.knowledge.freshness_posture}`);
|
|
571
|
+
if (report.knowledge.warning_codes.length > 0) {
|
|
572
|
+
lines.push(` Warnings: ${report.knowledge.warning_codes.join(", ")}`);
|
|
573
|
+
}
|
|
462
574
|
}
|
|
463
575
|
|
|
464
576
|
if (report.escalationCount > 0) {
|