@williambeto/ai-workflow 2.3.0 → 2.3.2

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.
Files changed (55) hide show
  1. package/AGENTS.md +27 -0
  2. package/CHANGELOG.md +30 -0
  3. package/README.md +14 -10
  4. package/dist-assets/agents/astra.md +74 -45
  5. package/dist-assets/agents/atlas.md +110 -152
  6. package/dist-assets/agents/nexus.md +64 -19
  7. package/dist-assets/agents/orion.md +72 -27
  8. package/dist-assets/agents/phoenix.md +64 -19
  9. package/dist-assets/agents/sage.md +67 -36
  10. package/dist-assets/commands/atlas.md +66 -6
  11. package/dist-assets/commands/audit.md +62 -4
  12. package/dist-assets/commands/deploy.md +66 -5
  13. package/dist-assets/commands/discover.md +72 -4
  14. package/dist-assets/commands/implement.md +66 -18
  15. package/dist-assets/commands/optimize-tokens.md +60 -4
  16. package/dist-assets/commands/plan.md +64 -4
  17. package/dist-assets/commands/release.md +76 -5
  18. package/dist-assets/commands/run.md +62 -16
  19. package/dist-assets/commands/spec-create.md +66 -4
  20. package/dist-assets/commands/spec-implement.md +68 -4
  21. package/dist-assets/commands/spec-review.md +63 -4
  22. package/dist-assets/commands/update-memory.md +62 -4
  23. package/dist-assets/commands/validate.md +70 -6
  24. package/dist-assets/skills/architecture/SKILL.md +62 -7
  25. package/dist-assets/skills/backend-development/SKILL.md +62 -7
  26. package/dist-assets/skills/deployment/SKILL.md +62 -7
  27. package/dist-assets/skills/design-principles/SKILL.md +59 -7
  28. package/dist-assets/skills/documentation/SKILL.md +61 -7
  29. package/dist-assets/skills/frontend-development/SKILL.md +62 -7
  30. package/dist-assets/skills/full-stack-development/SKILL.md +62 -7
  31. package/dist-assets/skills/optimize-tokens/SKILL.md +61 -7
  32. package/dist-assets/skills/pr-workflow/SKILL.md +65 -7
  33. package/dist-assets/skills/product-discovery/SKILL.md +81 -7
  34. package/dist-assets/skills/product-planning/SKILL.md +62 -7
  35. package/dist-assets/skills/project-memory/SKILL.md +55 -22
  36. package/dist-assets/skills/prompt-engineer/SKILL.md +59 -7
  37. package/dist-assets/skills/qa-workflow/SKILL.md +72 -7
  38. package/dist-assets/skills/refactoring/SKILL.md +68 -7
  39. package/dist-assets/skills/release-workflow/SKILL.md +72 -7
  40. package/dist-assets/skills/spec-driven-development/SKILL.md +75 -7
  41. package/dist-assets/skills/technical-leadership/SKILL.md +61 -7
  42. package/dist-assets/skills/ui-ux-design/SKILL.md +60 -7
  43. package/docs/compatibility/provider-usage.md +46 -0
  44. package/docs/compatibility/runtime-matrix.md +31 -0
  45. package/docs/getting-started/DESKTOP_PROMPT.md +52 -0
  46. package/docs/getting-started/quickstart.md +17 -0
  47. package/docs/getting-started/upgrading-to-v2.md +62 -0
  48. package/package.json +10 -3
  49. package/src/commands/execute.js +10 -1
  50. package/src/core/delegation-controller.js +72 -13
  51. package/src/core/execution-planner.js +2 -3
  52. package/src/core/request-classifier.js +15 -5
  53. package/src/core/request-router.js +289 -0
  54. package/src/core/runtime/opencode-adapter.js +38 -5
  55. package/docs/releases/v2.3.0-release-decision.md +0 -35
@@ -125,12 +125,21 @@ export async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverri
125
125
 
126
126
  // 1. Classification
127
127
  const classifier = new RequestClassifier();
128
- const classification = classifier.classify(naturalRequest);
128
+ const classification = classifier.classify(naturalRequest, { cwd });
129
129
  console.log(`[CLASSIFIED] Intent: ${classification.intent}, Mode: ${classification.mode}, Profile: ${classification.profile}`);
