vgxness 1.20.9 → 1.20.11

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.
@@ -133,7 +133,7 @@ export class CodeContextService {
133
133
  workspaceRoot,
134
134
  recommendedCommands: [
135
135
  'codegraph init # optional: create/update CodeGraph index outside VGXNESS',
136
- 'graphify --path . --output graphify-out # optional: create/update Graphify graph outside VGXNESS',
136
+ 'graphify update . --force --no-cluster # optional: create/update Graphify code graph outside VGXNESS',
137
137
  ],
138
138
  safety: { ...codeContextSafety },
139
139
  summary: '[read-only] Advisory plan only. VGXNESS does not install packages, initialize indexes, register external MCP servers, or write provider config.',
@@ -156,7 +156,7 @@ function providerLabel(provider) {
156
156
  return provider === 'codegraph' ? 'CodeGraph' : 'Graphify';
157
157
  }
158
158
  function queryArgs(provider, mode, question) {
159
- return provider === 'codegraph' ? [mode, question] : ['query', '--mode', mode, question];
159
+ return provider === 'codegraph' ? [mode, question] : ['query', question];
160
160
  }
161
161
  function firstNonEmpty(...values) {
162
162
  return values.find((value) => value.trim().length > 0)?.trim() ?? '';
@@ -185,7 +185,7 @@ function runBoundedCommand(request) {
185
185
  shell: false,
186
186
  encoding: 'utf8',
187
187
  timeout: request.timeoutMs,
188
- maxBuffer: request.maxBytes,
188
+ maxBuffer: maxMaxBytes,
189
189
  stdio: ['ignore', 'pipe', 'pipe'],
190
190
  ...(request.env === undefined ? {} : { env: request.env }),
191
191
  };
@@ -18,7 +18,7 @@ export const graphifyProviderDefinition = {
18
18
  reportRelativePath: join('graphify-out', 'GRAPH_REPORT.md'),
19
19
  setupGuidance: [
20
20
  'Install Graphify explicitly if desired: pipx install graphifyy',
21
- 'Generate graph output only after approval: graphify <workspace>',
21
+ 'Generate graph output only after approval: graphify update <workspace> --force --no-cluster',
22
22
  ],
23
23
  };
24
24
  export const codeContextProviderDefinitions = [codegraphProviderDefinition, graphifyProviderDefinition];
@@ -0,0 +1,85 @@
1
+ import { parallelAgentBatchCategories, parallelAgentBatchDefaultMaxConcurrency, parallelAgentBatchHardMaxConcurrency, parallelAgentBatchMaxTasks, } from '../../domain/runs/parallel-agent-batch.js';
2
+ const requiredSynthesis = {
3
+ required: true,
4
+ includeEvidence: true,
5
+ includeConflicts: true,
6
+ includeFailures: true,
7
+ includeResidualRisk: true,
8
+ includeNextAction: true,
9
+ };
10
+ export class ParallelAgentBatchService {
11
+ defaultMaxConcurrency;
12
+ hardMaxConcurrency;
13
+ allowedAgentNames;
14
+ constructor(options = {}) {
15
+ this.hardMaxConcurrency = options.hardMaxConcurrency ?? parallelAgentBatchHardMaxConcurrency;
16
+ this.defaultMaxConcurrency = options.defaultMaxConcurrency ?? parallelAgentBatchDefaultMaxConcurrency;
17
+ this.allowedAgentNames = options.allowedAgentNames === undefined ? undefined : new Set(options.allowedAgentNames);
18
+ }
19
+ validatePlan(plan) {
20
+ const issues = [];
21
+ const warnings = [];
22
+ const maxConcurrency = plan.maxConcurrency ?? this.defaultMaxConcurrency;
23
+ if (!isValidConcurrency(maxConcurrency, this.hardMaxConcurrency))
24
+ issues.push({
25
+ code: 'invalid-concurrency',
26
+ message: `maxConcurrency must be an integer between 1 and ${this.hardMaxConcurrency}; received ${String(maxConcurrency)}`,
27
+ });
28
+ if (!isValidConcurrency(this.defaultMaxConcurrency, this.hardMaxConcurrency))
29
+ issues.push({
30
+ code: 'invalid-concurrency',
31
+ message: `defaultMaxConcurrency must be an integer between 1 and ${this.hardMaxConcurrency}; received ${String(this.defaultMaxConcurrency)}`,
32
+ });
33
+ if (!Array.isArray(plan.tasks) || plan.tasks.length === 0)
34
+ issues.push({ code: 'invalid-task-count', message: 'parallel batch must contain at least one task' });
35
+ else if (plan.tasks.length > parallelAgentBatchMaxTasks)
36
+ issues.push({ code: 'invalid-task-count', message: `parallel batch cannot exceed ${parallelAgentBatchMaxTasks} tasks` });
37
+ if (plan.tasks.length > 0 && plan.tasks.length <= maxConcurrency)
38
+ warnings.push('task count does not exceed concurrency; batch may finish in a single wave');
39
+ if (!hasRequiredSynthesisContract(plan.synthesis))
40
+ issues.push({ code: 'invalid-synthesis-contract', message: 'synthesis contract must require evidence, conflicts, failures, residual risk, and next action' });
41
+ const taskIds = new Set(plan.tasks.map((task) => task.id).filter((id) => id.trim().length > 0));
42
+ for (const task of plan.tasks)
43
+ this.validateTask(task, taskIds, issues);
44
+ if (issues.length > 0)
45
+ return { ok: false, issues, warnings };
46
+ return { ok: true, value: { ...plan, maxConcurrency }, warnings };
47
+ }
48
+ validateTask(task, taskIds, issues) {
49
+ requireTrimmed(task.id, 'id', task.id, issues);
50
+ requireTrimmed(task.agentName, 'agentName', task.id, issues);
51
+ requireTrimmed(task.workspaceRoot, 'workspaceRoot', task.id, issues);
52
+ requireTrimmed(task.scopeSummary, 'scopeSummary', task.id, issues);
53
+ requireTrimmed(task.independenceRationale, 'independenceRationale', task.id, issues);
54
+ if (task.allowedTools.length === 0)
55
+ issues.push({ code: 'missing-field', taskId: task.id, message: 'allowedTools must name at least one permitted tool' });
56
+ if (!parallelAgentBatchCategories.includes(task.category))
57
+ issues.push({ code: 'unknown-category', taskId: task.id, message: `unsupported category: ${task.category}` });
58
+ if (this.allowedAgentNames !== undefined && !this.allowedAgentNames.has(task.agentName))
59
+ issues.push({ code: 'unknown-agent', taskId: task.id, message: `agent is not allowlisted: ${task.agentName}` });
60
+ if (task.recursiveDelegationAllowed === true)
61
+ issues.push({ code: 'recursive-delegation', taskId: task.id, message: 'subagents must not be allowed to launch subagents recursively' });
62
+ for (const dependency of task.dependsOn ?? []) {
63
+ if (taskIds.has(dependency))
64
+ issues.push({ code: 'dependent-task', taskId: task.id, message: `task depends on same-batch task ${dependency}; run these serially instead` });
65
+ }
66
+ if (task.riskTier !== 'read-only' && task.requiresPreflight !== true)
67
+ issues.push({ code: 'risky-preflight-required', taskId: task.id, message: 'risky or mutating tasks require preflight' });
68
+ if (task.category === 'implementation') {
69
+ if ((task.targetPaths ?? []).filter((path) => path.trim().length > 0).length === 0)
70
+ issues.push({ code: 'implementation-scope-required', taskId: task.id, message: 'implementation tasks must declare independent target paths or scopes' });
71
+ if (task.requiresPreflight !== true)
72
+ issues.push({ code: 'implementation-preflight-required', taskId: task.id, message: 'implementation tasks require preflight before launch' });
73
+ }
74
+ }
75
+ }
76
+ function isValidConcurrency(value, hardMaxConcurrency) {
77
+ return Number.isInteger(value) && value >= 1 && value <= hardMaxConcurrency;
78
+ }
79
+ function requireTrimmed(value, field, taskId, issues) {
80
+ if (value.trim().length === 0)
81
+ issues.push({ code: 'missing-field', taskId, message: `${field} is required` });
82
+ }
83
+ function hasRequiredSynthesisContract(value) {
84
+ return Object.entries(requiredSynthesis).every(([key, expected]) => value[key] === expected);
85
+ }
@@ -2,7 +2,7 @@ import { canonicalBehaviorContractVersion } from '../../behavior/behavior-contra
2
2
  export const canonicalDefaultAgentName = 'vgxness-manager';
3
3
  export const canonicalOpenCodeDefaultModel = 'openai/gpt-5.5';
4
4
  export const canonicalOpenCodeManagerReasoningEffort = 'high';
5
- export const canonicalPromptContractVersion = 24;
5
+ export const canonicalPromptContractVersion = 25;
6
6
  export const canonicalSddSubagentNames = [
7
7
  'vgxness-sdd-explore',
8
8
  'vgxness-sdd-propose',
@@ -79,7 +79,7 @@ function managerDefinition() {
79
79
  name: canonicalDefaultAgentName,
80
80
  description: 'Coordinates VGXNESS MCP state and SDD sub-agents while routing Tier 0-2 lightweight work, Tier 3 preflight validation, and Tier 4 formal SDD.',
81
81
  instructions: { kind: 'inline', value: registryManagerInstructionsV11 },
82
- capabilities: ['sdd-orchestration', 'agent-routing', 'mcp-coordination', 'project-local-automation', 'coordination', 'workflow-selection', 'resume'],
82
+ capabilities: ['sdd-orchestration', 'agent-routing', 'parallel-agent-delegation', 'mcp-coordination', 'project-local-automation', 'coordination', 'workflow-selection', 'resume'],
83
83
  permissions: { read: 'allow', edit: 'ask', shell: 'ask', git: 'ask', memory: 'allow', 'provider-tool': 'deny', secrets: 'deny' },
84
84
  memory: { scopes: ['project'] },
85
85
  workflows: ['explore', 'quickfix', 'plan', 'build', 'debug', 'sdd', 'agent-seeding', 'opencode-install'],
@@ -161,6 +161,7 @@ export function createCanonicalOpenCodeSddMcpToolPermissions() {
161
161
  vgxness_opencode_manager_payload: 'allow',
162
162
  vgxness_run_list: 'allow',
163
163
  vgxness_run_get: 'allow',
164
+ vgxness_run_parallel_batch_validate: 'allow',
164
165
  vgxness_run_start: 'allow',
165
166
  vgxness_run_checkpoint: 'allow',
166
167
  vgxness_run_preflight: 'allow',
@@ -209,6 +210,9 @@ This prompt is the VGXNESS operating contract; OpenCode also appends environment
209
210
  Use the lightest safe path; do not force SDD/subagents for small direct work. T0 answer/status, T1 inspect/read-only, T2 small direct edit/command, T3 preflighted risky action, T4 SDD only for phase-shaped product/architecture/large/multi-step changes. For small concrete changes, execute directly with normal verification and preflight only when risky. Do not create SDD artifacts, runs, or subagents just because code changes are involved.
210
211
  Do not invent or invoke generic reviewer subagents; use exact canonical SDD subagents only when the task is truly SDD-shaped or the user asks for SDD.
211
212
 
213
+ ## Parallel delegation
214
+ When work has independent analysis, verification, research, codebase investigation, debugging, code review, SDD drafting/review, or context-dependent implementation streams, create a bounded parallel batch instead of serializing by habit. Default max concurrency is 3 subagents; honor configured lower/higher limits only within validated safety caps. Launch only exact allowlisted subagents/roles, never wildcard or unknown agents, and never allow recursive delegation. Stay serial when tasks depend on each other, scope is unclear, the result of one task chooses the next task, or the cost/noise exceeds the benefit. For implementation fan-out, require independent paths/scopes plus preflight/human gates for risky mutations, destructive actions, publish, merge, provider config, privileged scope, secrets, or external roots. Each launched subagent gets bounded instructions: task, workspace, allowed tools, expected evidence, no recursive delegation, and concise result envelope. Before reporting completion, synthesize every result with evidence, conflicts, failures/timeouts, residual risk, and the chosen next serial action.
215
+
212
216
  ## Provider-native daily flow
213
217
  SDD happens in OpenCode conversation + VGXNESS MCP/subagents. No terminal SDD phase commands. CLI is an escape hatch for bootstrap, doctor, recovery, setup gaps, or explicit request.
214
218
 
@@ -250,7 +254,7 @@ Be concise.`;
250
254
  */
251
255
  const registryManagerInstructionsV11 = [
252
256
  'You are the VGXNESS manager/coordinator, not a monolithic executor and not an SDD-only bot. Coach briefly while coordinating: explain useful tradeoffs, be realistic about risks and unknowns, respectfully challenge weak assumptions with better options, keep the user comfortable and in control, and stay concise. Always verify before agreeing with technical claims; when blocked, ask one focused question and stop.',
253
- 'Default to direct inline execution for small concrete user requests, narrow edits, single-file fixes, docs updates, simple commands, and focused verification. When a user starts a new project/product/app or brings an undeveloped idea, run a preparation phase first: clarify goals, users, scope, constraints, stack, repo/location, integrations, data, security, deliverables, milestones, success criteria, and desired PRD/requirements/design/tasks/roadmap artifacts; offer 2-4 options when provider UI supports choices. Delegate only when scope is SDD phase-shaped, broad repository exploration, complex implementation, incident recovery, or multi-step analysis that benefits from an exact focused executor. When uncertain, narrow scope first; use exact SDD subagents only if the work is genuinely phase-shaped. do not invent or invoke generic reviewer subagents; use exact canonical SDD subagents or MCP guidance only.',
257
+ 'Default to direct inline execution for small concrete user requests, narrow edits, single-file fixes, docs updates, simple commands, and focused verification. When a user starts a new project/product/app or brings an undeveloped idea, run a preparation phase first: clarify goals, users, scope, constraints, stack, repo/location, integrations, data, security, deliverables, milestones, success criteria, and desired PRD/requirements/design/tasks/roadmap artifacts; offer 2-4 options when provider UI supports choices. Delegate only when scope is SDD phase-shaped, broad repository exploration, complex implementation, incident recovery, multi-step analysis, or independent workstreams that benefit from parallel subagents. When the task can split into independent analysis, verification, research, debugging, code review, SDD drafting/review, or context-dependent implementation streams, plan a bounded parallel batch with default max concurrency 3 and configurable validated caps. Launch exact allowlisted subagents only; never wildcard, unknown agents, or recursive delegation. Keep implementation fan-out gated by independent paths/scopes plus preflight/human approval for risky mutations, destructive actions, publish, merge, provider config, privileged scope, secrets, or external roots. Synthesize all subagent results with evidence, conflicts, failures, residual risk, and next action before reporting completion. When uncertain, narrow scope first; use exact SDD subagents only if the work is genuinely phase-shaped. do not invent or invoke generic reviewer subagents; use exact canonical SDD subagents or MCP guidance only.',
254
258
  'Use VGXNESS MCP as the durable control plane: for generic resume/continue prompts call vgxness_resume_context first, pass explicitChange only when the user gave an exact valid change, and do not infer a change from context_cockpit.memoryPreviews; for retrospective/status prompts such as "what did we do?" or "qué habíamos hecho", inspect recent completed run history first with vgxness_run_list completed and mention interrupted candidates only as old blockers, not as latest truth; vgxness_context_cockpit and vgxness_session_restore are advisory context only after the gate, and context cockpit previews, memoryPreviews, and SDD summaries are advisory only. Close/pause/compact with vgxness_session_close using actor manager plus an actionable summary when a current session id exists.',
255
259
  'For generic continue/sigamos/continuemos/resume development without an explicit user change, call vgxness_resume_context before any SDD status/continuation. If decision is proceed-explicit-change or proceed-single-active-work, then call SDD tools for that change. If inspect-interrupted-run, call vgxness_run_resume_inspect. If ask-change (no interrupted run, no exact user-specified change, and no single parsed active-work change), hard stop: do not call vgxness_agent_resolve; do not call vgxness_sdd_status or vgxness_sdd_continue, do not create runs, do not inspect repo files/native repo tools, do not delegate to SDD subagents, do not start runs, do not use skill_index/skill_search fallback, or invent agent ids; ask one question: "¿qué cambio retomamos?".',
256
260
  'Manager save-then-accept behavior: after a valid hidden phase subagent artifact, save it immediately as a draft with sdd_save_artifact, summarize the saved draft, then ask whether the human accepts it and wants to continue to the next phase. An affirmative continue/sí/yes/dale reply in that immediate context is explicit human acceptance. A reply of only save/no/revise keeps draft and must not advance. Never silently auto-accept.',
@@ -0,0 +1,13 @@
1
+ export const parallelAgentBatchDefaultMaxConcurrency = 3;
2
+ export const parallelAgentBatchHardMaxConcurrency = 8;
3
+ export const parallelAgentBatchMaxTasks = 20;
4
+ export const parallelAgentBatchCategories = [
5
+ 'analysis',
6
+ 'verification',
7
+ 'research',
8
+ 'sdd',
9
+ 'codebase-investigation',
10
+ 'debugging',
11
+ 'code-review',
12
+ 'implementation',
13
+ ];
@@ -51,7 +51,7 @@ export function runCodeContextCommand(command, parsed, environment) {
51
51
  kind: 'code-context-setup-plan',
52
52
  version: 1,
53
53
  workspaceRoot,
54
- recommendedCommands: ['codegraph init # optional: run manually outside VGXNESS', 'graphify --path . --output graphify-out # optional: run manually outside VGXNESS'],
54
+ recommendedCommands: ['codegraph init # optional: run manually outside VGXNESS', 'graphify update . --force --no-cluster # optional: run manually outside VGXNESS'],
55
55
  safety: { ...codeContextSafety },
56
56
  summary: '[read-only] Advisory plan only. VGXNESS does not install packages, initialize indexes, register external MCP servers, or write provider config.',
57
57
  };
@@ -1,3 +1,4 @@
1
+ import { ParallelAgentBatchService } from '../../../../application/runs/parallel-agent-batch-service.js';
1
2
  import { AgentMemoryCaptureService } from '../../../../memory/agent-memory-capture-service.js';
2
3
  import { errorEnvelope, successEnvelope, } from '../../schema.js';
3
4
  function toEnvelope(tool, result) {
@@ -13,6 +14,39 @@ export function runListEnvelope(input, services) {
13
14
  export function runGetEnvelope(input, services) {
14
15
  return toEnvelope('vgxness_run_get', services.runs.getRun(input.id));
15
16
  }
17
+ export function runParallelBatchValidateEnvelope(input) {
18
+ const options = {};
19
+ if (input.defaultMaxConcurrency !== undefined)
20
+ options.defaultMaxConcurrency = input.defaultMaxConcurrency;
21
+ if (input.hardMaxConcurrency !== undefined)
22
+ options.hardMaxConcurrency = input.hardMaxConcurrency;
23
+ if (input.allowedAgentNames !== undefined)
24
+ options.allowedAgentNames = input.allowedAgentNames;
25
+ const service = new ParallelAgentBatchService(options);
26
+ const { defaultMaxConcurrency: _defaultMaxConcurrency, hardMaxConcurrency: _hardMaxConcurrency, allowedAgentNames: _allowedAgentNames, ...plan } = input;
27
+ const validation = service.validatePlan(plan);
28
+ return successEnvelope('vgxness_run_parallel_batch_validate', {
29
+ kind: 'parallel-agent-batch-validation',
30
+ version: 1,
31
+ validation,
32
+ launchGuidance: {
33
+ launchSurface: 'opencode-native-delegate',
34
+ mcpExecutesSubagents: false,
35
+ nextStep: validation.ok
36
+ ? 'Use OpenCode native delegate/task launch for the validated subagent tasks, respecting maxConcurrency and synthesis requirements.'
37
+ : 'Fix validation issues before launching subagents.',
38
+ },
39
+ safety: {
40
+ readOnly: true,
41
+ validatesOnly: true,
42
+ launchesSubagents: false,
43
+ invokesProvider: false,
44
+ writesProviderConfig: false,
45
+ mutatesRunState: false,
46
+ mutatesSddArtifacts: false,
47
+ },
48
+ });
49
+ }
16
50
  export function runPreflightEnvelope(input, services) {
17
51
  return toEnvelope('vgxness_run_preflight', services.runs.preflightOperation(input));
18
52
  }
@@ -23,7 +23,7 @@ import { codeContextStatusEnvelope } from './control-plane/handlers/code-context
23
23
  import { contextCockpitEnvelope, resumeContextEnvelope } from './control-plane/handlers/context.js';
24
24
  import { memoryGetEnvelope, memorySaveEnvelope, memorySearchEnvelope, memoryUpdateEnvelope } from './control-plane/handlers/memory.js';
25
25
  import { providerChangePlanEnvelope, providerDoctorEnvelope, providerStatusEnvelope } from './control-plane/handlers/providers.js';
26
- import { runCheckpointEnvelope, runFinalizeEnvelope, runGetEnvelope, runListEnvelope, runPreflightEnvelope, runResumeCandidatesEnvelope, runResumeGateEnvelope, runResumeInspectEnvelope, runStartEnvelope, runTaskGrantCreateEnvelope, runTaskGrantGetEnvelope, runTaskGrantListEnvelope, runTaskGrantPreviewMatchEnvelope, runTaskGrantRevokeEnvelope, } from './control-plane/handlers/runs.js';
26
+ import { runCheckpointEnvelope, runFinalizeEnvelope, runGetEnvelope, runListEnvelope, runParallelBatchValidateEnvelope, runPreflightEnvelope, runResumeCandidatesEnvelope, runResumeGateEnvelope, runResumeInspectEnvelope, runStartEnvelope, runTaskGrantCreateEnvelope, runTaskGrantGetEnvelope, runTaskGrantListEnvelope, runTaskGrantPreviewMatchEnvelope, runTaskGrantRevokeEnvelope, } from './control-plane/handlers/runs.js';
27
27
  import { governanceReportEnvelope, sddAcceptArtifactEnvelope, sddCockpitEnvelope, sddContinueEnvelope, sddGetArtifactEnvelope, sddGetReadinessEnvelope, sddListArtifactsEnvelope, sddNextEnvelope, sddReadyEnvelope, sddReopenArtifactEnvelope, sddSaveArtifactEnvelope, sddStatusEnvelope, } from './control-plane/handlers/sdd.js';
28
28
  import { sessionAppendActivityEnvelope, sessionCloseEnvelope, sessionRestoreEnvelope, sessionStartEnvelope } from './control-plane/handlers/sessions.js';
29
29
  import { skillActivateEnvelope, skillCreateDraftEnvelope, skillGetEnvelope, skillGetEvaluationEnvelope, skillIndexEnvelope, skillListEvaluationsEnvelope, skillListImprovementProposalsEnvelope, skillPayloadEnvelope, skillProposeImprovementEnvelope, skillPublishVersionEnvelope, skillRecordUsageEnvelope, skillRequestEvaluationEnvelope, skillSearchEnvelope, skillStatusEnvelope, skillUpdateDraftEnvelope, } from './control-plane/handlers/skills.js';
@@ -127,6 +127,8 @@ export function callVgxTool(call, services) {
127
127
  return runListEnvelope(validated.input, services);
128
128
  case 'vgxness_run_get':
129
129
  return runGetEnvelope(validated.input, services);
130
+ case 'vgxness_run_parallel_batch_validate':
131
+ return runParallelBatchValidateEnvelope(validated.input);
130
132
  case 'vgxness_run_preflight':
131
133
  return runPreflightEnvelope(validated.input, services);
132
134
  case 'vgxness_run_task_grant_create':
@@ -48,6 +48,7 @@ export const SUPPORTED_VGX_MCP_TOOL_NAMES = [
48
48
  'vgxness_opencode_handoff_preview',
49
49
  'vgxness_run_list',
50
50
  'vgxness_run_get',
51
+ 'vgxness_run_parallel_batch_validate',
51
52
  'vgxness_run_preflight',
52
53
  'vgxness_run_task_grant_create',
53
54
  'vgxness_run_task_grant_list',
@@ -112,6 +113,7 @@ export const EXPOSED_VGX_MCP_TOOL_NAMES = [
112
113
  'opencode_handoff_preview',
113
114
  'run_list',
114
115
  'run_get',
116
+ 'run_parallel_batch_validate',
115
117
  'run_preflight',
116
118
  'run_task_grant_create',
117
119
  'run_task_grant_list',
@@ -1,5 +1,33 @@
1
1
  import { z } from 'zod';
2
2
  import { agentModes, explicitRequestInputSchema, finalRunStatuses, jsonValueSchema, permissionCategories, runOutcomes, runStatuses, taskGrantCategories, taskGrantCommandPolicyInputSchema, taskGrantHumanActorInputSchema, taskGrantRevocationActorInputSchema, taskGrantStatuses, workflowIds, } from '../common.js';
3
+ const parallelBatchCategories = ['analysis', 'verification', 'research', 'sdd', 'codebase-investigation', 'debugging', 'code-review', 'implementation'];
4
+ const parallelBatchRiskTiers = ['read-only', 'risky', 'mutation'];
5
+ const parallelBatchSynthesisSchema = z
6
+ .object({
7
+ required: z.literal(true),
8
+ includeEvidence: z.literal(true),
9
+ includeConflicts: z.literal(true),
10
+ includeFailures: z.literal(true),
11
+ includeResidualRisk: z.literal(true),
12
+ includeNextAction: z.literal(true),
13
+ })
14
+ .strict();
15
+ const parallelBatchTaskSchema = z
16
+ .object({
17
+ id: z.string().min(1),
18
+ category: z.enum(parallelBatchCategories),
19
+ agentName: z.string().min(1),
20
+ workspaceRoot: z.string().min(1),
21
+ allowedTools: z.array(z.string().min(1)).min(1),
22
+ scopeSummary: z.string().min(1),
23
+ independenceRationale: z.string().min(1),
24
+ riskTier: z.enum(parallelBatchRiskTiers),
25
+ requiresPreflight: z.boolean(),
26
+ targetPaths: z.array(z.string().min(1)).optional(),
27
+ dependsOn: z.array(z.string().min(1)).optional(),
28
+ recursiveDelegationAllowed: z.boolean().optional(),
29
+ })
30
+ .strict();
3
31
  export const RUNS_MCP_TOOL_INPUT_SCHEMAS = {
4
32
  vgxness_run_list: z
5
33
  .object({
@@ -13,6 +41,16 @@ export const RUNS_MCP_TOOL_INPUT_SCHEMAS = {
13
41
  id: z.string().min(1),
14
42
  })
15
43
  .passthrough(),
44
+ vgxness_run_parallel_batch_validate: z
45
+ .object({
46
+ maxConcurrency: z.number().int().min(1).optional(),
47
+ defaultMaxConcurrency: z.number().int().min(1).optional(),
48
+ hardMaxConcurrency: z.number().int().min(1).optional(),
49
+ allowedAgentNames: z.array(z.string().min(1)).optional(),
50
+ tasks: z.array(parallelBatchTaskSchema).min(1),
51
+ synthesis: parallelBatchSynthesisSchema,
52
+ })
53
+ .strict(),
16
54
  vgxness_run_preflight: z
17
55
  .object({
18
56
  runId: z.string().min(1),
@@ -30,6 +30,123 @@ export function validateRunGetInput(input, tool) {
30
30
  return id;
31
31
  return { ok: true, value: { id: id.value } };
32
32
  }
33
+ const parallelBatchCategories = ['analysis', 'verification', 'research', 'sdd', 'codebase-investigation', 'debugging', 'code-review', 'implementation'];
34
+ const parallelBatchRiskTiers = ['read-only', 'risky', 'mutation'];
35
+ export function validateRunParallelBatchValidateInput(input, tool) {
36
+ const record = inputRecord(input, tool, [
37
+ 'maxConcurrency',
38
+ 'defaultMaxConcurrency',
39
+ 'hardMaxConcurrency',
40
+ 'allowedAgentNames',
41
+ 'tasks',
42
+ 'synthesis',
43
+ ]);
44
+ if (!record.ok)
45
+ return record;
46
+ const result = {};
47
+ for (const field of ['maxConcurrency', 'defaultMaxConcurrency', 'hardMaxConcurrency']) {
48
+ if (record.value[field] === undefined)
49
+ continue;
50
+ if (!Number.isInteger(record.value[field]) || record.value[field] < 1)
51
+ return validationFailure(`${field} must be a positive integer`, tool);
52
+ result[field] = record.value[field];
53
+ }
54
+ const allowedAgentNames = readOptionalStringArray(record.value, 'allowedAgentNames', tool);
55
+ if (!allowedAgentNames.ok)
56
+ return allowedAgentNames;
57
+ if (allowedAgentNames.value !== undefined)
58
+ result.allowedAgentNames = allowedAgentNames.value;
59
+ const tasks = readParallelBatchTasks(record.value.tasks, tool);
60
+ if (!tasks.ok)
61
+ return tasks;
62
+ result.tasks = tasks.value;
63
+ const synthesis = readParallelBatchSynthesis(record.value.synthesis, tool);
64
+ if (!synthesis.ok)
65
+ return synthesis;
66
+ result.synthesis = synthesis.value;
67
+ return { ok: true, value: result };
68
+ }
69
+ function readParallelBatchTasks(value, tool) {
70
+ if (!Array.isArray(value) || value.length === 0)
71
+ return validationFailure('tasks must be a non-empty array', tool);
72
+ const tasks = [];
73
+ for (const [index, item] of value.entries()) {
74
+ const record = asRecord(item, tool, `tasks[${index}] must be an object`);
75
+ if (!record.ok)
76
+ return record;
77
+ const unexpected = Object.keys(record.value).find((key) => !['id', 'category', 'agentName', 'workspaceRoot', 'allowedTools', 'scopeSummary', 'independenceRationale', 'riskTier', 'requiresPreflight', 'targetPaths', 'dependsOn', 'recursiveDelegationAllowed'].includes(key));
78
+ if (unexpected !== undefined)
79
+ return validationFailure(`Unexpected tasks[${index}] field: ${unexpected}`, tool);
80
+ const id = readNonEmptyString(record.value, 'id', tool);
81
+ if (!id.ok)
82
+ return id;
83
+ const category = readRequiredOneOf(record.value, 'category', parallelBatchCategories, tool);
84
+ if (!category.ok)
85
+ return category;
86
+ const agentName = readNonEmptyString(record.value, 'agentName', tool);
87
+ if (!agentName.ok)
88
+ return agentName;
89
+ const workspaceRoot = readNonEmptyString(record.value, 'workspaceRoot', tool);
90
+ if (!workspaceRoot.ok)
91
+ return workspaceRoot;
92
+ const allowedTools = readRequiredStringArray(record.value, 'allowedTools', tool);
93
+ if (!allowedTools.ok)
94
+ return allowedTools;
95
+ const scopeSummary = readNonEmptyString(record.value, 'scopeSummary', tool);
96
+ if (!scopeSummary.ok)
97
+ return scopeSummary;
98
+ const independenceRationale = readNonEmptyString(record.value, 'independenceRationale', tool);
99
+ if (!independenceRationale.ok)
100
+ return independenceRationale;
101
+ const riskTier = readRequiredOneOf(record.value, 'riskTier', parallelBatchRiskTiers, tool);
102
+ if (!riskTier.ok)
103
+ return riskTier;
104
+ if (typeof record.value.requiresPreflight !== 'boolean')
105
+ return validationFailure('requiresPreflight must be a boolean', tool);
106
+ const task = {
107
+ id: id.value,
108
+ category: category.value,
109
+ agentName: agentName.value,
110
+ workspaceRoot: workspaceRoot.value,
111
+ allowedTools: allowedTools.value,
112
+ scopeSummary: scopeSummary.value,
113
+ independenceRationale: independenceRationale.value,
114
+ riskTier: riskTier.value,
115
+ requiresPreflight: record.value.requiresPreflight,
116
+ };
117
+ const targetPaths = readOptionalStringArray(record.value, 'targetPaths', tool);
118
+ if (!targetPaths.ok)
119
+ return targetPaths;
120
+ if (targetPaths.value !== undefined)
121
+ task.targetPaths = targetPaths.value;
122
+ const dependsOn = readOptionalStringArray(record.value, 'dependsOn', tool);
123
+ if (!dependsOn.ok)
124
+ return dependsOn;
125
+ if (dependsOn.value !== undefined)
126
+ task.dependsOn = dependsOn.value;
127
+ if (record.value.recursiveDelegationAllowed !== undefined) {
128
+ if (typeof record.value.recursiveDelegationAllowed !== 'boolean')
129
+ return validationFailure('recursiveDelegationAllowed must be a boolean', tool);
130
+ task.recursiveDelegationAllowed = record.value.recursiveDelegationAllowed;
131
+ }
132
+ tasks.push(task);
133
+ }
134
+ return { ok: true, value: tasks };
135
+ }
136
+ function readParallelBatchSynthesis(value, tool) {
137
+ const record = asRecord(value, tool, 'synthesis must be an object');
138
+ if (!record.ok)
139
+ return record;
140
+ const required = ['required', 'includeEvidence', 'includeConflicts', 'includeFailures', 'includeResidualRisk', 'includeNextAction'];
141
+ const unexpected = Object.keys(record.value).find((key) => !required.includes(key));
142
+ if (unexpected !== undefined)
143
+ return validationFailure(`Unexpected synthesis field: ${unexpected}`, tool);
144
+ for (const field of required) {
145
+ if (record.value[field] !== true)
146
+ return validationFailure(`synthesis.${field} must be true`, tool);
147
+ }
148
+ return { ok: true, value: record.value };
149
+ }
33
150
  export function validateRunPreflightInput(input, tool) {
34
151
  const record = inputRecord(input, tool, [
35
152
  'runId',
@@ -5,7 +5,7 @@ import { asRecord, readString, toValidationResultFailure, validationFailure, val
5
5
  import { validateGovernanceReportInput } from './validation/governance.js';
6
6
  import { validateMemoryGetInput, validateMemorySaveInput, validateMemorySearchInput, validateMemoryUpdateInput } from './validation/memory.js';
7
7
  import { validateProviderChangePlanInput, validateProviderHealthInput } from './validation/providers.js';
8
- import { validateRunCheckpointInput, validateRunFinalizeInput, validateRunGetInput, validateRunListInput, validateRunPreflightInput, validateRunResumeCandidatesInput, validateRunResumeGateInput, validateRunResumeInspectInput, validateRunStartInput, validateRunTaskGrantCreateInput, validateRunTaskGrantGetInput, validateRunTaskGrantListInput, validateRunTaskGrantPreviewMatchInput, validateRunTaskGrantRevokeInput, } from './validation/runs.js';
8
+ import { validateRunCheckpointInput, validateRunFinalizeInput, validateRunGetInput, validateRunListInput, validateRunParallelBatchValidateInput, validateRunPreflightInput, validateRunResumeCandidatesInput, validateRunResumeGateInput, validateRunResumeInspectInput, validateRunStartInput, validateRunTaskGrantCreateInput, validateRunTaskGrantGetInput, validateRunTaskGrantListInput, validateRunTaskGrantPreviewMatchInput, validateRunTaskGrantRevokeInput, } from './validation/runs.js';
9
9
  import { validateContextCockpitInput, validateResumeContextInput, validateSddAcceptArtifactInput, validateSddCockpitInput, validateSddContinueInput, validateSddGetArtifactInput, validateSddGetReadinessInput, validateSddListArtifactsInput, validateSddNextInput, validateSddReadyInput, validateSddReopenArtifactInput, validateSddSaveArtifactInput, validateSddStatusInput, } from './validation/sdd.js';
10
10
  import { validateSessionAppendActivityInput, validateSessionCloseInput, validateSessionRestoreInput, validateSessionStartInput, } from './validation/sessions.js';
11
11
  import { validateSkillActivateInput, validateSkillCreateDraftInput, validateSkillGetEvaluationInput, validateSkillGetInput, validateSkillIndexInput, validateSkillListEvaluationsInput, validateSkillListImprovementProposalsInput, validateSkillPayloadInput, validateSkillProposeImprovementInput, validateSkillPublishVersionInput, validateSkillRecordUsageInput, validateSkillRequestEvaluationInput, validateSkillSearchInput, validateSkillStatusInput, validateSkillUpdateDraftInput, } from './validation/skills.js';
@@ -113,6 +113,8 @@ export function validateVgxMcpToolCall(call) {
113
113
  return validationSuccess(tool.value, validateRunListInput(input, tool.value));
114
114
  case 'vgxness_run_get':
115
115
  return validationSuccess(tool.value, validateRunGetInput(input, tool.value));
116
+ case 'vgxness_run_parallel_batch_validate':
117
+ return validationSuccess(tool.value, validateRunParallelBatchValidateInput(input, tool.value));
116
118
  case 'vgxness_run_preflight':
117
119
  return validationSuccess(tool.value, validateRunPreflightInput(input, tool.value));
118
120
  case 'vgxness_run_task_grant_create':
package/docs/mcp.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MCP tools
2
2
 
3
- VGXNESS exposes 43 typed MCP tools over stdio through `vgxness mcp start`. The canonical list is the `SUPPORTED_VGX_MCP_TOOL_NAMES` array in `src/mcp/schema.ts` — treat that as the source of truth. The list below mirrors it; if a tool appears here that is not in the array, or vice versa, the array wins.
3
+ VGXNESS exposes 63 typed MCP tools over stdio through `vgxness mcp start`. The canonical list is the `SUPPORTED_VGX_MCP_TOOL_NAMES` array in `src/mcp/schema.ts` — treat that as the source of truth. The list below mirrors it; if a tool appears here that is not in the array, or vice versa, the array wins.
4
4
 
5
5
  Tools are exposed under the `vgxness_*` prefix. Some MCP hosts strip the prefix and accept the short form (`sdd_status`, `memory_save`, etc.). The schema accepts both.
6
6
 
@@ -83,12 +83,13 @@ Payload tools that accept `agentId` resolve it as a canonical registry id first.
83
83
  | `vgxness_opencode_manager_payload` | Build the OpenCode manager payload envelope. | `OpenCodeManagerPayloadInput` shape (project, scope, agent, workflow/phase, workspaceRoot, maxSourceBytes, payloadMode) | `installable: false`, `readOnly: true`. |
84
84
  | `vgxness_opencode_handoff_preview` | Compose a full handoff preview: provider artifacts + skill diagnostics + SDD status + provider status + safety flags. | `project`; optional `scope`, `agentId`, `agentName`, `workspaceRoot`, `maxSourceBytes`, `change`, `phase` | Read-only preview. Does not execute OpenCode, install hooks, write provider config, or capture memory candidates. |
85
85
 
86
- ## Runs (9)
86
+ ## Runs (15)
87
87
 
88
88
  | Tool | Purpose | Required inputs | Notes |
89
89
  |---|---|---|---|
90
90
  | `vgxness_run_list` | List runs with filters. | `limit`; optional `project`, `status` (one of the 8 `RunStatus` values) | |
91
91
  | `vgxness_run_get` | Read a run with `events`, `checkpoints`, `approvals`, `operationAttempts`. | `id` | |
92
+ | `vgxness_run_parallel_batch_validate` | Validate a bounded parallel subagent batch plan before provider-native launch. | `tasks`, `synthesis`; optional `maxConcurrency`, `defaultMaxConcurrency`, `hardMaxConcurrency`, `allowedAgentNames` | Read-only validation surface. Default concurrency is 3 unless configured. Does not launch subagents, invoke providers, mutate runs, or write SDD artifacts; it returns launch guidance for OpenCode native delegation. |
92
93
  | `vgxness_run_start` | Create a run record. | `CreateRunInput` shape | |
93
94
  | `vgxness_run_checkpoint` | Append a resumable JSON state. | `AppendRunCheckpointInput` shape | |
94
95
  | `vgxness_run_finalize` | Finalize a run with `outcome` and `outcomeReason`. | `FinalizeRunInput` shape | `outcome` must match a terminal `status`. Re-finalization is rejected. May return advisory `memoryCapture` candidates from bounded finalization fields; candidates are not persisted automatically. |
@@ -161,6 +162,20 @@ vgxness_run_finalize { runId, outcome: "success", outcomeReason: "..." }
161
162
 
162
163
  `vgxness_run_preflight` output includes `workspaceStrategy` with one of `current-checkout`, `branch`, `manual-worktree`, or `stacked-prs`. This is advisory planning metadata only. Its execution guarantees are always non-mutating (`createsBranch:false`, `createsWorktree:false`, `createsPullRequest:false`, `mutatesRepository:false`, `mutatesProviderConfig:false`). Treat `manual-worktree` and `stacked-prs` as instructions for a human or future executor to arrange review/isolation deliberately before mutation, not as evidence that VGXNESS already created Git state.
163
164
 
165
+ Validating a parallel subagent batch before provider-native launch:
166
+
167
+ ```text
168
+ vgxness_run_parallel_batch_validate {
169
+ maxConcurrency: 3,
170
+ tasks: [
171
+ { id, category: "analysis", agentName, workspaceRoot, allowedTools, scopeSummary, independenceRationale, riskTier: "read-only", requiresPreflight: false }
172
+ ],
173
+ synthesis: { required: true, includeEvidence: true, includeConflicts: true, includeFailures: true, includeResidualRisk: true, includeNextAction: true }
174
+ }
175
+ ```
176
+
177
+ `vgxness_run_parallel_batch_validate` is a guardrail, not an executor. It checks batch size, configured concurrency, independent tasks, allowlisted agents, no recursive delegation, implementation preflight requirements, and synthesis obligations. When validation succeeds, the manager may use the provider's native subagent/delegation capability (for example OpenCode native delegation) while respecting the validated `maxConcurrency`. MCP itself does not launch or monitor those subagents.
178
+
164
179
  Finding interrupted runs when the run id is unknown:
165
180
 
166
181
  ```text
@@ -177,6 +192,7 @@ MCP tools do not:
177
192
 
178
193
  - Write project-local provider config (`.opencode/`, `.mcp.json`, `.claude/`, project-root `CLAUDE.md`). VGX-managed OpenCode and Claude writes are user-global only and must stay behind explicit install/apply confirmation outside read-only status/preview tools.
179
194
  - Execute providers (`opencode`, `claude`, etc.).
195
+ - Launch subagents. Parallel subagent launch is provider-native after `vgxness_run_parallel_batch_validate` succeeds; MCP validates only.
180
196
  - Install global memory.
181
197
  - Persist `memoryCapture` lifecycle candidates automatically or use them as proof of SDD acceptance, readiness, verification, approval, or authorization.
182
198
  - Create `openspec/`.
package/docs/safety.md CHANGED
@@ -68,6 +68,20 @@ The experimental `vgxness code` runtime approval flow was removed with `src/code
68
68
 
69
69
  The gateway is conservative by default: a non-`read` tool in a non-`apply-progress` phase defaults to `ask`. Even with `--approval-policy allow`, the gateway can re-promote to `ask` for risky categories or workspace boundary issues.
70
70
 
71
+ ## Parallel subagent delegation
72
+
73
+ Parallel delegation is provider-native, not an MCP executor. The manager may decide to fan out independent analysis, verification, research, review, debugging, SDD drafting/review, or context-scoped implementation work, but the plan should pass through `vgxness_run_parallel_batch_validate` first when using the VGXNESS MCP control plane.
74
+
75
+ `vgxness_run_parallel_batch_validate` is read-only and validates only:
76
+
77
+ - default concurrency is 3 unless a smaller/larger bounded value is configured;
78
+ - every task must declare category, agent, workspace root, allowed tools, scope, independence rationale, risk tier, and preflight requirement;
79
+ - same-batch dependencies and recursive delegation are rejected;
80
+ - implementation tasks require target paths and preflight declaration;
81
+ - synthesis must include evidence, conflicts, failures, residual risk, and next action.
82
+
83
+ The MCP tool does not launch subagents, invoke providers, mutate run state, write artifacts, or monitor provider-native executions. OpenCode or another provider performs the actual subagent launch after validation, and normal preflight/approval rules still apply to risky or mutating work.
84
+
71
85
  ## Task-scoped external command grants
72
86
 
73
87
  Task-scoped command grants are a narrow V1 escape hatch for a human to authorize a bounded external working directory for an allowlisted command preflight. They only convert an eligible `ask` decision to an audited `allow`; they never convert `deny` or hard-risk requests to `allow`, and apply/implementation work still requires the normal SDD governance gates.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgxness",
3
- "version": "1.20.9",
3
+ "version": "1.20.11",
4
4
  "description": "CLI and MCP control plane for guided AI-agent workflows, SDD, memory, and OpenCode setup.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {