baselineos 0.2.0-beta.1
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/LICENSE +17 -0
- package/README.md +198 -0
- package/dist/__evals__/runner.d.ts +2 -0
- package/dist/__evals__/runner.js +14687 -0
- package/dist/__evals__/runner.js.map +1 -0
- package/dist/api/server.d.ts +21 -0
- package/dist/api/server.js +1007 -0
- package/dist/api/server.js.map +1 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +8427 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/core/agent-bus.d.ts +110 -0
- package/dist/core/agent-bus.js +242 -0
- package/dist/core/agent-bus.js.map +1 -0
- package/dist/core/cache.d.ts +66 -0
- package/dist/core/cache.js +160 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/config.d.ts +1002 -0
- package/dist/core/config.js +429 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/indexer.d.ts +152 -0
- package/dist/core/indexer.js +481 -0
- package/dist/core/indexer.js.map +1 -0
- package/dist/core/llm-tracer.d.ts +2 -0
- package/dist/core/llm-tracer.js +241 -0
- package/dist/core/llm-tracer.js.map +1 -0
- package/dist/core/memory.d.ts +86 -0
- package/dist/core/memory.js +346 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/opa-client.d.ts +51 -0
- package/dist/core/opa-client.js +157 -0
- package/dist/core/opa-client.js.map +1 -0
- package/dist/core/opa-policy-gate.d.ts +133 -0
- package/dist/core/opa-policy-gate.js +454 -0
- package/dist/core/opa-policy-gate.js.map +1 -0
- package/dist/core/orchestrator.d.ts +14 -0
- package/dist/core/orchestrator.js +1297 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/pii-detector.d.ts +82 -0
- package/dist/core/pii-detector.js +126 -0
- package/dist/core/pii-detector.js.map +1 -0
- package/dist/core/rag-engine.d.ts +121 -0
- package/dist/core/rag-engine.js +504 -0
- package/dist/core/rag-engine.js.map +1 -0
- package/dist/core/task-queue.d.ts +69 -0
- package/dist/core/task-queue.js +124 -0
- package/dist/core/task-queue.js.map +1 -0
- package/dist/core/telemetry.d.ts +56 -0
- package/dist/core/telemetry.js +94 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/types.d.ts +328 -0
- package/dist/core/types.js +24 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +12444 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-tracer-CIIujuO-.d.ts +493 -0
- package/dist/mcp/server.d.ts +2651 -0
- package/dist/mcp/server.js +676 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/orchestrator-DF89k_AK.d.ts +506 -0
- package/package.json +157 -0
- package/templates/README.md +7 -0
- package/templates/baseline.config.ts +207 -0
|
@@ -0,0 +1,1297 @@
|
|
|
1
|
+
import { EventEmitter } from 'eventemitter3';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { trace, metrics, SpanKind, SpanStatusCode } from '@opentelemetry/api';
|
|
6
|
+
import '@opentelemetry/sdk-node';
|
|
7
|
+
import '@opentelemetry/exporter-trace-otlp-http';
|
|
8
|
+
import '@opentelemetry/resources';
|
|
9
|
+
import '@opentelemetry/semantic-conventions';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { Queue, Worker } from 'bullmq';
|
|
12
|
+
|
|
13
|
+
// src/core/orchestrator.ts
|
|
14
|
+
var tracer = new Proxy({}, {
|
|
15
|
+
get(_target, prop) {
|
|
16
|
+
return trace.getTracer("baselineos", "0.2.0-beta.1")[prop];
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
async function withSpan(name, attributes, fn) {
|
|
20
|
+
return tracer.startActiveSpan(name, { kind: SpanKind.INTERNAL, attributes }, async (span) => {
|
|
21
|
+
try {
|
|
22
|
+
const result = await fn(span);
|
|
23
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
24
|
+
return result;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
span.recordException(err);
|
|
27
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
28
|
+
throw err;
|
|
29
|
+
} finally {
|
|
30
|
+
span.end();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
var TaskInputSchema = z.object({
|
|
35
|
+
title: z.string().min(1),
|
|
36
|
+
description: z.string().min(1),
|
|
37
|
+
priority: z.enum(["critical", "high", "medium", "low"]).default("medium"),
|
|
38
|
+
requiredCapabilities: z.array(z.string()).default([]),
|
|
39
|
+
acceptanceCriteria: z.array(z.object({
|
|
40
|
+
description: z.string(),
|
|
41
|
+
type: z.enum(["automated", "agent-review", "human-review"]).default("automated"),
|
|
42
|
+
weight: z.number().min(0).max(1).default(1)
|
|
43
|
+
})).default([]),
|
|
44
|
+
context: z.record(z.unknown()).default({})
|
|
45
|
+
});
|
|
46
|
+
var PiiBlockedError = class extends Error {
|
|
47
|
+
constructor(types) {
|
|
48
|
+
super(`PII detected in prompt \u2014 blocked: ${types.join(", ")}`);
|
|
49
|
+
this.types = types;
|
|
50
|
+
this.name = "PiiBlockedError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var PRIORITY_MAP = {
|
|
54
|
+
critical: 1,
|
|
55
|
+
high: 2,
|
|
56
|
+
medium: 5,
|
|
57
|
+
low: 10
|
|
58
|
+
};
|
|
59
|
+
var TaskQueue = class {
|
|
60
|
+
queue;
|
|
61
|
+
worker;
|
|
62
|
+
queueName;
|
|
63
|
+
/**
|
|
64
|
+
* @param executor Called by the worker when a job is dequeued.
|
|
65
|
+
* Receives the taskId. Should call Orchestrator.dispatchExecution().
|
|
66
|
+
* @param config Queue configuration.
|
|
67
|
+
*/
|
|
68
|
+
constructor(executor, config = {}) {
|
|
69
|
+
this.queueName = config.queueName ?? "baseline-tasks";
|
|
70
|
+
const redisUrl = config.redisUrl ?? process.env["BASELINE_REDIS_URL"] ?? "redis://localhost:6379";
|
|
71
|
+
const connection = this.parseRedisUrl(redisUrl);
|
|
72
|
+
const attempts = config.attempts ?? 3;
|
|
73
|
+
const backoffDelay = config.backoffDelay ?? 2e3;
|
|
74
|
+
this.queue = new Queue(this.queueName, {
|
|
75
|
+
connection,
|
|
76
|
+
defaultJobOptions: {
|
|
77
|
+
attempts,
|
|
78
|
+
backoff: { type: "exponential", delay: backoffDelay },
|
|
79
|
+
removeOnComplete: { count: 500 },
|
|
80
|
+
removeOnFail: { count: 200 }
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
this.worker = new Worker(
|
|
84
|
+
this.queueName,
|
|
85
|
+
async (job) => {
|
|
86
|
+
await executor(job.data.taskId);
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
connection,
|
|
90
|
+
concurrency: config.concurrency ?? 5
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
this.worker.on("failed", (job, err) => {
|
|
94
|
+
console.warn(`[TaskQueue] Job ${job?.id ?? "?"} (task ${job?.data.taskId ?? "?"}) failed: ${err.message}`);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Enqueue a task for execution.
|
|
99
|
+
* Uses taskId as jobId — duplicate enqueues are silently ignored.
|
|
100
|
+
*/
|
|
101
|
+
async enqueue(taskId, priority) {
|
|
102
|
+
await this.queue.add(
|
|
103
|
+
"execute",
|
|
104
|
+
{ taskId },
|
|
105
|
+
{
|
|
106
|
+
jobId: taskId,
|
|
107
|
+
priority: PRIORITY_MAP[priority]
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Queue statistics — useful for health checks and dashboards.
|
|
113
|
+
*/
|
|
114
|
+
async getStats() {
|
|
115
|
+
const [waiting, active, completed, failed, delayed] = await Promise.all([
|
|
116
|
+
this.queue.getWaitingCount(),
|
|
117
|
+
this.queue.getActiveCount(),
|
|
118
|
+
this.queue.getCompletedCount(),
|
|
119
|
+
this.queue.getFailedCount(),
|
|
120
|
+
this.queue.getDelayedCount()
|
|
121
|
+
]);
|
|
122
|
+
return { waiting, active, completed, failed, delayed };
|
|
123
|
+
}
|
|
124
|
+
/** Gracefully close queue and worker connections. */
|
|
125
|
+
async close() {
|
|
126
|
+
await this.worker.close();
|
|
127
|
+
await this.queue.close();
|
|
128
|
+
}
|
|
129
|
+
// ─── Internal ──────────────────────────────────────────────────────────────
|
|
130
|
+
parseRedisUrl(url) {
|
|
131
|
+
try {
|
|
132
|
+
const u = new URL(url);
|
|
133
|
+
const config = {
|
|
134
|
+
host: u.hostname || "localhost",
|
|
135
|
+
port: u.port ? parseInt(u.port, 10) : 6379,
|
|
136
|
+
username: u.username || void 0,
|
|
137
|
+
password: u.password || void 0
|
|
138
|
+
};
|
|
139
|
+
if (u.pathname && u.pathname !== "/") {
|
|
140
|
+
config["db"] = parseInt(u.pathname.slice(1), 10);
|
|
141
|
+
}
|
|
142
|
+
return config;
|
|
143
|
+
} catch {
|
|
144
|
+
return { host: "localhost", port: 6379 };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// src/core/orchestrator.ts
|
|
150
|
+
var DEFAULT_CONFIG = {
|
|
151
|
+
maxAutonomousComplexity: "complex",
|
|
152
|
+
selfVerificationRequired: true,
|
|
153
|
+
supervisorReviewThreshold: 0.7,
|
|
154
|
+
qualityReviewSampleRate: 0.1,
|
|
155
|
+
defaultTrustScore: 70,
|
|
156
|
+
defaultMaxAttempts: 3,
|
|
157
|
+
maxTokensPerTask: 1e5,
|
|
158
|
+
maxConcurrentAgents: 10,
|
|
159
|
+
trustGrowthRate: 0.02,
|
|
160
|
+
trustDecayRate: 0.05,
|
|
161
|
+
trustMinimum: 30
|
|
162
|
+
};
|
|
163
|
+
var COMPLEXITY_ORDER = ["trivial", "simple", "moderate", "complex", "epic"];
|
|
164
|
+
function complexityToNumber(c) {
|
|
165
|
+
return COMPLEXITY_ORDER.indexOf(c);
|
|
166
|
+
}
|
|
167
|
+
var Orchestrator = class extends EventEmitter {
|
|
168
|
+
config;
|
|
169
|
+
tasks;
|
|
170
|
+
agents;
|
|
171
|
+
disabledWorkflows;
|
|
172
|
+
checkpoints;
|
|
173
|
+
reviews;
|
|
174
|
+
plans;
|
|
175
|
+
workflows;
|
|
176
|
+
auditByTask;
|
|
177
|
+
auditGlobal;
|
|
178
|
+
taskQueue;
|
|
179
|
+
auditLog;
|
|
180
|
+
constructor(config = {}) {
|
|
181
|
+
super();
|
|
182
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
183
|
+
this.tasks = /* @__PURE__ */ new Map();
|
|
184
|
+
this.agents = /* @__PURE__ */ new Map();
|
|
185
|
+
this.checkpoints = /* @__PURE__ */ new Map();
|
|
186
|
+
this.reviews = /* @__PURE__ */ new Map();
|
|
187
|
+
this.plans = /* @__PURE__ */ new Map();
|
|
188
|
+
this.workflows = /* @__PURE__ */ new Map();
|
|
189
|
+
this.auditByTask = /* @__PURE__ */ new Map();
|
|
190
|
+
this.auditGlobal = [];
|
|
191
|
+
this.disabledWorkflows = new Set(this.config.disabledWorkflows ?? []);
|
|
192
|
+
if (this.config.taskQueueConfig) {
|
|
193
|
+
this.taskQueue = new TaskQueue(
|
|
194
|
+
async (taskId) => {
|
|
195
|
+
await this.dispatchExecution(taskId);
|
|
196
|
+
},
|
|
197
|
+
this.config.taskQueueConfig
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (this.config.auditLog) {
|
|
201
|
+
this.auditLog = this.config.auditLog;
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(this.config.workflows)) {
|
|
204
|
+
for (const workflow of this.config.workflows) {
|
|
205
|
+
this.registerWorkflow(workflow);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
this.attachAuditListeners();
|
|
209
|
+
}
|
|
210
|
+
// ─── Task Management ─────────────────────────────────────────────────────────
|
|
211
|
+
async createTask(input) {
|
|
212
|
+
const validated = TaskInputSchema.parse(input);
|
|
213
|
+
const task = {
|
|
214
|
+
id: randomUUID(),
|
|
215
|
+
title: validated.title,
|
|
216
|
+
description: validated.description,
|
|
217
|
+
status: "pending",
|
|
218
|
+
priority: validated.priority,
|
|
219
|
+
complexity: this.assessComplexity(validated),
|
|
220
|
+
subtasks: [],
|
|
221
|
+
dependencies: [],
|
|
222
|
+
requiredCapabilities: validated.requiredCapabilities,
|
|
223
|
+
attempts: 0,
|
|
224
|
+
maxAttempts: this.config.defaultMaxAttempts,
|
|
225
|
+
acceptanceCriteria: validated.acceptanceCriteria.map((ac, i) => ({
|
|
226
|
+
id: `ac-${i}`,
|
|
227
|
+
description: ac.description,
|
|
228
|
+
type: ac.type,
|
|
229
|
+
weight: ac.weight
|
|
230
|
+
})),
|
|
231
|
+
verificationResults: [],
|
|
232
|
+
createdAt: Date.now(),
|
|
233
|
+
updatedAt: Date.now(),
|
|
234
|
+
context: validated.context,
|
|
235
|
+
artifacts: []
|
|
236
|
+
};
|
|
237
|
+
this.tasks.set(task.id, task);
|
|
238
|
+
this.emit("task:created", { type: "task:created", task });
|
|
239
|
+
if (this.requiresHumanApproval(task)) {
|
|
240
|
+
task.status = "blocked";
|
|
241
|
+
task.needsHumanReview = true;
|
|
242
|
+
return task;
|
|
243
|
+
}
|
|
244
|
+
if (this.shouldDecompose(task)) {
|
|
245
|
+
await this.decomposeTask(task);
|
|
246
|
+
}
|
|
247
|
+
return task;
|
|
248
|
+
}
|
|
249
|
+
async executeTask(taskId) {
|
|
250
|
+
const task = this.tasks.get(taskId);
|
|
251
|
+
if (!task) {
|
|
252
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
253
|
+
}
|
|
254
|
+
if (task.status === "blocked" && task.needsHumanReview) {
|
|
255
|
+
throw new Error(`Task ${taskId} requires human approval before execution`);
|
|
256
|
+
}
|
|
257
|
+
if (this.config.policyGate) {
|
|
258
|
+
const bestAgent = this.selectBestAgent(task);
|
|
259
|
+
const agentId = bestAgent?.id ?? "system";
|
|
260
|
+
const trustScore = bestAgent?.trustScore ?? this.config.defaultTrustScore;
|
|
261
|
+
const gateResult = await this.config.policyGate.preExecution(task, agentId, trustScore);
|
|
262
|
+
if (gateResult.blocked) {
|
|
263
|
+
task.status = "blocked";
|
|
264
|
+
task.needsHumanReview = true;
|
|
265
|
+
task.updatedAt = Date.now();
|
|
266
|
+
const violation = gateResult.evaluations.find((e) => !e.allowed);
|
|
267
|
+
const reason = violation?.reason ?? "Policy gate denied execution";
|
|
268
|
+
const policy = violation?.policy ?? "unknown";
|
|
269
|
+
if (gateResult.approvalToken) {
|
|
270
|
+
task.context = {
|
|
271
|
+
...task.context,
|
|
272
|
+
approvalToken: gateResult.approvalToken,
|
|
273
|
+
approvalTokenExpires: gateResult.approvalTokenExpires
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
this.emit("task:blocked", { type: "task:blocked", task, reason, policy });
|
|
277
|
+
this.emit("escalation", { type: "escalation", task, reason, escalatedTo: "human" });
|
|
278
|
+
metrics.getMeter("baselineos").createCounter("baseline_escalation_total").add(1, {
|
|
279
|
+
reason: reason ?? "unknown",
|
|
280
|
+
agent_id: task.assignedAgent ?? "unknown"
|
|
281
|
+
});
|
|
282
|
+
return task;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (this.taskQueue) {
|
|
286
|
+
await this.taskQueue.enqueue(taskId, task.priority);
|
|
287
|
+
return task;
|
|
288
|
+
}
|
|
289
|
+
return this.dispatchExecution(taskId);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Core execution body — called by the BullMQ worker (or directly when no queue).
|
|
293
|
+
* Runs the full supervision loop: execute → self-verify → review → correct.
|
|
294
|
+
* Not intended to be called externally; exposed for the queue worker closure.
|
|
295
|
+
*/
|
|
296
|
+
async dispatchExecution(taskId) {
|
|
297
|
+
const task = this.tasks.get(taskId);
|
|
298
|
+
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
299
|
+
task.status = "executing";
|
|
300
|
+
task.attempts++;
|
|
301
|
+
this.setFinalApprovalGate(task, "pending");
|
|
302
|
+
this.emit("task:started", { type: "task:started", task });
|
|
303
|
+
if (this.config.personaEngine) {
|
|
304
|
+
const userId = task.context?.userId ?? "system";
|
|
305
|
+
const activePersona = this.config.personaEngine.getActivePersona(userId);
|
|
306
|
+
if (activePersona) {
|
|
307
|
+
task.context = { ...task.context, persona: { id: activePersona.id, name: activePersona.name } };
|
|
308
|
+
}
|
|
309
|
+
this.config.personaEngine.learnFromBehavior(userId, {
|
|
310
|
+
type: "task_execution",
|
|
311
|
+
context: { taskId: task.id, complexity: task.complexity, priority: task.priority },
|
|
312
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (this.config.layerSweep) {
|
|
316
|
+
const bestAgent = this.selectBestAgent(task);
|
|
317
|
+
const sweepResult = this.config.layerSweep.sweep({
|
|
318
|
+
taskId: task.id,
|
|
319
|
+
title: task.title,
|
|
320
|
+
description: task.description,
|
|
321
|
+
complexity: task.complexity,
|
|
322
|
+
priority: task.priority,
|
|
323
|
+
agent: bestAgent ? { id: bestAgent.id, role: bestAgent.role, trustScore: bestAgent.trustScore } : void 0
|
|
324
|
+
});
|
|
325
|
+
task.context = { ...task.context, layerSweep: sweepResult };
|
|
326
|
+
if (!sweepResult.overallPassed) {
|
|
327
|
+
const failedLayers = sweepResult.layerResults.filter((r) => !r.passed).map((r) => `${r.layerId}: ${r.checks.filter((c) => !c.passed).map((c) => c.detail).join("; ")}`).join(" | ");
|
|
328
|
+
task.status = "blocked";
|
|
329
|
+
task.needsHumanReview = true;
|
|
330
|
+
task.updatedAt = Date.now();
|
|
331
|
+
this.emit("task:blocked", { type: "task:blocked", task, reason: `Layer sweep failed: ${failedLayers}`, policy: "layer-sweep" });
|
|
332
|
+
this.emit("escalation", { type: "escalation", task, reason: `Governance layers failed: ${failedLayers}`, escalatedTo: "human" });
|
|
333
|
+
return task;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
await withSpan("task.execute", {
|
|
337
|
+
"task.id": task.id,
|
|
338
|
+
"task.priority": task.priority,
|
|
339
|
+
"task.complexity": task.complexity,
|
|
340
|
+
"task.attempt": task.attempts
|
|
341
|
+
}, async (span) => {
|
|
342
|
+
try {
|
|
343
|
+
await this.executeWithSupervision(task);
|
|
344
|
+
const finalApprovalGate = this.getFinalApprovalGate(task);
|
|
345
|
+
if (task.needsHumanReview || finalApprovalGate !== "approved") {
|
|
346
|
+
const reason = task.context.finalGateReason ?? "Final approval gate unresolved";
|
|
347
|
+
task.status = "blocked";
|
|
348
|
+
task.needsHumanReview = true;
|
|
349
|
+
this.setFinalApprovalGate(task, "blocked", reason);
|
|
350
|
+
span.setAttribute("task.outcome", "blocked");
|
|
351
|
+
this.emit("escalation", { type: "escalation", task, reason, escalatedTo: "human" });
|
|
352
|
+
metrics.getMeter("baselineos").createCounter("baseline_escalation_total").add(1, {
|
|
353
|
+
reason: reason ?? "unknown",
|
|
354
|
+
agent_id: task.assignedAgent ?? "unknown"
|
|
355
|
+
});
|
|
356
|
+
} else {
|
|
357
|
+
task.status = "completed";
|
|
358
|
+
task.completedAt = Date.now();
|
|
359
|
+
span.setAttribute("task.outcome", "completed");
|
|
360
|
+
this.emit("task:completed", { type: "task:completed", task });
|
|
361
|
+
}
|
|
362
|
+
if (task.assignedAgent) {
|
|
363
|
+
this.updateAgentTrust(task.assignedAgent, true);
|
|
364
|
+
}
|
|
365
|
+
} catch (error) {
|
|
366
|
+
task.status = "failed";
|
|
367
|
+
this.setFinalApprovalGate(task, "blocked", "Execution failed");
|
|
368
|
+
span.setAttribute("task.outcome", "failed");
|
|
369
|
+
this.emit("task:failed", { type: "task:failed", task, error });
|
|
370
|
+
if (task.assignedAgent) {
|
|
371
|
+
this.updateAgentTrust(task.assignedAgent, false);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
task.updatedAt = Date.now();
|
|
376
|
+
return task;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Execute task with autonomous supervision loop.
|
|
380
|
+
* This is where the AMP/Ralph magic happens.
|
|
381
|
+
*/
|
|
382
|
+
async executeWithSupervision(task, startTime = Date.now()) {
|
|
383
|
+
const budget = this.getWorkflowBudget(task);
|
|
384
|
+
await this.performExecution(task, budget);
|
|
385
|
+
if (this.config.selfVerificationRequired) {
|
|
386
|
+
const selfVerifyResult = await this.selfVerify(task);
|
|
387
|
+
if (!selfVerifyResult.passed) {
|
|
388
|
+
if (task.attempts < task.maxAttempts) {
|
|
389
|
+
task.status = "correcting";
|
|
390
|
+
this.emit("task:correction-needed", {
|
|
391
|
+
type: "task:correction-needed",
|
|
392
|
+
task,
|
|
393
|
+
feedback: selfVerifyResult.details
|
|
394
|
+
});
|
|
395
|
+
task.context = {
|
|
396
|
+
...task.context,
|
|
397
|
+
correctionFeedback: selfVerifyResult.details,
|
|
398
|
+
previousAttempt: task.attempts
|
|
399
|
+
};
|
|
400
|
+
task.attempts += 1;
|
|
401
|
+
if (budget?.maxAttempts && task.attempts > budget.maxAttempts) {
|
|
402
|
+
task.needsHumanReview = true;
|
|
403
|
+
this.setFinalApprovalGate(task, "blocked", "Workflow budget max attempts exceeded");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (budget?.maxDurationMs && Date.now() - startTime > budget.maxDurationMs) {
|
|
407
|
+
task.needsHumanReview = true;
|
|
408
|
+
this.setFinalApprovalGate(task, "blocked", "Workflow budget max duration exceeded");
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
return this.executeWithSupervision(task, startTime);
|
|
412
|
+
}
|
|
413
|
+
task.needsHumanReview = true;
|
|
414
|
+
this.setFinalApprovalGate(task, "blocked", "Self-verification failed and max attempts exhausted");
|
|
415
|
+
task.context = {
|
|
416
|
+
...task.context,
|
|
417
|
+
escalationReason: "Self-verification failed and max attempts exhausted",
|
|
418
|
+
lastVerificationFailure: selfVerifyResult.details
|
|
419
|
+
};
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const shouldReview = this.shouldTriggerSupervisorReview(task);
|
|
424
|
+
if (shouldReview) {
|
|
425
|
+
task.status = "reviewing";
|
|
426
|
+
const supervisor = this.findSupervisor(task);
|
|
427
|
+
if (supervisor) {
|
|
428
|
+
this.emit("task:review-requested", {
|
|
429
|
+
type: "task:review-requested",
|
|
430
|
+
task,
|
|
431
|
+
reviewer: supervisor
|
|
432
|
+
});
|
|
433
|
+
const review = await this.conductReview(task, supervisor);
|
|
434
|
+
this.reviews.set(review.id, review);
|
|
435
|
+
this.emit("task:reviewed", { type: "task:reviewed", task, review });
|
|
436
|
+
if (review.overallAssessment === "needs-work") {
|
|
437
|
+
if (task.attempts < task.maxAttempts) {
|
|
438
|
+
task.status = "correcting";
|
|
439
|
+
task.context = {
|
|
440
|
+
...task.context,
|
|
441
|
+
reviewFeedback: review.feedback,
|
|
442
|
+
requiredChanges: review.requiredChanges
|
|
443
|
+
};
|
|
444
|
+
this.emit("task:correction-needed", {
|
|
445
|
+
type: "task:correction-needed",
|
|
446
|
+
task,
|
|
447
|
+
feedback: review.feedback
|
|
448
|
+
});
|
|
449
|
+
task.attempts += 1;
|
|
450
|
+
if (budget?.maxAttempts && task.attempts > budget.maxAttempts) {
|
|
451
|
+
task.needsHumanReview = true;
|
|
452
|
+
this.setFinalApprovalGate(task, "blocked", "Workflow budget max attempts exceeded");
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (budget?.maxDurationMs && Date.now() - startTime > budget.maxDurationMs) {
|
|
456
|
+
task.needsHumanReview = true;
|
|
457
|
+
this.setFinalApprovalGate(task, "blocked", "Workflow budget max duration exceeded");
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
return this.executeWithSupervision(task, startTime);
|
|
461
|
+
}
|
|
462
|
+
task.needsHumanReview = true;
|
|
463
|
+
this.setFinalApprovalGate(task, "blocked", "Supervisor requested changes and max attempts exhausted");
|
|
464
|
+
task.context = {
|
|
465
|
+
...task.context,
|
|
466
|
+
escalationReason: "Supervisor requested changes and max attempts exhausted",
|
|
467
|
+
lastReviewFeedback: review.feedback
|
|
468
|
+
};
|
|
469
|
+
return;
|
|
470
|
+
} else if (review.overallAssessment === "rejected") {
|
|
471
|
+
const qualityAgent = this.findQualityAgent(task);
|
|
472
|
+
if (qualityAgent) {
|
|
473
|
+
const qualityReview = await this.conductReview(task, qualityAgent);
|
|
474
|
+
if (qualityReview.overallAssessment !== "approved") {
|
|
475
|
+
task.needsHumanReview = true;
|
|
476
|
+
this.setFinalApprovalGate(task, "blocked", "Quality review failed after supervisor rejection");
|
|
477
|
+
this.emit("escalation", {
|
|
478
|
+
type: "escalation",
|
|
479
|
+
task,
|
|
480
|
+
reason: "Quality review failed after supervisor rejection",
|
|
481
|
+
escalatedTo: "human"
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
task.needsHumanReview = true;
|
|
486
|
+
this.setFinalApprovalGate(task, "blocked", "Quality agent unavailable after supervisor rejection");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (Math.random() < this.config.qualityReviewSampleRate) {
|
|
492
|
+
const qualityAgent = this.findQualityAgent(task);
|
|
493
|
+
if (qualityAgent) {
|
|
494
|
+
const qualityReview = await this.conductReview(task, qualityAgent);
|
|
495
|
+
this.reviews.set(qualityReview.id, qualityReview);
|
|
496
|
+
this.emit("task:reviewed", { type: "task:reviewed", task, review: qualityReview });
|
|
497
|
+
if (qualityReview.overallAssessment !== "approved") {
|
|
498
|
+
task.needsHumanReview = true;
|
|
499
|
+
this.setFinalApprovalGate(task, "blocked", "Quality sampling review failed");
|
|
500
|
+
task.context = {
|
|
501
|
+
...task.context,
|
|
502
|
+
escalationReason: "Quality sampling review failed",
|
|
503
|
+
qualityReviewFeedback: qualityReview.feedback
|
|
504
|
+
};
|
|
505
|
+
this.emit("escalation", {
|
|
506
|
+
type: "escalation",
|
|
507
|
+
task,
|
|
508
|
+
reason: "Quality sampling review failed",
|
|
509
|
+
escalatedTo: "human"
|
|
510
|
+
});
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
this.setFinalApprovalGate(task, "approved");
|
|
516
|
+
}
|
|
517
|
+
async performExecution(task, budget) {
|
|
518
|
+
return withSpan("task.perform", {
|
|
519
|
+
"task.id": task.id,
|
|
520
|
+
"task.workflow_id": task.workflowId ?? ""
|
|
521
|
+
}, async () => {
|
|
522
|
+
return this._performExecution(task, budget);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
async _performExecution(task, budget) {
|
|
526
|
+
if (this.config.piiDetector) {
|
|
527
|
+
try {
|
|
528
|
+
const safeDescription = this.config.piiDetector.processText(task.description, task.id);
|
|
529
|
+
const safeTitle = this.config.piiDetector.processText(task.title, task.id);
|
|
530
|
+
if (safeDescription !== task.description || safeTitle !== task.title) {
|
|
531
|
+
task.context = {
|
|
532
|
+
...task.context,
|
|
533
|
+
originalDescription: task.description,
|
|
534
|
+
originalTitle: task.title,
|
|
535
|
+
piiRedacted: true
|
|
536
|
+
};
|
|
537
|
+
task.description = safeDescription;
|
|
538
|
+
task.title = safeTitle;
|
|
539
|
+
}
|
|
540
|
+
} catch (err) {
|
|
541
|
+
if (err instanceof PiiBlockedError) {
|
|
542
|
+
task.status = "failed";
|
|
543
|
+
task.context = { ...task.context, piiBlocked: true, piiTypes: err.types };
|
|
544
|
+
task.updatedAt = Date.now();
|
|
545
|
+
throw err;
|
|
546
|
+
}
|
|
547
|
+
throw err;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const checkpoint = {
|
|
551
|
+
id: randomUUID(),
|
|
552
|
+
taskId: task.id,
|
|
553
|
+
state: { ...task.context },
|
|
554
|
+
artifacts: [...task.artifacts],
|
|
555
|
+
decisions: [],
|
|
556
|
+
createdBy: task.assignedAgent || "orchestrator",
|
|
557
|
+
createdAt: Date.now(),
|
|
558
|
+
description: `Before attempt ${task.attempts}`,
|
|
559
|
+
recoverable: true
|
|
560
|
+
};
|
|
561
|
+
this.checkpoints.set(checkpoint.id, checkpoint);
|
|
562
|
+
task.currentCheckpoint = checkpoint.id;
|
|
563
|
+
this.emit("task:checkpoint", { type: "task:checkpoint", task, checkpoint });
|
|
564
|
+
this.persistCheckpoint(checkpoint);
|
|
565
|
+
const engine = this.config.engine;
|
|
566
|
+
const plan = task.workflowPlanId ? this.plans.get(task.workflowPlanId) : void 0;
|
|
567
|
+
const steps = plan?.steps ?? [];
|
|
568
|
+
const maxSteps = budget?.maxSteps;
|
|
569
|
+
if (steps.length > 0) {
|
|
570
|
+
let stepCount = 0;
|
|
571
|
+
for (const step of steps) {
|
|
572
|
+
if (maxSteps && stepCount >= maxSteps) {
|
|
573
|
+
task.needsHumanReview = true;
|
|
574
|
+
this.setFinalApprovalGate(task, "blocked", "Workflow budget max steps exceeded");
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
step.status = "executing";
|
|
578
|
+
if (engine) {
|
|
579
|
+
const result = await engine.execute(task, step);
|
|
580
|
+
step.status = "completed";
|
|
581
|
+
step.result = {
|
|
582
|
+
success: result.compliance.passed,
|
|
583
|
+
output: result.output,
|
|
584
|
+
artifacts: result.artifacts,
|
|
585
|
+
tokensUsed: result.tokensUsed,
|
|
586
|
+
duration: result.duration
|
|
587
|
+
};
|
|
588
|
+
task.artifacts.push(...result.artifacts);
|
|
589
|
+
task.actualTokens = (task.actualTokens ?? 0) + result.tokensUsed;
|
|
590
|
+
task.context = {
|
|
591
|
+
...task.context,
|
|
592
|
+
lastOutput: result.output,
|
|
593
|
+
lastModel: result.model,
|
|
594
|
+
compliancePassed: result.compliance.passed,
|
|
595
|
+
complianceScore: result.compliance.score
|
|
596
|
+
};
|
|
597
|
+
if (!result.compliance.passed) {
|
|
598
|
+
task.context = {
|
|
599
|
+
...task.context,
|
|
600
|
+
complianceViolations: result.compliance.violations
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
605
|
+
step.status = "completed";
|
|
606
|
+
step.result = {
|
|
607
|
+
success: true,
|
|
608
|
+
output: { message: `Executed step: ${step.title}` },
|
|
609
|
+
artifacts: [],
|
|
610
|
+
tokensUsed: 0,
|
|
611
|
+
duration: 150
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
stepCount += 1;
|
|
615
|
+
}
|
|
616
|
+
} else {
|
|
617
|
+
if (engine) {
|
|
618
|
+
const result = await engine.execute(task);
|
|
619
|
+
task.artifacts.push(...result.artifacts);
|
|
620
|
+
task.actualTokens = (task.actualTokens ?? 0) + result.tokensUsed;
|
|
621
|
+
task.context = {
|
|
622
|
+
...task.context,
|
|
623
|
+
lastOutput: result.output,
|
|
624
|
+
lastModel: result.model,
|
|
625
|
+
compliancePassed: result.compliance.passed,
|
|
626
|
+
complianceScore: result.compliance.score
|
|
627
|
+
};
|
|
628
|
+
if (!result.compliance.passed) {
|
|
629
|
+
task.context = {
|
|
630
|
+
...task.context,
|
|
631
|
+
complianceViolations: result.compliance.violations
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// ─── Workflow Registry ─────────────────────────────────────────────────────
|
|
640
|
+
registerWorkflow(definition) {
|
|
641
|
+
if (!definition.id) throw new Error("Workflow must have an id");
|
|
642
|
+
this.workflows.set(definition.id, definition);
|
|
643
|
+
}
|
|
644
|
+
listWorkflows() {
|
|
645
|
+
return Array.from(this.workflows.values());
|
|
646
|
+
}
|
|
647
|
+
getWorkflow(id) {
|
|
648
|
+
return this.workflows.get(id);
|
|
649
|
+
}
|
|
650
|
+
/** Disable a workflow type (kill switch). Persists for the lifetime of this Orchestrator instance. */
|
|
651
|
+
disableWorkflow(workflowId) {
|
|
652
|
+
this.disabledWorkflows.add(workflowId);
|
|
653
|
+
this.config.bus?.broadcast("system", "system:workflow-disabled", { workflowId }, "high");
|
|
654
|
+
}
|
|
655
|
+
/** Re-enable a previously disabled workflow. */
|
|
656
|
+
enableWorkflow(workflowId) {
|
|
657
|
+
this.disabledWorkflows.delete(workflowId);
|
|
658
|
+
this.config.bus?.broadcast("system", "system:workflow-enabled", { workflowId }, "normal");
|
|
659
|
+
}
|
|
660
|
+
/** Returns the set of currently disabled workflow IDs. */
|
|
661
|
+
getDisabledWorkflows() {
|
|
662
|
+
return Array.from(this.disabledWorkflows);
|
|
663
|
+
}
|
|
664
|
+
async createTaskFromWorkflow(workflowId, input = {}) {
|
|
665
|
+
if (this.disabledWorkflows.has(workflowId)) {
|
|
666
|
+
throw new Error(`Workflow '${workflowId}' is disabled (kill switch active). Enable it via orchestrator.enableWorkflow('${workflowId}') before creating tasks.`);
|
|
667
|
+
}
|
|
668
|
+
const workflow = this.getWorkflow(workflowId);
|
|
669
|
+
if (!workflow) {
|
|
670
|
+
throw new Error(`Workflow not found: ${workflowId}`);
|
|
671
|
+
}
|
|
672
|
+
const task = await this.createTask({
|
|
673
|
+
title: input.title ?? workflow.name,
|
|
674
|
+
description: input.description ?? workflow.description ?? workflow.name,
|
|
675
|
+
priority: input.priority ?? "medium",
|
|
676
|
+
requiredCapabilities: input.requiredCapabilities ?? workflow.requiredCapabilities ?? [],
|
|
677
|
+
acceptanceCriteria: input.acceptanceCriteria ?? [],
|
|
678
|
+
context: {
|
|
679
|
+
...input.context ?? {},
|
|
680
|
+
workflowId: workflow.id
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
task.workflowId = workflow.id;
|
|
684
|
+
const plan = this.createExecutionPlanForWorkflow(task, workflow);
|
|
685
|
+
task.workflowPlanId = plan.id;
|
|
686
|
+
this.plans.set(plan.id, plan);
|
|
687
|
+
if (workflow.budget?.maxAttempts) {
|
|
688
|
+
task.maxAttempts = workflow.budget.maxAttempts;
|
|
689
|
+
}
|
|
690
|
+
return task;
|
|
691
|
+
}
|
|
692
|
+
async executeWorkflow(workflowId, input = {}) {
|
|
693
|
+
const task = await this.createTaskFromWorkflow(workflowId, input);
|
|
694
|
+
return this.executeTask(task.id);
|
|
695
|
+
}
|
|
696
|
+
createExecutionPlanForWorkflow(task, workflow) {
|
|
697
|
+
const steps = workflow.steps.map((step) => ({
|
|
698
|
+
id: step.id,
|
|
699
|
+
title: step.title,
|
|
700
|
+
description: step.description,
|
|
701
|
+
action: step.action,
|
|
702
|
+
dependencies: [],
|
|
703
|
+
status: "pending",
|
|
704
|
+
verificationCriteria: step.verificationCriteria ?? []
|
|
705
|
+
}));
|
|
706
|
+
return {
|
|
707
|
+
id: `plan-${task.id}`,
|
|
708
|
+
taskId: task.id,
|
|
709
|
+
steps,
|
|
710
|
+
estimatedTokens: 0,
|
|
711
|
+
estimatedDuration: 0,
|
|
712
|
+
riskAssessment: [],
|
|
713
|
+
createdAt: Date.now()
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
getWorkflowBudget(task) {
|
|
717
|
+
if (task.workflowId) {
|
|
718
|
+
return this.workflows.get(task.workflowId)?.budget;
|
|
719
|
+
}
|
|
720
|
+
return void 0;
|
|
721
|
+
}
|
|
722
|
+
// ─── Checkpoint Persistence + Recovery ─────────────────────────────────────
|
|
723
|
+
persistCheckpoint(checkpoint) {
|
|
724
|
+
const base = this.config.checkpointsPath ?? ".baseline/checkpoints";
|
|
725
|
+
try {
|
|
726
|
+
if (!existsSync(base)) {
|
|
727
|
+
mkdirSync(base, { recursive: true });
|
|
728
|
+
}
|
|
729
|
+
const file = join(base, `${checkpoint.id}.json`);
|
|
730
|
+
writeFileSync(file, JSON.stringify(checkpoint, null, 2));
|
|
731
|
+
} catch (err) {
|
|
732
|
+
process.stderr.write(`[baseline:orchestrator] Checkpoint persist failed: ${err.message}
|
|
733
|
+
`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
loadCheckpoint(checkpointId) {
|
|
737
|
+
const base = this.config.checkpointsPath ?? ".baseline/checkpoints";
|
|
738
|
+
const file = join(base, `${checkpointId}.json`);
|
|
739
|
+
if (!existsSync(file)) return null;
|
|
740
|
+
try {
|
|
741
|
+
const raw = JSON.parse(readFileSync(file, "utf-8"));
|
|
742
|
+
return raw;
|
|
743
|
+
} catch (err) {
|
|
744
|
+
process.stderr.write(`[baseline:orchestrator] Checkpoint load failed for ${checkpointId}: ${err.message}
|
|
745
|
+
`);
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
recoverTaskFromCheckpoint(checkpointId) {
|
|
750
|
+
const checkpoint = this.loadCheckpoint(checkpointId);
|
|
751
|
+
if (!checkpoint) return null;
|
|
752
|
+
const existing = this.tasks.get(checkpoint.taskId);
|
|
753
|
+
if (existing) {
|
|
754
|
+
existing.context = { ...checkpoint.state };
|
|
755
|
+
existing.status = "pending";
|
|
756
|
+
existing.currentCheckpoint = checkpoint.id;
|
|
757
|
+
this.tasks.set(existing.id, existing);
|
|
758
|
+
return existing;
|
|
759
|
+
}
|
|
760
|
+
const recovered = {
|
|
761
|
+
id: checkpoint.taskId,
|
|
762
|
+
title: "Recovered Task",
|
|
763
|
+
description: checkpoint.description,
|
|
764
|
+
status: "pending",
|
|
765
|
+
priority: "medium",
|
|
766
|
+
complexity: "moderate",
|
|
767
|
+
subtasks: [],
|
|
768
|
+
dependencies: [],
|
|
769
|
+
requiredCapabilities: [],
|
|
770
|
+
attempts: 0,
|
|
771
|
+
maxAttempts: this.config.defaultMaxAttempts,
|
|
772
|
+
acceptanceCriteria: [],
|
|
773
|
+
verificationResults: [],
|
|
774
|
+
createdAt: Date.now(),
|
|
775
|
+
updatedAt: Date.now(),
|
|
776
|
+
context: { ...checkpoint.state },
|
|
777
|
+
artifacts: checkpoint.artifacts ?? [],
|
|
778
|
+
currentCheckpoint: checkpoint.id
|
|
779
|
+
};
|
|
780
|
+
this.tasks.set(recovered.id, recovered);
|
|
781
|
+
return recovered;
|
|
782
|
+
}
|
|
783
|
+
async selfVerify(task) {
|
|
784
|
+
return withSpan("task.self_verify", { "task.id": task.id }, async () => {
|
|
785
|
+
return this._selfVerify(task);
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
async _selfVerify(task) {
|
|
789
|
+
const engine = this.config.engine;
|
|
790
|
+
const output = task.context.lastOutput ?? "";
|
|
791
|
+
let result;
|
|
792
|
+
if (engine && output) {
|
|
793
|
+
result = await engine.selfVerify(task, output);
|
|
794
|
+
} else {
|
|
795
|
+
result = {
|
|
796
|
+
criterionId: "self-verify",
|
|
797
|
+
passed: true,
|
|
798
|
+
confidence: 0.85,
|
|
799
|
+
details: "Self-verification passed (mock)",
|
|
800
|
+
verifiedBy: task.assignedAgent || "orchestrator",
|
|
801
|
+
verifiedAt: Date.now()
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
task.verificationResults.push(result);
|
|
805
|
+
this.emit("task:verified", { type: "task:verified", task, result });
|
|
806
|
+
return result;
|
|
807
|
+
}
|
|
808
|
+
shouldTriggerSupervisorReview(task) {
|
|
809
|
+
const avgConfidence = task.verificationResults.length > 0 ? task.verificationResults.reduce((sum, r) => sum + r.confidence, 0) / task.verificationResults.length : 0.5;
|
|
810
|
+
if (avgConfidence < this.config.supervisorReviewThreshold) {
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
if (task.priority === "critical") {
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
return Math.random() < this.config.qualityReviewSampleRate;
|
|
817
|
+
}
|
|
818
|
+
async conductReview(task, reviewer) {
|
|
819
|
+
return withSpan("task.review", {
|
|
820
|
+
"task.id": task.id,
|
|
821
|
+
"reviewer.id": reviewer.id,
|
|
822
|
+
"reviewer.role": reviewer.role
|
|
823
|
+
}, async () => {
|
|
824
|
+
return this._conductReview(task, reviewer);
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
async _conductReview(task, reviewer) {
|
|
828
|
+
const engine = this.config.engine;
|
|
829
|
+
const output = task.context.lastOutput ?? "";
|
|
830
|
+
const reviewType = reviewer.role === "supervisor" ? "supervisor" : "quality";
|
|
831
|
+
if (engine && output) {
|
|
832
|
+
const review = await engine.conductReview(task, output, reviewType);
|
|
833
|
+
review.reviewerId = reviewer.id;
|
|
834
|
+
review.revieweeId = task.assignedAgent || "unknown";
|
|
835
|
+
return review;
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
id: randomUUID(),
|
|
839
|
+
taskId: task.id,
|
|
840
|
+
reviewerId: reviewer.id,
|
|
841
|
+
revieweeId: task.assignedAgent || "unknown",
|
|
842
|
+
type: reviewType,
|
|
843
|
+
scope: "full",
|
|
844
|
+
findings: [],
|
|
845
|
+
overallAssessment: "approved",
|
|
846
|
+
confidence: 0.9,
|
|
847
|
+
feedback: "Review completed successfully (mock)",
|
|
848
|
+
suggestions: [],
|
|
849
|
+
requiredChanges: [],
|
|
850
|
+
createdAt: Date.now(),
|
|
851
|
+
completedAt: Date.now(),
|
|
852
|
+
timeSpent: 100
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
findSupervisor(_task) {
|
|
856
|
+
return Array.from(this.agents.values()).find(
|
|
857
|
+
(a) => a.role === "supervisor" && a.status === "idle"
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
findQualityAgent(_task) {
|
|
861
|
+
return Array.from(this.agents.values()).find(
|
|
862
|
+
(a) => a.role === "quality" && a.status === "idle"
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
getTask(taskId) {
|
|
866
|
+
return this.tasks.get(taskId);
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Approve a blocked task, optionally recording who approved it.
|
|
870
|
+
* When BASELINE_APPROVAL_SECRET is set, approvalToken must match the
|
|
871
|
+
* HMAC token issued when the task was blocked (SIGNAL-017).
|
|
872
|
+
*/
|
|
873
|
+
async approveTask(taskId, approvedBy = "human", approvalToken) {
|
|
874
|
+
const task = this.tasks.get(taskId);
|
|
875
|
+
if (!task) {
|
|
876
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
877
|
+
}
|
|
878
|
+
if (task.status !== "blocked") {
|
|
879
|
+
throw new Error(`Task ${taskId} is not blocked (status: ${task.status})`);
|
|
880
|
+
}
|
|
881
|
+
const approvalSecret = process.env["BASELINE_APPROVAL_SECRET"];
|
|
882
|
+
if (approvalSecret) {
|
|
883
|
+
const storedToken = task.context.approvalToken;
|
|
884
|
+
const tokenExpires = task.context.approvalTokenExpires;
|
|
885
|
+
if (!approvalToken) {
|
|
886
|
+
throw new Error(`Approval token required \u2014 set BASELINE_APPROVAL_SECRET is active`);
|
|
887
|
+
}
|
|
888
|
+
if (!storedToken) {
|
|
889
|
+
throw new Error(`No approval token on file for task ${taskId}`);
|
|
890
|
+
}
|
|
891
|
+
if (tokenExpires && Date.now() > tokenExpires) {
|
|
892
|
+
throw new Error(`Approval token expired for task ${taskId}`);
|
|
893
|
+
}
|
|
894
|
+
const { timingSafeEqual } = await import('crypto');
|
|
895
|
+
const expected = Buffer.from(storedToken);
|
|
896
|
+
const received = Buffer.from(approvalToken);
|
|
897
|
+
if (expected.length !== received.length || !timingSafeEqual(expected, received)) {
|
|
898
|
+
throw new Error(`Invalid approval token for task ${taskId}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
task.needsHumanReview = false;
|
|
902
|
+
task.status = "pending";
|
|
903
|
+
task.updatedAt = Date.now();
|
|
904
|
+
task.context = {
|
|
905
|
+
...task.context,
|
|
906
|
+
approvedBy,
|
|
907
|
+
approvedAt: Date.now(),
|
|
908
|
+
finalApprovalGate: void 0,
|
|
909
|
+
approvalToken: void 0,
|
|
910
|
+
// one-time use — invalidate after approval
|
|
911
|
+
approvalTokenExpires: void 0
|
|
912
|
+
};
|
|
913
|
+
this.emit("task:approved", { type: "task:approved", task, approvedBy });
|
|
914
|
+
return this.executeTask(taskId);
|
|
915
|
+
}
|
|
916
|
+
cancelTask(taskId, reason = "Cancelled by user") {
|
|
917
|
+
const task = this.tasks.get(taskId);
|
|
918
|
+
if (!task) return false;
|
|
919
|
+
if (task.status === "completed" || task.status === "failed") {
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
task.status = "failed";
|
|
923
|
+
task.updatedAt = Date.now();
|
|
924
|
+
task.context = {
|
|
925
|
+
...task.context,
|
|
926
|
+
cancelled: true,
|
|
927
|
+
cancelReason: reason
|
|
928
|
+
};
|
|
929
|
+
this.emit("task:failed", { type: "task:failed", task, error: reason });
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
getAllTasks() {
|
|
933
|
+
return Array.from(this.tasks.values());
|
|
934
|
+
}
|
|
935
|
+
// ─── Agent Management ──────────────────────────────────────────────────────
|
|
936
|
+
registerAgent(agent) {
|
|
937
|
+
const fullAgent = {
|
|
938
|
+
...agent,
|
|
939
|
+
createdAt: Date.now(),
|
|
940
|
+
lastActiveAt: Date.now()
|
|
941
|
+
};
|
|
942
|
+
this.agents.set(fullAgent.id, fullAgent);
|
|
943
|
+
return fullAgent;
|
|
944
|
+
}
|
|
945
|
+
getAllAgents() {
|
|
946
|
+
return Array.from(this.agents.values());
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Select the best available agent for a task based on trust score and capability match.
|
|
950
|
+
* Agents are ranked by: (capability overlap × trust score). Suspended agents are excluded.
|
|
951
|
+
*/
|
|
952
|
+
selectBestAgent(task) {
|
|
953
|
+
const candidates = Array.from(this.agents.values()).filter((a) => a.status !== "suspended" && a.status !== "offline").filter((a) => {
|
|
954
|
+
if (task.requiredCapabilities.length === 0) return true;
|
|
955
|
+
return task.requiredCapabilities.some((cap) => a.capabilities.includes(cap));
|
|
956
|
+
});
|
|
957
|
+
if (candidates.length === 0) return void 0;
|
|
958
|
+
const scored = candidates.map((agent) => {
|
|
959
|
+
const capOverlap = task.requiredCapabilities.length > 0 ? task.requiredCapabilities.filter((cap) => agent.capabilities.includes(cap)).length / task.requiredCapabilities.length : 1;
|
|
960
|
+
return { agent, score: capOverlap * agent.trustScore };
|
|
961
|
+
});
|
|
962
|
+
scored.sort((a, b) => b.score - a.score);
|
|
963
|
+
return scored[0]?.agent;
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Suspend an agent. Suspended agents are excluded from task selection.
|
|
967
|
+
*/
|
|
968
|
+
suspendAgent(agentId, reason = "Trust score below minimum") {
|
|
969
|
+
const agent = this.agents.get(agentId);
|
|
970
|
+
if (!agent || agent.status === "suspended") return false;
|
|
971
|
+
agent.status = "suspended";
|
|
972
|
+
agent.lastActiveAt = Date.now();
|
|
973
|
+
this.emit("agent:suspended", {
|
|
974
|
+
type: "agent:suspended",
|
|
975
|
+
agent,
|
|
976
|
+
reason
|
|
977
|
+
});
|
|
978
|
+
return true;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Reinstate a suspended agent.
|
|
982
|
+
*/
|
|
983
|
+
reinstateAgent(agentId) {
|
|
984
|
+
const agent = this.agents.get(agentId);
|
|
985
|
+
if (!agent || agent.status !== "suspended") return false;
|
|
986
|
+
agent.status = "idle";
|
|
987
|
+
agent.lastActiveAt = Date.now();
|
|
988
|
+
this.emit("agent:reinstated", {
|
|
989
|
+
type: "agent:reinstated",
|
|
990
|
+
agent
|
|
991
|
+
});
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
updateAgentTrust(agentId, success) {
|
|
995
|
+
const agent = this.agents.get(agentId);
|
|
996
|
+
if (!agent) return;
|
|
997
|
+
const previousTrust = agent.trustScore;
|
|
998
|
+
if (success) {
|
|
999
|
+
agent.trustScore += this.config.trustGrowthRate * (100 - agent.trustScore);
|
|
1000
|
+
agent.tasksCompleted++;
|
|
1001
|
+
} else {
|
|
1002
|
+
agent.trustScore -= this.config.trustDecayRate * agent.trustScore;
|
|
1003
|
+
agent.tasksFailed++;
|
|
1004
|
+
}
|
|
1005
|
+
agent.lastActiveAt = Date.now();
|
|
1006
|
+
this.emit("agent:trust-updated", {
|
|
1007
|
+
type: "agent:trust-updated",
|
|
1008
|
+
agent,
|
|
1009
|
+
previousTrust
|
|
1010
|
+
});
|
|
1011
|
+
if (agent.trustScore < this.config.trustMinimum && agent.status !== "suspended") {
|
|
1012
|
+
this.suspendAgent(agentId, `Trust score ${agent.trustScore.toFixed(1)} below minimum ${this.config.trustMinimum}`);
|
|
1013
|
+
}
|
|
1014
|
+
if (this.config.onTrustUpdate) {
|
|
1015
|
+
this.config.onTrustUpdate(agentId, agent.trustScore, success);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// ─── Task Decomposition ────────────────────────────────────────────────────
|
|
1019
|
+
async decomposeTask(task) {
|
|
1020
|
+
task.status = "planning";
|
|
1021
|
+
const engine = this.config.engine;
|
|
1022
|
+
let phases;
|
|
1023
|
+
if (engine) {
|
|
1024
|
+
const decomposition = await engine.decompose(task);
|
|
1025
|
+
phases = decomposition.phases.length > 0 ? decomposition.phases : [
|
|
1026
|
+
{ title: "research", description: `Research: ${task.title}`, capabilities: task.requiredCapabilities },
|
|
1027
|
+
{ title: "implement", description: `Implement: ${task.title}`, capabilities: task.requiredCapabilities },
|
|
1028
|
+
{ title: "verify", description: `Verify: ${task.title}`, capabilities: task.requiredCapabilities }
|
|
1029
|
+
];
|
|
1030
|
+
} else {
|
|
1031
|
+
phases = ["research", "design", "implement", "verify", "document"].map((p) => ({
|
|
1032
|
+
title: p,
|
|
1033
|
+
description: `${p} phase for: ${task.description}`,
|
|
1034
|
+
capabilities: task.requiredCapabilities
|
|
1035
|
+
}));
|
|
1036
|
+
}
|
|
1037
|
+
for (let i = 0; i < phases.length; i++) {
|
|
1038
|
+
const phase = phases[i];
|
|
1039
|
+
const subtask = {
|
|
1040
|
+
id: randomUUID(),
|
|
1041
|
+
parentId: task.id,
|
|
1042
|
+
title: `${phase.title}: ${task.title}`,
|
|
1043
|
+
description: phase.description,
|
|
1044
|
+
status: "pending",
|
|
1045
|
+
priority: task.priority,
|
|
1046
|
+
complexity: "simple",
|
|
1047
|
+
subtasks: [],
|
|
1048
|
+
dependencies: i > 0 ? [task.subtasks[i - 1].id] : [],
|
|
1049
|
+
requiredCapabilities: phase.capabilities.length > 0 ? phase.capabilities : task.requiredCapabilities,
|
|
1050
|
+
attempts: 0,
|
|
1051
|
+
maxAttempts: this.config.defaultMaxAttempts,
|
|
1052
|
+
acceptanceCriteria: [],
|
|
1053
|
+
verificationResults: [],
|
|
1054
|
+
createdAt: Date.now(),
|
|
1055
|
+
updatedAt: Date.now(),
|
|
1056
|
+
context: { ...task.context, phase: phase.title },
|
|
1057
|
+
artifacts: []
|
|
1058
|
+
};
|
|
1059
|
+
task.subtasks.push(subtask);
|
|
1060
|
+
this.tasks.set(subtask.id, subtask);
|
|
1061
|
+
}
|
|
1062
|
+
task.status = "pending";
|
|
1063
|
+
this.emit("task:decomposed", { type: "task:decomposed", task, subtasks: task.subtasks });
|
|
1064
|
+
}
|
|
1065
|
+
// ─── Helper Methods ────────────────────────────────────────────────────────
|
|
1066
|
+
assessComplexity(input) {
|
|
1067
|
+
const descLength = input.description.length;
|
|
1068
|
+
const capCount = input.requiredCapabilities.length;
|
|
1069
|
+
const criteriaCount = input.acceptanceCriteria.length;
|
|
1070
|
+
const score = descLength / 100 + capCount * 2 + criteriaCount * 1.5;
|
|
1071
|
+
if (score < 5) return "trivial";
|
|
1072
|
+
if (score < 15) return "simple";
|
|
1073
|
+
if (score < 30) return "moderate";
|
|
1074
|
+
if (score < 50) return "complex";
|
|
1075
|
+
return "epic";
|
|
1076
|
+
}
|
|
1077
|
+
shouldDecompose(task) {
|
|
1078
|
+
return task.complexity === "complex" || task.complexity === "epic";
|
|
1079
|
+
}
|
|
1080
|
+
requiresHumanApproval(task) {
|
|
1081
|
+
const maxIndex = complexityToNumber(this.config.maxAutonomousComplexity);
|
|
1082
|
+
const taskIndex = complexityToNumber(task.complexity);
|
|
1083
|
+
return taskIndex > maxIndex;
|
|
1084
|
+
}
|
|
1085
|
+
// ─── Audit + Evidence ──────────────────────────────────────────────────────
|
|
1086
|
+
attachAuditListeners() {
|
|
1087
|
+
const events = [
|
|
1088
|
+
"task:created",
|
|
1089
|
+
"task:decomposed",
|
|
1090
|
+
"task:assigned",
|
|
1091
|
+
"task:started",
|
|
1092
|
+
"task:checkpoint",
|
|
1093
|
+
"task:verified",
|
|
1094
|
+
"task:review-requested",
|
|
1095
|
+
"task:reviewed",
|
|
1096
|
+
"task:correction-needed",
|
|
1097
|
+
"task:corrected",
|
|
1098
|
+
"task:completed",
|
|
1099
|
+
"task:failed",
|
|
1100
|
+
"task:blocked",
|
|
1101
|
+
"task:approved",
|
|
1102
|
+
"agent:status-changed",
|
|
1103
|
+
"agent:trust-updated",
|
|
1104
|
+
"escalation"
|
|
1105
|
+
];
|
|
1106
|
+
for (const type of events) {
|
|
1107
|
+
this.on(type, (event) => {
|
|
1108
|
+
this.recordAudit(event);
|
|
1109
|
+
this.publishToBus(event);
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
publishToBus(event) {
|
|
1114
|
+
const bus = this.config.bus;
|
|
1115
|
+
if (!bus) return;
|
|
1116
|
+
const category = event.type.startsWith("task:") ? "task" : event.type.startsWith("agent:") ? "agent" : "system";
|
|
1117
|
+
const agentId = "agent" in event ? event.agent.id : "task" in event && event.task.assignedAgent ? event.task.assignedAgent : void 0;
|
|
1118
|
+
bus.publish({
|
|
1119
|
+
from: agentId ?? "system",
|
|
1120
|
+
topic: event.type,
|
|
1121
|
+
category,
|
|
1122
|
+
payload: event,
|
|
1123
|
+
priority: event.type === "escalation" || event.type === "task:failed" ? "high" : "normal"
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
recordAudit(event) {
|
|
1127
|
+
const timestamp = Date.now();
|
|
1128
|
+
this.auditGlobal.push({ timestamp, event });
|
|
1129
|
+
const taskId = this.getEventTaskId(event);
|
|
1130
|
+
if (taskId) {
|
|
1131
|
+
const list = this.auditByTask.get(taskId) ?? [];
|
|
1132
|
+
list.push({ timestamp, event });
|
|
1133
|
+
this.auditByTask.set(taskId, list);
|
|
1134
|
+
}
|
|
1135
|
+
this.auditLog?.append(event);
|
|
1136
|
+
}
|
|
1137
|
+
getEventTaskId(event) {
|
|
1138
|
+
if ("task" in event) {
|
|
1139
|
+
return event.task.id;
|
|
1140
|
+
}
|
|
1141
|
+
return void 0;
|
|
1142
|
+
}
|
|
1143
|
+
getAuditTrail(taskId) {
|
|
1144
|
+
if (taskId) {
|
|
1145
|
+
return this.auditByTask.get(taskId) ?? [];
|
|
1146
|
+
}
|
|
1147
|
+
return this.auditGlobal;
|
|
1148
|
+
}
|
|
1149
|
+
getEvidenceBundle(taskId) {
|
|
1150
|
+
const task = this.tasks.get(taskId);
|
|
1151
|
+
const workflow = task?.workflowId ? this.workflows.get(task.workflowId) : void 0;
|
|
1152
|
+
const plan = task?.workflowPlanId ? this.plans.get(task.workflowPlanId) : void 0;
|
|
1153
|
+
const checkpoints = Array.from(this.checkpoints.values()).filter((c) => c.taskId === taskId);
|
|
1154
|
+
const reviews = this.getReviews(taskId);
|
|
1155
|
+
const verificationResults = task?.verificationResults ?? [];
|
|
1156
|
+
const artifacts = task?.artifacts ?? [];
|
|
1157
|
+
const audit = this.getAuditTrail(taskId);
|
|
1158
|
+
return {
|
|
1159
|
+
task,
|
|
1160
|
+
workflow,
|
|
1161
|
+
plan,
|
|
1162
|
+
checkpoints,
|
|
1163
|
+
reviews,
|
|
1164
|
+
verificationResults,
|
|
1165
|
+
artifacts,
|
|
1166
|
+
audit
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
// ─── Statistics ────────────────────────────────────────────────────────────
|
|
1170
|
+
getStats() {
|
|
1171
|
+
const tasksByStatus = {};
|
|
1172
|
+
for (const task of this.tasks.values()) {
|
|
1173
|
+
tasksByStatus[task.status] = (tasksByStatus[task.status] || 0) + 1;
|
|
1174
|
+
}
|
|
1175
|
+
const agentsByRole = {};
|
|
1176
|
+
for (const agent of this.agents.values()) {
|
|
1177
|
+
agentsByRole[agent.role] = (agentsByRole[agent.role] || 0) + 1;
|
|
1178
|
+
}
|
|
1179
|
+
return {
|
|
1180
|
+
tasks: { total: this.tasks.size, byStatus: tasksByStatus },
|
|
1181
|
+
agents: { total: this.agents.size, byRole: agentsByRole }
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
// ─── Review Access ─────────────────────────────────────────────────────────
|
|
1185
|
+
getReviews(taskId) {
|
|
1186
|
+
const reviews = Array.from(this.reviews.values());
|
|
1187
|
+
if (taskId) {
|
|
1188
|
+
return reviews.filter((r) => r.taskId === taskId);
|
|
1189
|
+
}
|
|
1190
|
+
return reviews;
|
|
1191
|
+
}
|
|
1192
|
+
setFinalApprovalGate(task, gate, reason) {
|
|
1193
|
+
task.context = {
|
|
1194
|
+
...task.context,
|
|
1195
|
+
finalApprovalGate: gate,
|
|
1196
|
+
...reason ? { finalGateReason: reason } : {}
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
getFinalApprovalGate(task) {
|
|
1200
|
+
const gate = task.context.finalApprovalGate;
|
|
1201
|
+
if (gate === "pending" || gate === "approved" || gate === "blocked") {
|
|
1202
|
+
return gate;
|
|
1203
|
+
}
|
|
1204
|
+
return void 0;
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
/**
|
|
1208
|
+
* BaselineOS OpenTelemetry — SIGNAL-012
|
|
1209
|
+
*
|
|
1210
|
+
* Distributed tracing for the multi-agent orchestration layer.
|
|
1211
|
+
* Initializes the OTel Node SDK and exports a shared tracer.
|
|
1212
|
+
*
|
|
1213
|
+
* Spans emitted:
|
|
1214
|
+
* task.execute root span per executeTask() call
|
|
1215
|
+
* task.policy.pre OPA pre-execution gate
|
|
1216
|
+
* task.policy.post OPA post-execution gate
|
|
1217
|
+
* task.perform performExecution() step loop
|
|
1218
|
+
* task.self_verify selfVerify() LLM call
|
|
1219
|
+
* task.review conductReview() LLM call
|
|
1220
|
+
*
|
|
1221
|
+
* Trace context is propagated through AgentBus messages via the
|
|
1222
|
+
* optional `traceContext` field (W3C traceparent format).
|
|
1223
|
+
*
|
|
1224
|
+
* Configuration:
|
|
1225
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT OTLP HTTP collector (default: http://localhost:4318)
|
|
1226
|
+
* OTEL_SERVICE_NAME service name override (default: baselineos)
|
|
1227
|
+
* BASELINE_OTEL_DISABLED set to '1' to disable entirely (e.g. in tests)
|
|
1228
|
+
*
|
|
1229
|
+
* @license Apache-2.0
|
|
1230
|
+
*/
|
|
1231
|
+
/**
|
|
1232
|
+
* BaselineOS Core Types
|
|
1233
|
+
*
|
|
1234
|
+
* @license Apache-2.0
|
|
1235
|
+
*/
|
|
1236
|
+
/**
|
|
1237
|
+
* PII Detector — SIGNAL-015
|
|
1238
|
+
*
|
|
1239
|
+
* Scans prompt text for personally identifiable information before it
|
|
1240
|
+
* reaches the LLM router. Supports two enforcement modes:
|
|
1241
|
+
*
|
|
1242
|
+
* redact — replaces detected values with [REDACTED:TYPE] (default)
|
|
1243
|
+
* block — throws PiiBlockedError, halting task execution
|
|
1244
|
+
*
|
|
1245
|
+
* Detected types:
|
|
1246
|
+
* email — RFC 5321 address pattern
|
|
1247
|
+
* phone — US/international format (10-digit minimum)
|
|
1248
|
+
* ssn — US Social Security Number (NNN-NN-NNNN)
|
|
1249
|
+
* credit-card — Visa, Mastercard, Amex, Discover
|
|
1250
|
+
* api-key — OpenAI sk-, Anthropic sk-ant-, GitHub ghp_, AWS AKIA
|
|
1251
|
+
* ip-address — IPv4 addresses
|
|
1252
|
+
*
|
|
1253
|
+
* Integration:
|
|
1254
|
+
* Called by Orchestrator._performExecution() before engine.execute().
|
|
1255
|
+
* Publishes 'governance:pii-detected' on the AgentBus when PII is found.
|
|
1256
|
+
* Sets pii.detected and pii.types on the active OTel span.
|
|
1257
|
+
*
|
|
1258
|
+
* @license Apache-2.0
|
|
1259
|
+
*/
|
|
1260
|
+
/**
|
|
1261
|
+
* Task Queue — SIGNAL-018
|
|
1262
|
+
*
|
|
1263
|
+
* BullMQ-backed durable task queue. Provides:
|
|
1264
|
+
* - Priority-ordered execution (critical → high → medium → low)
|
|
1265
|
+
* - Automatic retry with exponential backoff on failure
|
|
1266
|
+
* - Concurrency limiting (configurable, default 5)
|
|
1267
|
+
* - Job deduplication by task ID
|
|
1268
|
+
* - Queue observability via Bull Dashboard / BullMQ Board
|
|
1269
|
+
*
|
|
1270
|
+
* The queue is optional. When not configured, Orchestrator falls back
|
|
1271
|
+
* to direct in-process execution (existing behaviour).
|
|
1272
|
+
*
|
|
1273
|
+
* Configuration:
|
|
1274
|
+
* BASELINE_REDIS_URL Redis connection URL (default: redis://localhost:6379)
|
|
1275
|
+
*
|
|
1276
|
+
* Self-hosted Redis:
|
|
1277
|
+
* docker compose -f docker/docker-compose.monitoring.yml up -d
|
|
1278
|
+
* → redis://localhost:6379
|
|
1279
|
+
*
|
|
1280
|
+
* @license Apache-2.0
|
|
1281
|
+
*/
|
|
1282
|
+
/**
|
|
1283
|
+
* BaselineOS Orchestrator
|
|
1284
|
+
*
|
|
1285
|
+
* Autonomous multi-agent task execution with self-supervision.
|
|
1286
|
+
* Implements AMP/Ralph patterns for agent-to-agent oversight.
|
|
1287
|
+
*
|
|
1288
|
+
* Key Innovation: Tasks are decomposed, verified, reviewed, and corrected
|
|
1289
|
+
* by agents — not humans. Human intervention only when all autonomous
|
|
1290
|
+
* options are exhausted (<5% of tasks).
|
|
1291
|
+
*
|
|
1292
|
+
* @license Apache-2.0
|
|
1293
|
+
*/
|
|
1294
|
+
|
|
1295
|
+
export { Orchestrator };
|
|
1296
|
+
//# sourceMappingURL=orchestrator.js.map
|
|
1297
|
+
//# sourceMappingURL=orchestrator.js.map
|