130
130
  stateMachine.transitionTo("CLASSIFIED");
131
131
 
132
132
  await delegationController.route(naturalRequest, classification);
133
133
 
134
+ const routingDecision = classification.routingDecision || {};
135
+ if (routingDecision.permissionDecision === "blocked") {
136
+ stateMachine.transitionTo("BLOCKED");
137
+ throw new Error(`[ROUTE BLOCKED] ${routingDecision.reason}`);
138
+ }
139
+ if (routingDecision.permissionDecision === "rerouted") {
140
+ console.log(`[REROUTED] ${routingDecision.reason}`);
141
+ }
142
+
134
143
  // 2. Planning
135
144
  const planner = new ExecutionPlanner({ cwd });
136
145
  plan = planner.plan(classification, taskSlug);
@@ -14,6 +14,9 @@ export class DelegationController {
14
14
  * Logs routing decision by Atlas.
15
15
  */
16
16
  async route(naturalRequest, classification) {
17
+ const decision = classification.routingDecision || {};
18
+ this.lastRoutingDecision = decision;
19
+
17
20
  if (this.ledger) {
18
21
  this.ledger.logEvent({
19
22
  actor: "Atlas",
@@ -23,8 +26,19 @@ export class DelegationController {
23
26
  provenance: "request-classifier",
24
27
  data: {
25
28
  request: naturalRequest,
29
+ requestedActor: decision.requestedActor || null,
30
+ selectedActor: decision.selectedActor || null,
31
+ operationType: decision.operationType || null,
32
+ mutationIntent: decision.mutationIntent || null,
33
+ permissionDecision: decision.permissionDecision || null,
34
+ reason: decision.reason || null,
35
+ workflowPath: decision.workflowPath || [],
26
36
  classification,
27
- event: "routing.completed"
37
+ event: "routing.completed",
38
+ runtimeAgentRequested: null,
39
+ runtimeAgentApplied: null,
40
+ runtimeAgentSupported: false,
41
+ status: decision.permissionDecision === "blocked" ? "BLOCKED" : "COMPLETED"
28
42
  }
29
43
  });
30
44
  }
@@ -34,10 +48,16 @@ export class DelegationController {
34
48
  * Logs and executes the implementation process by Astra.
35
49
  */
36
50
  async implement(promptMsg, owner, options = {}) {
51
+ const decision = this.lastRoutingDecision || {};
52
+ const inspection = await this.adapter.inspect();
53
+ const isSpecialized = ["Nexus", "Orion", "Astra", "Sage", "Phoenix"].includes(owner);
54
+ const supportsAgent = inspection.supports?.agent;
55
+ const canApply = !isSpecialized || supportsAgent;
56
+
37
57
  if (this.ledger) {
38
58
  this.ledger.logEvent({
39
- actor: owner,
40
- actorType: "runtime-agent",
59
+ actor: canApply ? owner : "Atlas",
60
+ actorType: canApply ? "runtime-agent" : "control-plane",
41
61
  observed: true,
42
62
  runtime: "opencode",
43
63
  eventType: "implementation_start",
@@ -45,7 +65,15 @@ export class DelegationController {
45
65
  data: {
46
66
  agent: owner,
47
67
  prompt: promptMsg,
48
- event: "implementation.started"
68
+ event: "implementation.started",
69
+ requestedActor: decision.requestedActor || null,
70
+ selectedActor: decision.selectedActor || null,
71
+ runtimeAgentRequested: owner,
72
+ runtimeAgentApplied: null,
73
+ runtimeAgentSupported: supportsAgent || false,
74
+ permissionDecision: decision.permissionDecision || null,
75
+ reason: decision.reason || null,
76
+ status: "started"
49
77
  }
50
78
  });
51
79
  }
@@ -53,9 +81,10 @@ export class DelegationController {
53
81
  const runResult = await this.adapter.execute(promptMsg, { agent: owner, ...options });
54
82
 
55
83
  if (this.ledger) {
84
+ const isApplied = runResult.runtimeAgentApplied === owner;
56
85
  this.ledger.logEvent({
57
- actor: owner,
58
- actorType: "runtime-agent",
86
+ actor: isApplied ? owner : "Atlas",
87
+ actorType: isApplied ? "runtime-agent" : "control-plane",
59
88
  observed: true,
60
89
  runtime: "opencode",
61
90
  eventType: "implementation_complete",
@@ -65,7 +94,15 @@ export class DelegationController {
65
94
  commandsRun: runResult.commandsRun || [],
66
95
  eventCount: runResult.eventCount || 0,
67
96
  error: runResult.error,
68
- event: "implementation.completed"
97
+ event: "implementation.completed",
98
+ requestedActor: decision.requestedActor || null,
99
+ selectedActor: decision.selectedActor || null,
100
+ runtimeAgentRequested: runResult.runtimeAgentRequested || owner || null,
101
+ runtimeAgentApplied: runResult.runtimeAgentApplied || null,
102
+ runtimeAgentSupported: runResult.runtimeAgentSupported !== undefined ? runResult.runtimeAgentSupported : (supportsAgent || false),
103
+ permissionDecision: decision.permissionDecision || null,
104
+ reason: runResult.error || decision.reason || null,
105
+ status: runResult.status || (runResult.success ? "COMPLETED" : "FAILED")
69
106
  }
70
107
  });
71
108
  }
@@ -80,17 +117,30 @@ export class DelegationController {
80
117
  const result = await verifyFn();
81
118
 
82
119
  if (mode === "full") {
120
+ const decision = this.lastRoutingDecision || {};
121
+ const inspection = await this.adapter.inspect();
122
+ const supportsAgent = inspection.supports?.agent;
123
+ const canApply = supportsAgent;
124
+
83
125
  if (this.ledger) {
84
126
  this.ledger.logEvent({
85
- actor: "Sage",
86
- actorType: "auditor",
127
+ actor: canApply ? "Sage" : "Atlas",
128
+ actorType: canApply ? "auditor" : "control-plane",
87
129
  observed: true,
88
130
  runtime: "opencode",
89
131
  eventType: "validation_start",
90
132
  provenance: "opencode-adapter",
91
133
  data: {
92
134
  agent: "Sage",
93
- event: "validation.started"
135
+ event: "validation.started",
136
+ requestedActor: decision.requestedActor || null,
137
+ selectedActor: decision.selectedActor || null,
138
+ runtimeAgentRequested: "Sage",
139
+ runtimeAgentApplied: null,
140
+ runtimeAgentSupported: supportsAgent || false,
141
+ permissionDecision: decision.permissionDecision || null,
142
+ reason: decision.reason || null,
143
+ status: "started"
94
144
  }
95
145
  });
96
146
  }
@@ -107,9 +157,10 @@ export class DelegationController {
107
157
  const runResult = await this.adapter.execute(prompt, { agent: "Sage" });
108
158
 
109
159
  if (this.ledger) {
160
+ const isApplied = runResult.runtimeAgentApplied === "Sage";
110
161
  this.ledger.logEvent({
111
- actor: "Sage",
112
- actorType: "auditor",
162
+ actor: isApplied ? "Sage" : "Atlas",
163
+ actorType: isApplied ? "auditor" : "control-plane",
113
164
  observed: true,
114
165
  runtime: "opencode",
115
166
  eventType: "validation_complete",
@@ -119,7 +170,15 @@ export class DelegationController {
119
170
  commandsRun: runResult.commandsRun || [],
120
171
  eventCount: runResult.eventCount || 0,
121
172
  error: runResult.error,
122
- event: "validation.completed"
173
+ event: "validation.completed",
174
+ requestedActor: decision.requestedActor || null,
175
+ selectedActor: decision.selectedActor || null,
176
+ runtimeAgentRequested: runResult.runtimeAgentRequested || "Sage",
177
+ runtimeAgentApplied: runResult.runtimeAgentApplied || null,
178
+ runtimeAgentSupported: runResult.runtimeAgentSupported !== undefined ? runResult.runtimeAgentSupported : (supportsAgent || false),
179
+ permissionDecision: decision.permissionDecision || null,
180
+ reason: runResult.error || decision.reason || null,
181
+ status: runResult.status || (runResult.success ? "COMPLETED" : "FAILED")
123
182
  }
124
183
  });
125
184
  }
@@ -21,9 +21,8 @@ export class ExecutionPlanner {
21
21
  ? path.join("docs/workflows", taskSlug, "spec.md")
22
22
  : null;
23
23
 
24
- const owner = classification.intent === "write"
25
- ? "Astra"
26
- : classification.owner;
24
+ const owner = classification.routingDecision?.selectedActor ||
25
+ (classification.intent === "write" ? "Astra" : classification.owner);
27
26
 
28
27
  // Build default expected validations based on the resolved profile
29
28
  const validationsExpected = [];
@@ -1,4 +1,5 @@
1
1
  import { resolveWorkflowProfile, getWorkflowProfile } from "./workflow-profiles.js";
2
+ import { RequestRouter } from "./request-router.js";
2
3
 
3
4
  /**
4
5
  * RequestClassifier - Classifies natural requests into structured, testable contracts.
@@ -9,12 +10,14 @@ export class RequestClassifier {
9
10
  * @param {string} request - The natural language request string.
10
11
  * @returns {Object} Structured classification contract.
11
12
  */
12
- classify(request = "") {
13
+ classify(request = "", options = {}) {
13
14
  const text = String(request).trim();
14
15
  if (!text) {
15
16
  throw new Error("Cannot classify an empty request.");
16
17
  }
17
18
 
19
+ const cwd = options.cwd || process.cwd();
20
+
18
21
  // 1. Resolve Profile
19
22
  const profile = resolveWorkflowProfile({ request: text });
20
23
  const profileDef = getWorkflowProfile(profile);
@@ -43,16 +46,23 @@ export class RequestClassifier {
43
46
  const specNeeded = mode === "full" && intent === "write";
44
47
  const validationNeeded = intent === "write";
45
48
 
49
+ // Integrate central RequestRouter
50
+ const router = new RequestRouter({ cwd });
51
+ const requestUnderstanding = router.understand(text);
52
+ const routingDecision = router.route(requestUnderstanding);
53
+
46
54
  return {
47
55
  request: text,
48
56
  profile,
49
- owner: profileDef.owner,
50
- intent,
57
+ owner: routingDecision.selectedActor || profileDef.owner,
58
+ intent: requestUnderstanding.mutationIntent === "readonly" ? "read-only" : "write",
51
59
  mode,
52
- risk,
60
+ risk: requestUnderstanding.riskLevel,
53
61
  specNeeded,
54
62
  validationNeeded,
55
- skills: [...profileDef.skills]
63
+ skills: [...profileDef.skills],
64
+ requestUnderstanding,
65
+ routingDecision
56
66
  };
57
67
  }
58
68
  }
@@ -0,0 +1,289 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+
4
+ export class RequestRouter {
5
+ constructor({ cwd = process.cwd() } = {}) {
6
+ this.cwd = cwd;
7
+ this.validActors = ["Atlas", "Nexus", "Orion", "Astra", "Sage", "Phoenix"];
8
+ }
9
+
10
+ /**
11
+ * Understands and classifies a natural language request.
12
+ * @param {string} request
13
+ * @returns {Object} RequestUnderstanding
14
+ */
15
+ understand(request) {
16
+ const text = String(request).trim();
17
+ if (!text) {
18
+ throw new Error("Cannot classify an empty request.");
19
+ }
20
+
21
+ // 1. Extract requested actor
22
+ let requestedActor = undefined;
23
+ let invalidActor = undefined;
24
+
25
+ // Direct addressing/reference pattern e.g., "Nexus, discover...", "ask Sage to..."
26
+ const askMatch = text.match(/\b(?:ask|use|tell|run|agent|actor)\s+([a-zA-Z]+)\b/i);
27
+ const addressMatch = text.match(/\b([a-zA-Z]+)\s*(?:,)/);
28
+
29
+ let candidate = null;
30
+ if (askMatch) {
31
+ candidate = askMatch[1];
32
+ } else if (addressMatch) {
33
+ candidate = addressMatch[1];
34
+ } else {
35
+ // General match for known actors
36
+ const knownMatch = text.match(/\b(Atlas|Nexus|Orion|Astra|Sage|Phoenix)\b/i);
37
+ if (knownMatch) {
38
+ candidate = knownMatch[1];
39
+ }
40
+ }
41
+
42
+ if (candidate) {
43
+ const normalized = candidate.charAt(0).toUpperCase() + candidate.slice(1).toLowerCase();
44
+ if (this.validActors.includes(normalized)) {
45
+ requestedActor = normalized;
46
+ } else {
47
+ invalidActor = candidate;
48
+ }
49
+ }
50
+
51
+ // 2. Extract operationType and requestedCapability
52
+ let operationType = "explain"; // default
53
+ let requestedCapability = undefined;
54
+
55
+ const capabilityMappings = [
56
+ { op: "discover", pattern: /\b(discover|inspect|map|scan|structure|architecture|repo|search|find)\b/i },
57
+ { op: "validate", pattern: /\b(validate|audit|review|check|verify|test)\b/i },
58
+ { op: "implement", pattern: /\b(implement|build|create|add|develop|write|change|modify)\b/i },
59
+ { op: "fix", pattern: /\b(fix|repair|correct|heal|solve|remediate)\b/i },
60
+ { op: "release", pattern: /\b(release|tag|prepare release)\b/i },
61
+ { op: "deploy", pattern: /\b(deploy|publish|push)\b/i }
62
+ ];
63
+
64
+ for (const mapping of capabilityMappings) {
65
+ const match = text.match(mapping.pattern);
66
+ if (match) {
67
+ operationType = mapping.op;
68
+ requestedCapability = match[1];
69
+ break;
70
+ }
71
+ }
72
+
73
+ // 3. Determine mutationIntent
74
+ const writePatterns = /\b(implement|build|fix|write|change|modify|create|add|refactor|update)\b/i;
75
+ const readOnlyPatterns = /\b(analyze|check|inspect|review|find|search|show|list|read|view|verify|validate|discover|map)\b/i;
76
+
77
+ let mutationIntent = "write"; // default
78
+ if (/\b(read-only|readonly)\b/i.test(text)) {
79
+ mutationIntent = "readonly";
80
+ } else if (writePatterns.test(text)) {
81
+ mutationIntent = "write";
82
+ } else if (readOnlyPatterns.test(text) || ["discover", "validate"].includes(operationType)) {
83
+ mutationIntent = "readonly";
84
+ }
85
+
86
+ if (/\b(publish|release|deploy|push to npm|push to remote)\b/i.test(text)) {
87
+ mutationIntent = "publish";
88
+ }
89
+
90
+ // Determine Mode (needed for riskLevel mapping)
91
+ let mode = "standard";
92
+ if (/\b(deep|architectural|migration|major|full|\[deep\])\b/i.test(text)) {
93
+ mode = "full";
94
+ } else if (/\b(tiny|small|quick|simple|only\s+update|typo|comment|\[tiny\])\b/i.test(text)) {
95
+ mode = "quick";
96
+ }
97
+
98
+ // 4. Determine riskLevel
99
+ let riskLevel = "medium";
100
+ if (mode === "full" || mutationIntent === "publish" || /\b(bypass|force|direct)\b/i.test(text)) {
101
+ riskLevel = "high";
102
+ } else if (mode === "quick" || mutationIntent === "readonly") {
103
+ riskLevel = "low";
104
+ }
105
+
106
+ // 5. safetyConstraints and requiredEvidence
107
+ const safetyConstraints = [];
108
+ const requiredEvidence = [];
109
+
110
+ if (mutationIntent === "publish") {
111
+ safetyConstraints.push("Require release gate", "Awaiting user authorization");
112
+ requiredEvidence.push("release-decision", "tarball-hash");
113
+ } else if (mutationIntent === "write") {
114
+ safetyConstraints.push("Require branch gate", "No direct commits to main");
115
+ requiredEvidence.push("tests", "commit-hash");
116
+ } else {
117
+ safetyConstraints.push("No workspace mutations allowed");
118
+ }
119
+
120
+ if (/\b(bypass|force|direct)\b/i.test(text)) {
121
+ safetyConstraints.push("Block bypass attempts");
122
+ }
123
+
124
+ return {
125
+ rawRequest: text,
126
+ taskGoal: `Perform ${operationType} operation under ${mutationIntent} intent`,
127
+ requestedActor,
128
+ invalidActor,
129
+ requestedCapability,
130
+ operationType,
131
+ mutationIntent,
132
+ riskLevel,
133
+ requiredEvidence,
134
+ safetyConstraints
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Decides the routing decision.
140
+ * @param {Object} understanding - RequestUnderstanding
141
+ * @returns {Object} RoutingDecision
142
+ */
143
+ route(understanding) {
144
+ const { requestedActor, invalidActor, operationType, mutationIntent, rawRequest } = understanding;
145
+
146
+ // 1. Check for bypass safety attempts
147
+ if (/\b(bypass safety|bypass validation|force push|directly to main|push to remote without gate)\b/i.test(rawRequest)) {
148
+ return {
149
+ requestedActor,
150
+ selectedActor: "Atlas",
151
+ operationType,
152
+ mutationIntent,
153
+ permissionDecision: "blocked",
154
+ reason: "Security block: Attempt to bypass safety or push directly is forbidden.",
155
+ workflowPath: ["Atlas"]
156
+ };
157
+ }
158
+
159
+ // 2. Check for invalid/ambiguous actor names in critical actions
160
+ if (invalidActor) {
161
+ if (["release", "deploy", "fix", "implement"].includes(operationType) || mutationIntent === "publish") {
162
+ return {
163
+ requestedActor: undefined,
164
+ selectedActor: "Atlas",
165
+ operationType,
166
+ mutationIntent,
167
+ permissionDecision: "blocked",
168
+ reason: `Security block: Request mentions invalid/unrecognized actor '${invalidActor}' for a critical operation.`,
169
+ workflowPath: ["Atlas"]
170
+ };
171
+ }
172
+ }
173
+
174
+ // 3. Permission matrix check
175
+ const PERMISSION_MATRIX = {
176
+ Atlas: {
177
+ allowedOperations: ["explain"],
178
+ allowedMutations: ["readonly"]
179
+ },
180
+ Nexus: {
181
+ allowedOperations: ["discover", "explain", "plan"],
182
+ allowedMutations: ["readonly", "write"]
183
+ },
184
+ Orion: {
185
+ allowedOperations: ["plan", "release", "deploy"],
186
+ allowedMutations: ["readonly", "write", "publish"]
187
+ },
188
+ Astra: {
189
+ allowedOperations: ["implement", "fix", "explain"],
190
+ allowedMutations: ["readonly", "write"]
191
+ },
192
+ Sage: {
193
+ allowedOperations: ["validate", "explain"],
194
+ allowedMutations: ["readonly"]
195
+ },
196
+ Phoenix: {
197
+ allowedOperations: ["fix"],
198
+ allowedMutations: ["readonly", "write"]
199
+ }
200
+ };
201
+
202
+ let selectedActor = undefined;
203
+ let permissionDecision = "allowed";
204
+ let reason = "Routed successfully.";
205
+ let workflowPath = ["Atlas"];
206
+
207
+ if (requestedActor) {
208
+ const perms = PERMISSION_MATRIX[requestedActor];
209
+ const isOpAllowed = perms.allowedOperations.includes(operationType);
210
+ const isMutationAllowed = perms.allowedMutations.includes(mutationIntent);
211
+
212
+ if (isOpAllowed && isMutationAllowed) {
213
+ selectedActor = requestedActor;
214
+ } else {
215
+ // Valid actor but unauthorized operation or mutation
216
+ if (mutationIntent === "publish") {
217
+ selectedActor = "Orion";
218
+ permissionDecision = "rerouted";
219
+ reason = `Rerouted: Requested actor '${requestedActor}' is not authorized to publish. Redirecting to Orion.`;
220
+ } else {
221
+ selectedActor = requestedActor;
222
+ permissionDecision = "blocked";
223
+ reason = `Blocked: Requested actor '${requestedActor}' does not have permission for operation '${operationType}' or mutation '${mutationIntent}'.`;
224
+ }
225
+ }
226
+ } else {
227
+ // Infer actor from operation, risk, capability
228
+ if (operationType === "discover") {
229
+ selectedActor = "Nexus";
230
+ } else if (operationType === "validate") {
231
+ selectedActor = "Sage";
232
+ } else if (operationType === "fix") {
233
+ selectedActor = "Phoenix";
234
+ } else if (operationType === "implement") {
235
+ selectedActor = "Astra";
236
+ } else if (operationType === "release" || operationType === "deploy") {
237
+ selectedActor = "Orion";
238
+ } else {
239
+ // Fallback for general explain/plan
240
+ selectedActor = mutationIntent === "write" ? "Astra" : "Atlas";
241
+ }
242
+ }
243
+
244
+ // Phoenix findings check: A fix request requires findings before Phoenix acts
245
+ if (selectedActor === "Phoenix") {
246
+ const requestPath = path.join(this.cwd, ".ai-workflow/remediation-request.json");
247
+ let hasFindings = false;
248
+ if (fs.existsSync(requestPath)) {
249
+ try {
250
+ const reqData = JSON.parse(fs.readFileSync(requestPath, "utf8"));
251
+ if (reqData.affectedChecks && reqData.affectedChecks.length > 0) {
252
+ hasFindings = true;
253
+ }
254
+ } catch {
255
+ // ignore
256
+ }
257
+ }
258
+ if (!hasFindings) {
259
+ permissionDecision = "blocked";
260
+ reason = "Blocked: Phoenix acts only with concrete findings and bounded remediation.";
261
+ }
262
+ }
263
+
264
+ if (selectedActor && !workflowPath.includes(selectedActor)) {
265
+ workflowPath.push(selectedActor);
266
+ }
267
+
268
+ // Atlas self-execution guard
269
+ if (selectedActor === "Atlas" && (requestedActor || ["discover", "validate", "fix", "release", "deploy"].includes(operationType))) {
270
+ permissionDecision = "blocked";
271
+ reason = "Atlas self-execution guard: Atlas cannot silently execute specialized actor or capability requests.";
272
+ }
273
+
274
+ // Sage and Phoenix revalidation loop path in full mode:
275
+ if (selectedActor === "Astra" && understanding.riskLevel === "high") {
276
+ workflowPath.push("Sage", "Phoenix", "Sage");
277
+ }
278
+
279
+ return {
280
+ requestedActor,
281
+ selectedActor,
282
+ operationType,
283
+ mutationIntent,
284
+ permissionDecision,
285
+ reason,
286
+ workflowPath
287
+ };
288
+ }
289
+ }
@@ -51,12 +51,33 @@ export class OpenCodeAdapter {
51
51
  */
52
52
  async execute(message, { agent = null, model = null, readOnly = false } = {}) {
53
53
  const inspection = await this.inspect();
54
+ const isSpecializedAgent = agent && ["Nexus", "Orion", "Astra", "Sage", "Phoenix"].includes(agent);
55
+
54
56
  if (!inspection.available) {
55
57
  return {
56
58
  success: false,
59
+ status: isSpecializedAgent ? "BLOCKED" : "FAILED",
57
60
  commandsRun: [],
58
61
  eventCount: 0,
59
- error: "OpenCode CLI is not installed or not available in the system PATH."
62
+ error: "OpenCode CLI is not installed or not available in the system PATH.",
63
+ runtimeAgentRequested: agent || null,
64
+ runtimeAgentApplied: null,
65
+ runtimeAgentSupported: false,
66
+ runtimeAgentSelectionMode: isSpecializedAgent ? "explicit" : "default"
67
+ };
68
+ }
69
+
70
+ if (isSpecializedAgent && !inspection.supports.agent) {
71
+ return {
72
+ success: false,
73
+ status: "BLOCKED",
74
+ error: `Requested runtime actor '${agent}' cannot be applied because this OpenCode runtime does not support --agent.`,
75
+ runtimeAgentRequested: agent,
76
+ runtimeAgentApplied: null,
77
+ runtimeAgentSupported: false,
78
+ runtimeAgentSelectionMode: "unsupported",
79
+ commandsRun: [],
80
+ eventCount: 0
60
81
  };
61
82
  }
62
83
 
@@ -66,7 +87,7 @@ export class OpenCodeAdapter {
66
87
  if (inspection.supports.formatJson) {
67
88
  args.push("--format", "json");
68
89
  }
69
- if (agent && inspection.supports.agent) {
90
+ if (isSpecializedAgent && inspection.supports.agent) {
70
91
  args.push("--agent", agent);
71
92
  }
72
93
  if (model && inspection.supports.model) {
@@ -125,7 +146,11 @@ export class OpenCodeAdapter {
125
146
  success: false,
126
147
  commandsRun,
127
148
  eventCount,
128
- error: `Read-only confinement violation: tool '${name}' or command attempted write/modify operation during read-only task.`
149
+ error: `Read-only confinement violation: tool '${name}' or command attempted write/modify operation during read-only task.`,
150
+ runtimeAgentRequested: agent || null,
151
+ runtimeAgentApplied: null,
152
+ runtimeAgentSupported: inspection.supports.agent,
153
+ runtimeAgentSelectionMode: isSpecializedAgent ? "explicit" : "default"
129
154
  });
130
155
  return;
131
156
  }
@@ -143,7 +168,11 @@ export class OpenCodeAdapter {
143
168
  resolve({
144
169
  success: code === 0,
145
170
  commandsRun,
146
- eventCount
171
+ eventCount,
172
+ runtimeAgentRequested: agent || null,
173
+ runtimeAgentApplied: isSpecializedAgent ? agent : null,
174
+ runtimeAgentSupported: inspection.supports.agent,
175
+ runtimeAgentSelectionMode: isSpecializedAgent ? "explicit" : "default"
147
176
  });
148
177
  });
149
178
 
@@ -153,7 +182,11 @@ export class OpenCodeAdapter {
153
182
  success: false,
154
183
  commandsRun,
155
184
  eventCount: 0,
156
- error: err.message
185
+ error: err.message,
186
+ runtimeAgentRequested: agent || null,
187
+ runtimeAgentApplied: null,
188
+ runtimeAgentSupported: inspection.supports.agent,
189
+ runtimeAgentSelectionMode: isSpecializedAgent ? "explicit" : "default"
157
190
  });
158
191
  });
159
192
  });
@@ -1,35 +0,0 @@
1
- # AI Workflow Kit 2.3.0 Release Decision
2
-
3
- **Status:** Finalized
4
- **npm publish:** Pending (Awaiting user authorization)
5
- **Published dist-tag:** `latest`
6
-
7
- ## Identity
8
-
9
- - **Package:** `@williambeto/ai-workflow`
10
- - **Version:** `2.3.0`
11
- - **Commit:** `c16d06889ed4957840b69d61df43063908c3b220`
12
- - **Tarball SHA256:** `24b1cb412bb4503add8d8962568cdc5ea819e207e6dd0d0e3d0f9d8502c063ce`
13
- - **Previous public line:** `2.2.7`
14
-
15
- ## Validation evidence
16
-
17
- - **Unit tests:** PASS — 142 tests passed
18
- - **E2E tests:** PASS — 18 tests passed (including the new read-only safety validation and Sage/Phoenix/Astra loop verification)
19
- - **Release-readiness validation:** PASS — 17 required files checked
20
- - **Full validation:** PASS — 18/18 checks completed successfully (JSON, Cross-reference, Artifact safety, External link, Documentation consistency, Workflow state, Schema, Skill, Canonical policy reference, Privacy publication gate, UI evidence gate, Delegation, Behavioral contract, Token economy, Upgrade regression, Visual regression, Release readiness, Repository structure)
21
- - **Safe pack:** PASS — `@williambeto/ai-workflow@2.3.0`, 310 files, zero root-level tarballs left behind
22
- - **Fresh consumer install:** PASS (verified via packed consumer runner in a sandbox)
23
- - **CLI help/init/doctor:** PASS (verified via packed consumer runner in a sandbox)
24
- - **Real OpenCode Smoke Test:** PASS (integrated directly into the release verification)
25
- - **Open Blocker/High findings:** None
26
-
27
- ## Accepted limitations
28
-
29
- - See [known-limitations.md](../internal/known-limitations.md).
30
-
31
- ## Decision
32
-
33
- Decision finalized for version `2.3.0`. The release includes the isolated read-only workspace runner, corrected snapshot comparisons, bypassed physical files write during read-only executions, real OpenCode smoke test integration in release verification, and Sage validation agent coordination in full mode.
34
-
35
- **Approved by:** José Willams (Human User)