attocode 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -1
- package/README.md +65 -5
- package/dist/src/adapters.d.ts.map +1 -1
- package/dist/src/adapters.js +15 -11
- package/dist/src/adapters.js.map +1 -1
- package/dist/src/agent.d.ts +38 -98
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +505 -2892
- package/dist/src/agent.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +2 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/handler.d.ts.map +1 -1
- package/dist/src/commands/handler.js +11 -3
- package/dist/src/commands/handler.js.map +1 -1
- package/dist/src/commands/init-commands.d.ts.map +1 -1
- package/dist/src/commands/init-commands.js +16 -1
- package/dist/src/commands/init-commands.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +31 -0
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/config/base-types.d.ts +45 -0
- package/dist/src/config/base-types.d.ts.map +1 -0
- package/dist/src/config/base-types.js +9 -0
- package/dist/src/config/base-types.js.map +1 -0
- package/dist/src/config/config-manager.d.ts +35 -0
- package/dist/src/config/config-manager.d.ts.map +1 -0
- package/dist/src/config/config-manager.js +108 -0
- package/dist/src/config/config-manager.js.map +1 -0
- package/dist/src/config/index.d.ts +4 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +3 -0
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/config/schema.d.ts +1546 -0
- package/dist/src/config/schema.d.ts.map +1 -0
- package/dist/src/config/schema.js +268 -0
- package/dist/src/config/schema.js.map +1 -0
- package/dist/src/config.d.ts +4 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +8 -12
- package/dist/src/config.js.map +1 -1
- package/dist/src/core/agent-state-machine.d.ts +131 -0
- package/dist/src/core/agent-state-machine.d.ts.map +1 -0
- package/dist/src/core/agent-state-machine.js +302 -0
- package/dist/src/core/agent-state-machine.js.map +1 -0
- package/dist/src/core/base-manager.d.ts +79 -0
- package/dist/src/core/base-manager.d.ts.map +1 -0
- package/dist/src/core/base-manager.js +170 -0
- package/dist/src/core/base-manager.js.map +1 -0
- package/dist/src/core/completion-analyzer.d.ts +15 -0
- package/dist/src/core/completion-analyzer.d.ts.map +1 -0
- package/dist/src/core/completion-analyzer.js +53 -0
- package/dist/src/core/completion-analyzer.js.map +1 -0
- package/dist/src/core/execution-loop.d.ts +46 -0
- package/dist/src/core/execution-loop.d.ts.map +1 -0
- package/dist/src/core/execution-loop.js +1258 -0
- package/dist/src/core/execution-loop.js.map +1 -0
- package/dist/src/core/index.d.ts +7 -0
- package/dist/src/core/index.d.ts.map +1 -1
- package/dist/src/core/index.js +9 -0
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/core/process-handlers.d.ts.map +1 -1
- package/dist/src/core/process-handlers.js +14 -0
- package/dist/src/core/process-handlers.js.map +1 -1
- package/dist/src/core/protocol/types.d.ts +12 -12
- package/dist/src/core/response-handler.d.ts +16 -0
- package/dist/src/core/response-handler.d.ts.map +1 -0
- package/dist/src/core/response-handler.js +234 -0
- package/dist/src/core/response-handler.js.map +1 -0
- package/dist/src/core/subagent-spawner.d.ts +43 -0
- package/dist/src/core/subagent-spawner.d.ts.map +1 -0
- package/dist/src/core/subagent-spawner.js +966 -0
- package/dist/src/core/subagent-spawner.js.map +1 -0
- package/dist/src/core/tool-executor.d.ts +59 -0
- package/dist/src/core/tool-executor.d.ts.map +1 -0
- package/dist/src/core/tool-executor.js +677 -0
- package/dist/src/core/tool-executor.js.map +1 -0
- package/dist/src/core/types.d.ts +133 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +12 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/defaults.d.ts +2 -2
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +29 -1
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/integrations/auto-compaction.d.ts.map +1 -1
- package/dist/src/integrations/auto-compaction.js +3 -2
- package/dist/src/integrations/auto-compaction.js.map +1 -1
- package/dist/src/integrations/budget-pool.d.ts +7 -0
- package/dist/src/integrations/budget-pool.d.ts.map +1 -1
- package/dist/src/integrations/budget-pool.js +43 -0
- package/dist/src/integrations/budget-pool.js.map +1 -1
- package/dist/src/integrations/codebase-ast.d.ts +52 -0
- package/dist/src/integrations/codebase-ast.d.ts.map +1 -0
- package/dist/src/integrations/codebase-ast.js +457 -0
- package/dist/src/integrations/codebase-ast.js.map +1 -0
- package/dist/src/integrations/codebase-context.d.ts +18 -0
- package/dist/src/integrations/codebase-context.d.ts.map +1 -1
- package/dist/src/integrations/codebase-context.js +197 -17
- package/dist/src/integrations/codebase-context.js.map +1 -1
- package/dist/src/integrations/compaction.d.ts.map +1 -1
- package/dist/src/integrations/compaction.js +14 -6
- package/dist/src/integrations/compaction.js.map +1 -1
- package/dist/src/integrations/context-engineering.d.ts +8 -0
- package/dist/src/integrations/context-engineering.d.ts.map +1 -1
- package/dist/src/integrations/context-engineering.js +19 -0
- package/dist/src/integrations/context-engineering.js.map +1 -1
- package/dist/src/integrations/economics.d.ts +25 -1
- package/dist/src/integrations/economics.d.ts.map +1 -1
- package/dist/src/integrations/economics.js +217 -38
- package/dist/src/integrations/economics.js.map +1 -1
- package/dist/src/integrations/edit-validator.d.ts +30 -0
- package/dist/src/integrations/edit-validator.d.ts.map +1 -0
- package/dist/src/integrations/edit-validator.js +85 -0
- package/dist/src/integrations/edit-validator.js.map +1 -0
- package/dist/src/integrations/file-cache.d.ts +7 -0
- package/dist/src/integrations/file-cache.d.ts.map +1 -1
- package/dist/src/integrations/file-cache.js +54 -0
- package/dist/src/integrations/file-cache.js.map +1 -1
- package/dist/src/integrations/health-check.d.ts.map +1 -1
- package/dist/src/integrations/health-check.js +3 -2
- package/dist/src/integrations/health-check.js.map +1 -1
- package/dist/src/integrations/hierarchical-config.d.ts +3 -0
- package/dist/src/integrations/hierarchical-config.d.ts.map +1 -1
- package/dist/src/integrations/hierarchical-config.js +3 -0
- package/dist/src/integrations/hierarchical-config.js.map +1 -1
- package/dist/src/integrations/hooks.d.ts +2 -0
- package/dist/src/integrations/hooks.d.ts.map +1 -1
- package/dist/src/integrations/hooks.js +99 -15
- package/dist/src/integrations/hooks.js.map +1 -1
- package/dist/src/integrations/index.d.ts +7 -0
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +9 -1
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/logger.d.ts +104 -0
- package/dist/src/integrations/logger.d.ts.map +1 -0
- package/dist/src/integrations/logger.js +219 -0
- package/dist/src/integrations/logger.js.map +1 -0
- package/dist/src/integrations/lsp.d.ts.map +1 -1
- package/dist/src/integrations/lsp.js +5 -4
- package/dist/src/integrations/lsp.js.map +1 -1
- package/dist/src/integrations/mcp-client.d.ts.map +1 -1
- package/dist/src/integrations/mcp-client.js +8 -7
- package/dist/src/integrations/mcp-client.js.map +1 -1
- package/dist/src/integrations/observability.d.ts.map +1 -1
- package/dist/src/integrations/observability.js +5 -4
- package/dist/src/integrations/observability.js.map +1 -1
- package/dist/src/integrations/openrouter-pricing.d.ts.map +1 -1
- package/dist/src/integrations/openrouter-pricing.js +4 -3
- package/dist/src/integrations/openrouter-pricing.js.map +1 -1
- package/dist/src/integrations/persistence.d.ts.map +1 -1
- package/dist/src/integrations/persistence.js +5 -4
- package/dist/src/integrations/persistence.js.map +1 -1
- package/dist/src/integrations/planning.d.ts.map +1 -1
- package/dist/src/integrations/planning.js +5 -4
- package/dist/src/integrations/planning.js.map +1 -1
- package/dist/src/integrations/retry.d.ts +1 -0
- package/dist/src/integrations/retry.d.ts.map +1 -1
- package/dist/src/integrations/retry.js.map +1 -1
- package/dist/src/integrations/routing.d.ts.map +1 -1
- package/dist/src/integrations/routing.js +2 -1
- package/dist/src/integrations/routing.js.map +1 -1
- package/dist/src/integrations/safety.d.ts.map +1 -1
- package/dist/src/integrations/safety.js +13 -13
- package/dist/src/integrations/safety.js.map +1 -1
- package/dist/src/integrations/sandbox/docker.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/docker.js +2 -1
- package/dist/src/integrations/sandbox/docker.js.map +1 -1
- package/dist/src/integrations/sandbox/index.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/index.js +5 -4
- package/dist/src/integrations/sandbox/index.js.map +1 -1
- package/dist/src/integrations/session-store.d.ts +1 -0
- package/dist/src/integrations/session-store.d.ts.map +1 -1
- package/dist/src/integrations/session-store.js +1 -0
- package/dist/src/integrations/session-store.js.map +1 -1
- package/dist/src/integrations/shared-blackboard.d.ts +3 -0
- package/dist/src/integrations/shared-blackboard.d.ts.map +1 -1
- package/dist/src/integrations/shared-blackboard.js +47 -0
- package/dist/src/integrations/shared-blackboard.js.map +1 -1
- package/dist/src/integrations/smart-decomposer.d.ts +27 -0
- package/dist/src/integrations/smart-decomposer.d.ts.map +1 -1
- package/dist/src/integrations/smart-decomposer.js +414 -30
- package/dist/src/integrations/smart-decomposer.js.map +1 -1
- package/dist/src/integrations/sqlite-store.d.ts +2 -0
- package/dist/src/integrations/sqlite-store.d.ts.map +1 -1
- package/dist/src/integrations/sqlite-store.js +18 -6
- package/dist/src/integrations/sqlite-store.js.map +1 -1
- package/dist/src/integrations/swarm/failure-classifier.d.ts +11 -0
- package/dist/src/integrations/swarm/failure-classifier.d.ts.map +1 -0
- package/dist/src/integrations/swarm/failure-classifier.js +95 -0
- package/dist/src/integrations/swarm/failure-classifier.js.map +1 -0
- package/dist/src/integrations/swarm/model-selector.d.ts.map +1 -1
- package/dist/src/integrations/swarm/model-selector.js +2 -1
- package/dist/src/integrations/swarm/model-selector.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts +8 -0
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.js +95 -0
- package/dist/src/integrations/swarm/swarm-config-loader.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts +74 -0
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.js +37 -0
- package/dist/src/integrations/swarm/swarm-event-bridge.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts +3 -0
- package/dist/src/integrations/swarm/swarm-events.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.js +1 -1
- package/dist/src/integrations/swarm/swarm-events.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts +23 -0
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.js +530 -55
- package/dist/src/integrations/swarm/swarm-orchestrator.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-state-store.d.ts +4 -1
- package/dist/src/integrations/swarm/swarm-state-store.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-state-store.js +8 -1
- package/dist/src/integrations/swarm/swarm-state-store.js.map +1 -1
- package/dist/src/integrations/swarm/task-queue.d.ts +10 -0
- package/dist/src/integrations/swarm/task-queue.d.ts.map +1 -1
- package/dist/src/integrations/swarm/task-queue.js +36 -1
- package/dist/src/integrations/swarm/task-queue.js.map +1 -1
- package/dist/src/integrations/swarm/types.d.ts +41 -0
- package/dist/src/integrations/swarm/types.d.ts.map +1 -1
- package/dist/src/integrations/swarm/types.js +9 -0
- package/dist/src/integrations/swarm/types.js.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.d.ts +12 -2
- package/dist/src/integrations/swarm/worker-pool.d.ts.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.js +53 -4
- package/dist/src/integrations/swarm/worker-pool.js.map +1 -1
- package/dist/src/integrations/task-manager.d.ts +33 -1
- package/dist/src/integrations/task-manager.d.ts.map +1 -1
- package/dist/src/integrations/task-manager.js +78 -4
- package/dist/src/integrations/task-manager.js.map +1 -1
- package/dist/src/main.js +83 -32
- package/dist/src/main.js.map +1 -1
- package/dist/src/modes/repl.d.ts.map +1 -1
- package/dist/src/modes/repl.js +40 -8
- package/dist/src/modes/repl.js.map +1 -1
- package/dist/src/modes/tui.d.ts.map +1 -1
- package/dist/src/modes/tui.js +36 -6
- package/dist/src/modes/tui.js.map +1 -1
- package/dist/src/observability/tracer.d.ts.map +1 -1
- package/dist/src/observability/tracer.js +2 -1
- package/dist/src/observability/tracer.js.map +1 -1
- package/dist/src/persistence/schema.d.ts.map +1 -1
- package/dist/src/persistence/schema.js +11 -0
- package/dist/src/persistence/schema.js.map +1 -1
- package/dist/src/providers/adapters/anthropic.d.ts.map +1 -1
- package/dist/src/providers/adapters/anthropic.js +3 -2
- package/dist/src/providers/adapters/anthropic.js.map +1 -1
- package/dist/src/providers/adapters/openai.d.ts.map +1 -1
- package/dist/src/providers/adapters/openai.js +3 -2
- package/dist/src/providers/adapters/openai.js.map +1 -1
- package/dist/src/providers/adapters/openrouter.d.ts.map +1 -1
- package/dist/src/providers/adapters/openrouter.js +11 -11
- package/dist/src/providers/adapters/openrouter.js.map +1 -1
- package/dist/src/providers/circuit-breaker.d.ts +1 -0
- package/dist/src/providers/circuit-breaker.d.ts.map +1 -1
- package/dist/src/providers/circuit-breaker.js.map +1 -1
- package/dist/src/providers/provider.d.ts.map +1 -1
- package/dist/src/providers/provider.js +2 -1
- package/dist/src/providers/provider.js.map +1 -1
- package/dist/src/providers/resilient-provider.d.ts.map +1 -1
- package/dist/src/providers/resilient-provider.js +2 -1
- package/dist/src/providers/resilient-provider.js.map +1 -1
- package/dist/src/session-picker.d.ts.map +1 -1
- package/dist/src/session-picker.js +40 -5
- package/dist/src/session-picker.js.map +1 -1
- package/dist/src/shared/budget-tracker.d.ts +65 -0
- package/dist/src/shared/budget-tracker.d.ts.map +1 -0
- package/dist/src/shared/budget-tracker.js +128 -0
- package/dist/src/shared/budget-tracker.js.map +1 -0
- package/dist/src/shared/context-engine.d.ts +64 -0
- package/dist/src/shared/context-engine.d.ts.map +1 -0
- package/dist/src/shared/context-engine.js +117 -0
- package/dist/src/shared/context-engine.js.map +1 -0
- package/dist/src/shared/index.d.ts +12 -0
- package/dist/src/shared/index.d.ts.map +1 -0
- package/dist/src/shared/index.js +12 -0
- package/dist/src/shared/index.js.map +1 -0
- package/dist/src/shared/persistence.d.ts +57 -0
- package/dist/src/shared/persistence.d.ts.map +1 -0
- package/dist/src/shared/persistence.js +168 -0
- package/dist/src/shared/persistence.js.map +1 -0
- package/dist/src/shared/shared-context-state.d.ts +89 -0
- package/dist/src/shared/shared-context-state.d.ts.map +1 -0
- package/dist/src/shared/shared-context-state.js +175 -0
- package/dist/src/shared/shared-context-state.js.map +1 -0
- package/dist/src/shared/shared-economics-state.d.ts +61 -0
- package/dist/src/shared/shared-economics-state.d.ts.map +1 -0
- package/dist/src/shared/shared-economics-state.js +100 -0
- package/dist/src/shared/shared-economics-state.js.map +1 -0
- package/dist/src/tools/bash.d.ts +3 -3
- package/dist/src/tools/bash.d.ts.map +1 -1
- package/dist/src/tools/bash.js +2 -1
- package/dist/src/tools/bash.js.map +1 -1
- package/dist/src/tools/file.d.ts +3 -3
- package/dist/src/tools/permission.d.ts.map +1 -1
- package/dist/src/tools/permission.js +6 -5
- package/dist/src/tools/permission.js.map +1 -1
- package/dist/src/tools/types.d.ts +1 -0
- package/dist/src/tools/types.d.ts.map +1 -1
- package/dist/src/tools/types.js.map +1 -1
- package/dist/src/tracing/trace-collector.d.ts +125 -0
- package/dist/src/tracing/trace-collector.d.ts.map +1 -1
- package/dist/src/tracing/trace-collector.js +112 -5
- package/dist/src/tracing/trace-collector.js.map +1 -1
- package/dist/src/tracing/types.d.ts +96 -1
- package/dist/src/tracing/types.d.ts.map +1 -1
- package/dist/src/tracing/types.js.map +1 -1
- package/dist/src/tricks/failure-evidence.d.ts.map +1 -1
- package/dist/src/tricks/failure-evidence.js +2 -1
- package/dist/src/tricks/failure-evidence.js.map +1 -1
- package/dist/src/tui/app.d.ts +13 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +129 -15
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/src/tui/components/ErrorBoundary.js +3 -2
- package/dist/src/tui/components/ErrorBoundary.js.map +1 -1
- package/dist/src/tui/event-display.d.ts.map +1 -1
- package/dist/src/tui/event-display.js +36 -62
- package/dist/src/tui/event-display.js.map +1 -1
- package/dist/src/tui/index.d.ts +4 -0
- package/dist/src/tui/index.d.ts.map +1 -1
- package/dist/src/tui/index.js +17 -0
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/types.d.ts +143 -1
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +18 -3
|
@@ -27,6 +27,10 @@ import { createSwarmWorkerPool } from './worker-pool.js';
|
|
|
27
27
|
import { evaluateWorkerOutput, runPreFlightChecks, checkArtifacts, checkArtifactsEnhanced, runConcreteChecks } from './swarm-quality-gate.js';
|
|
28
28
|
import { ModelHealthTracker, selectAlternativeModel } from './model-selector.js';
|
|
29
29
|
import { SwarmStateStore } from './swarm-state-store.js';
|
|
30
|
+
import { createSharedContextState } from '../../shared/shared-context-state.js';
|
|
31
|
+
import { createSharedEconomicsState } from '../../shared/shared-economics-state.js';
|
|
32
|
+
import { createSharedContextEngine } from '../../shared/context-engine.js';
|
|
33
|
+
import { classifySwarmFailure } from './failure-classifier.js';
|
|
30
34
|
// ─── Hollow Completion Detection ──────────────────────────────────────────
|
|
31
35
|
/**
|
|
32
36
|
* V11: Hollow completion detection — catches empty completions AND "success" with failure language.
|
|
@@ -45,6 +49,35 @@ const BOILERPLATE_INDICATORS = [
|
|
|
45
49
|
'the task has been completed', 'done', 'completed', 'finished',
|
|
46
50
|
'no issues found', 'everything looks good', 'all tasks completed',
|
|
47
51
|
];
|
|
52
|
+
function hasFutureIntentLanguage(content) {
|
|
53
|
+
const trimmed = content.trim();
|
|
54
|
+
if (!trimmed)
|
|
55
|
+
return false;
|
|
56
|
+
const lower = trimmed.toLowerCase();
|
|
57
|
+
const completionSignals = /\b(done|completed|finished|created|saved|wrote|implemented|fixed|updated|added)\b/;
|
|
58
|
+
if (completionSignals.test(lower))
|
|
59
|
+
return false;
|
|
60
|
+
const futureIntentPatterns = [
|
|
61
|
+
/\b(i\s+will|i'll|let me)\s+(create|write|save|update|modify|fix|add|edit|implement|change|run|execute|build|continue)\b/,
|
|
62
|
+
/\b(i\s+need to|i\s+should|i\s+can)\s+(create|write|update|modify|fix|add|edit|implement|continue)\b/,
|
|
63
|
+
/\b(next step|remaining work|still need|to be done)\b/,
|
|
64
|
+
/\b(i am going to|i'm going to)\b/,
|
|
65
|
+
];
|
|
66
|
+
return futureIntentPatterns.some(p => p.test(lower));
|
|
67
|
+
}
|
|
68
|
+
function repoLooksUnscaffolded(baseDir) {
|
|
69
|
+
try {
|
|
70
|
+
const packageJson = path.join(baseDir, 'package.json');
|
|
71
|
+
const srcDir = path.join(baseDir, 'src');
|
|
72
|
+
if (!fs.existsSync(packageJson) && !fs.existsSync(srcDir)) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
48
81
|
export function isHollowCompletion(spawnResult, taskType, swarmConfig) {
|
|
49
82
|
// Timeout uses toolCalls === -1, not hollow
|
|
50
83
|
if ((spawnResult.metrics.toolCalls ?? 0) === -1)
|
|
@@ -86,6 +119,10 @@ export class SwarmOrchestrator {
|
|
|
86
119
|
config;
|
|
87
120
|
provider;
|
|
88
121
|
blackboard;
|
|
122
|
+
// Phase 3.1+3.2: Shared state for cross-worker learning
|
|
123
|
+
sharedContextState;
|
|
124
|
+
sharedEconomicsState;
|
|
125
|
+
sharedContextEngine;
|
|
89
126
|
taskQueue;
|
|
90
127
|
budgetPool;
|
|
91
128
|
workerPool;
|
|
@@ -149,15 +186,28 @@ export class SwarmOrchestrator {
|
|
|
149
186
|
this.spawnAgentFn = spawnAgentFn;
|
|
150
187
|
this.healthTracker = new ModelHealthTracker();
|
|
151
188
|
this.adaptiveStaggerMs = this.getStaggerMs();
|
|
189
|
+
// Phase 3.1+3.2: Shared context & economics for cross-worker learning
|
|
190
|
+
this.sharedContextState = createSharedContextState({
|
|
191
|
+
staticPrefix: 'You are a swarm worker agent.',
|
|
192
|
+
maxFailures: 100,
|
|
193
|
+
maxReferences: 200,
|
|
194
|
+
});
|
|
195
|
+
this.sharedEconomicsState = createSharedEconomicsState({
|
|
196
|
+
globalDoomLoopThreshold: 10,
|
|
197
|
+
});
|
|
198
|
+
this.sharedContextEngine = createSharedContextEngine(this.sharedContextState, {
|
|
199
|
+
maxFailuresInPrompt: 5,
|
|
200
|
+
includeInsights: true,
|
|
201
|
+
});
|
|
152
202
|
this.taskQueue = createSwarmTaskQueue();
|
|
153
203
|
this.budgetPool = createSwarmBudgetPool(this.config);
|
|
154
|
-
this.workerPool = createSwarmWorkerPool(this.config, agentRegistry, spawnAgentFn, this.budgetPool, this.healthTracker);
|
|
204
|
+
this.workerPool = createSwarmWorkerPool(this.config, agentRegistry, spawnAgentFn, this.budgetPool, this.healthTracker, this.sharedContextEngine);
|
|
155
205
|
// Initialize state store if persistence enabled
|
|
156
206
|
if (this.config.enablePersistence) {
|
|
157
207
|
this.stateStore = new SwarmStateStore(this.config.stateDir ?? '.agent/swarm-state', this.config.resumeSessionId);
|
|
158
208
|
}
|
|
159
209
|
// C1: Build LLM decompose function with explicit JSON schema
|
|
160
|
-
const llmDecompose = async (task,
|
|
210
|
+
const llmDecompose = async (task, context) => {
|
|
161
211
|
// V7: Dynamically build the allowed type list from built-in + user-defined types
|
|
162
212
|
const builtinTypes = ['research', 'analysis', 'design', 'implement', 'test', 'refactor', 'review', 'document', 'integrate', 'deploy', 'merge'];
|
|
163
213
|
const customTypes = Object.keys(this.config.taskTypes ?? {}).filter(t => !builtinTypes.includes(t));
|
|
@@ -179,6 +229,26 @@ export class SwarmOrchestrator {
|
|
|
179
229
|
}).join('\n');
|
|
180
230
|
customTypeSection = `\n\nCustom task types available:\n${descriptions}\nUse these when their description matches the subtask's purpose.`;
|
|
181
231
|
}
|
|
232
|
+
// Build codebase context section from repo map if available
|
|
233
|
+
let codebaseSection = '';
|
|
234
|
+
if (context.repoMap) {
|
|
235
|
+
const map = context.repoMap;
|
|
236
|
+
const topFiles = Array.from(map.chunks.values())
|
|
237
|
+
.sort((a, b) => b.importance - a.importance)
|
|
238
|
+
.slice(0, 30)
|
|
239
|
+
.map(c => ` - ${c.filePath} (${c.type}, ${c.tokenCount} tokens, importance: ${c.importance.toFixed(2)})`);
|
|
240
|
+
codebaseSection = `
|
|
241
|
+
|
|
242
|
+
CODEBASE STRUCTURE (${map.chunks.size} files, ${map.totalTokens} total tokens):
|
|
243
|
+
Entry points: ${map.entryPoints.slice(0, 5).join(', ')}
|
|
244
|
+
Core modules: ${map.coreModules.slice(0, 5).join(', ')}
|
|
245
|
+
Key files:
|
|
246
|
+
${topFiles.join('\n')}
|
|
247
|
+
|
|
248
|
+
CRITICAL: Your subtasks MUST reference actual files from this codebase.
|
|
249
|
+
Do NOT invent new project scaffolding or create files that don't relate to the existing codebase.
|
|
250
|
+
Decompose the work based on what ALREADY EXISTS in the project.`;
|
|
251
|
+
}
|
|
182
252
|
const systemPrompt = `You are a task decomposition expert. Break down the given task into well-defined subtasks with clear dependencies.
|
|
183
253
|
|
|
184
254
|
CRITICAL: Dependencies MUST use zero-based integer indices referring to other subtasks in the array.
|
|
@@ -197,7 +267,7 @@ Respond with valid JSON matching this exact schema:
|
|
|
197
267
|
],
|
|
198
268
|
"strategy": "sequential" | "parallel" | "hierarchical" | "adaptive" | "pipeline",
|
|
199
269
|
"reasoning": "Brief explanation of why this decomposition was chosen"
|
|
200
|
-
}${customTypeSection}
|
|
270
|
+
}${customTypeSection}${codebaseSection}
|
|
201
271
|
|
|
202
272
|
EXAMPLE 1 — Research task (3 parallel research + 1 merge):
|
|
203
273
|
{
|
|
@@ -234,12 +304,52 @@ Rules:
|
|
|
234
304
|
{ role: 'user', content: task },
|
|
235
305
|
], {
|
|
236
306
|
model: this.config.orchestratorModel,
|
|
237
|
-
maxTokens:
|
|
307
|
+
maxTokens: 16000,
|
|
238
308
|
temperature: 0.3,
|
|
239
309
|
});
|
|
240
310
|
this.trackOrchestratorUsage(response, 'decompose');
|
|
241
311
|
// Use parseDecompositionResponse which handles markdown code blocks and edge cases
|
|
242
|
-
|
|
312
|
+
const result = parseDecompositionResponse(response.content);
|
|
313
|
+
// If decomposition returned 0 subtasks, log diagnostics and retry with explicit JSON instruction
|
|
314
|
+
if (result.subtasks.length === 0) {
|
|
315
|
+
const snippet = response.content?.slice(0, 500) ?? '(empty response)';
|
|
316
|
+
const parseError = result.parseError ?? 'unknown';
|
|
317
|
+
this.errors.push({
|
|
318
|
+
phase: 'decomposition',
|
|
319
|
+
message: `LLM returned no subtasks. Parse error: ${parseError}. Response preview: ${snippet}`,
|
|
320
|
+
recovered: true,
|
|
321
|
+
});
|
|
322
|
+
this.emit({
|
|
323
|
+
type: 'swarm.orchestrator.decision',
|
|
324
|
+
decision: {
|
|
325
|
+
timestamp: Date.now(),
|
|
326
|
+
phase: 'decomposition',
|
|
327
|
+
decision: `Empty decomposition — retrying with explicit JSON instruction`,
|
|
328
|
+
reasoning: `Parse error: ${parseError}. Response preview (first 500 chars): ${snippet}`,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
// Retry with explicit JSON instruction — don't include previous truncated response (wastes input tokens)
|
|
332
|
+
const retryResponse = await this.provider.chat([
|
|
333
|
+
{ role: 'system', content: systemPrompt },
|
|
334
|
+
{ role: 'user', content: `${task}\n\nIMPORTANT: Your previous attempt was truncated or could not be parsed (${parseError}). Return ONLY a raw JSON object with NO markdown formatting, NO explanation text, NO code fences. The JSON must have a "subtasks" array with at least 2 entries matching the schema above. Keep subtask descriptions concise to avoid truncation.` },
|
|
335
|
+
], {
|
|
336
|
+
model: this.config.orchestratorModel,
|
|
337
|
+
maxTokens: 16000,
|
|
338
|
+
temperature: 0.2,
|
|
339
|
+
});
|
|
340
|
+
this.trackOrchestratorUsage(retryResponse, 'decompose-retry');
|
|
341
|
+
const retryResult = parseDecompositionResponse(retryResponse.content);
|
|
342
|
+
if (retryResult.subtasks.length === 0) {
|
|
343
|
+
const retrySnippet = retryResponse.content?.slice(0, 500) ?? '(empty response)';
|
|
344
|
+
this.errors.push({
|
|
345
|
+
phase: 'decomposition',
|
|
346
|
+
message: `Retry also returned no subtasks. Response preview: ${retrySnippet}`,
|
|
347
|
+
recovered: false,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return retryResult;
|
|
351
|
+
}
|
|
352
|
+
return result;
|
|
243
353
|
};
|
|
244
354
|
// Configure decomposer for swarm use
|
|
245
355
|
const decomposer = createSmartDecomposer({
|
|
@@ -257,6 +367,18 @@ Rules:
|
|
|
257
367
|
getBudgetPool() {
|
|
258
368
|
return this.budgetPool;
|
|
259
369
|
}
|
|
370
|
+
/** Get shared context state for cross-worker failure learning. */
|
|
371
|
+
getSharedContextState() {
|
|
372
|
+
return this.sharedContextState;
|
|
373
|
+
}
|
|
374
|
+
/** Get shared economics state for cross-worker doom loop aggregation. */
|
|
375
|
+
getSharedEconomicsState() {
|
|
376
|
+
return this.sharedEconomicsState;
|
|
377
|
+
}
|
|
378
|
+
/** Get shared context engine for cross-worker failure learning. */
|
|
379
|
+
getSharedContextEngine() {
|
|
380
|
+
return this.sharedContextEngine;
|
|
381
|
+
}
|
|
260
382
|
/**
|
|
261
383
|
* Subscribe to swarm events.
|
|
262
384
|
*/
|
|
@@ -276,8 +398,12 @@ Rules:
|
|
|
276
398
|
try {
|
|
277
399
|
listener(event);
|
|
278
400
|
}
|
|
279
|
-
catch {
|
|
280
|
-
// Don't let listener errors break the orchestrator
|
|
401
|
+
catch (err) {
|
|
402
|
+
// Don't let listener errors break the orchestrator, but log for debugging
|
|
403
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
404
|
+
if (process.env.DEBUG) {
|
|
405
|
+
console.error(`[SwarmOrchestrator] Listener error on ${event.type}: ${msg}`);
|
|
406
|
+
}
|
|
281
407
|
}
|
|
282
408
|
}
|
|
283
409
|
}
|
|
@@ -287,8 +413,12 @@ Rules:
|
|
|
287
413
|
trackOrchestratorUsage(response, purpose) {
|
|
288
414
|
if (!response.usage)
|
|
289
415
|
return;
|
|
290
|
-
|
|
291
|
-
|
|
416
|
+
// Handle both raw API fields (total_tokens, prompt_tokens, completion_tokens)
|
|
417
|
+
// and ChatResponse fields (inputTokens, outputTokens)
|
|
418
|
+
const input = response.usage.prompt_tokens ?? response.usage.inputTokens ?? 0;
|
|
419
|
+
const output = response.usage.completion_tokens ?? response.usage.outputTokens ?? 0;
|
|
420
|
+
const tokens = response.usage.total_tokens ?? (input + output);
|
|
421
|
+
const cost = response.usage.cost ?? tokens * 0.000015; // ~$15/M tokens average for orchestrator models
|
|
292
422
|
this.orchestratorTokens += tokens;
|
|
293
423
|
this.orchestratorCost += cost;
|
|
294
424
|
this.orchestratorCalls++;
|
|
@@ -325,10 +455,26 @@ Rules:
|
|
|
325
455
|
// Phase 1: Decompose
|
|
326
456
|
this.currentPhase = 'decomposing';
|
|
327
457
|
this.emit({ type: 'swarm.phase.progress', phase: 'decomposing', message: 'Decomposing task into subtasks...' });
|
|
328
|
-
|
|
329
|
-
if (!
|
|
458
|
+
const decomposeOutcome = await this.decompose(task);
|
|
459
|
+
if (!decomposeOutcome.result) {
|
|
330
460
|
this.currentPhase = 'failed';
|
|
331
|
-
return this.buildErrorResult(
|
|
461
|
+
return this.buildErrorResult(`Decomposition failed: ${decomposeOutcome.failureReason}`);
|
|
462
|
+
}
|
|
463
|
+
let decomposition = decomposeOutcome.result;
|
|
464
|
+
// If repository is mostly empty, force a scaffold-first dependency chain
|
|
465
|
+
// so implementation tasks don't immediately fail on missing files.
|
|
466
|
+
if (repoLooksUnscaffolded(this.config.facts?.workingDirectory ?? process.cwd())) {
|
|
467
|
+
const scaffoldTask = decomposition.subtasks.find(st => /\b(scaffold|bootstrap|initialize|setup|set up|project scaffold)\b/i.test(st.description));
|
|
468
|
+
if (scaffoldTask) {
|
|
469
|
+
for (const subtask of decomposition.subtasks) {
|
|
470
|
+
if (subtask.id === scaffoldTask.id)
|
|
471
|
+
continue;
|
|
472
|
+
if (!subtask.dependencies.includes(scaffoldTask.id)) {
|
|
473
|
+
subtask.dependencies.push(scaffoldTask.id);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
this.logDecision('scaffold-first', `Repo appears unscaffolded; enforcing scaffold task ${scaffoldTask.id} as prerequisite`, '');
|
|
477
|
+
}
|
|
332
478
|
}
|
|
333
479
|
// F5: Validate decomposition — check for cycles, invalid deps, granularity
|
|
334
480
|
const validation = validateDecomposition(decomposition);
|
|
@@ -338,11 +484,12 @@ Rules:
|
|
|
338
484
|
if (!validation.valid) {
|
|
339
485
|
this.logDecision('decomposition-validation', `Invalid decomposition: ${validation.issues.join('; ')}`, 'Retrying...');
|
|
340
486
|
// Retry decomposition once with feedback
|
|
341
|
-
|
|
342
|
-
if (!
|
|
487
|
+
const retryOutcome = await this.decompose(`${task}\n\nIMPORTANT: Previous decomposition was invalid: ${validation.issues.join('. ')}. Fix these issues.`);
|
|
488
|
+
if (!retryOutcome.result) {
|
|
343
489
|
this.currentPhase = 'failed';
|
|
344
490
|
return this.buildErrorResult(`Decomposition validation failed: ${validation.issues.join('; ')}`);
|
|
345
491
|
}
|
|
492
|
+
decomposition = retryOutcome.result;
|
|
346
493
|
const retryValidation = validateDecomposition(decomposition);
|
|
347
494
|
if (!retryValidation.valid) {
|
|
348
495
|
this.logDecision('decomposition-validation', `Retry still invalid: ${retryValidation.issues.join('; ')}`, 'Proceeding anyway');
|
|
@@ -463,9 +610,15 @@ Rules:
|
|
|
463
610
|
this.checkpoint('final');
|
|
464
611
|
const hasArtifacts = (this.artifactInventory?.totalFiles ?? 0) > 0;
|
|
465
612
|
this.emit({ type: 'swarm.complete', stats: executionStats, errors: this.errors, artifactInventory: this.artifactInventory });
|
|
613
|
+
// Success requires completing at least 70% of tasks (not just > 0)
|
|
614
|
+
const completionRatio = executionStats.totalTasks > 0
|
|
615
|
+
? executionStats.completedTasks / executionStats.totalTasks
|
|
616
|
+
: 0;
|
|
617
|
+
const isSuccess = completionRatio >= 0.7;
|
|
618
|
+
const isPartialSuccess = !isSuccess && executionStats.completedTasks > 0;
|
|
466
619
|
return {
|
|
467
|
-
success:
|
|
468
|
-
partialSuccess: !executionStats.completedTasks && hasArtifacts,
|
|
620
|
+
success: isSuccess,
|
|
621
|
+
partialSuccess: isPartialSuccess || (!executionStats.completedTasks && hasArtifacts),
|
|
469
622
|
partialFailure: executionStats.failedTasks > 0,
|
|
470
623
|
synthesisResult: synthesisResult ?? undefined,
|
|
471
624
|
artifactInventory: this.artifactInventory,
|
|
@@ -495,33 +648,234 @@ Rules:
|
|
|
495
648
|
*/
|
|
496
649
|
async decompose(task) {
|
|
497
650
|
try {
|
|
498
|
-
const
|
|
651
|
+
const repoMap = this.config.codebaseContext?.getRepoMap() ?? undefined;
|
|
652
|
+
const result = await this._decomposer.decompose(task, {
|
|
653
|
+
repoMap,
|
|
654
|
+
});
|
|
499
655
|
if (result.subtasks.length < 2) {
|
|
500
|
-
|
|
501
|
-
|
|
656
|
+
const reason = result.subtasks.length === 0
|
|
657
|
+
? `Decomposition produced 0 subtasks (model: ${this.config.orchestratorModel}).`
|
|
658
|
+
: `Decomposition produced only ${result.subtasks.length} subtask — too few for swarm mode.`;
|
|
659
|
+
this.logDecision('decomposition', `Insufficient subtasks: ${result.subtasks.length}`, reason);
|
|
660
|
+
try {
|
|
661
|
+
const lastResortResult = await this.lastResortDecompose(task);
|
|
662
|
+
if (lastResortResult && lastResortResult.subtasks.length >= 2) {
|
|
663
|
+
this.logDecision('decomposition', `Last-resort decomposition succeeded: ${lastResortResult.subtasks.length} subtasks`, 'Recovered from insufficient primary decomposition');
|
|
664
|
+
return { result: lastResortResult };
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
this.logDecision('decomposition', 'Last-resort decomposition failed after insufficient primary decomposition', error.message);
|
|
669
|
+
}
|
|
670
|
+
const fallback = this.buildEmergencyDecomposition(task, reason);
|
|
671
|
+
this.emit({
|
|
672
|
+
type: 'swarm.phase.progress',
|
|
673
|
+
phase: 'decomposing',
|
|
674
|
+
message: `Using emergency decomposition fallback (${this.classifyDecompositionFailure(reason)})`,
|
|
675
|
+
});
|
|
676
|
+
this.logDecision('decomposition', `Using emergency scaffold decomposition: ${fallback.subtasks.length} subtasks`, 'Swarm will continue with deterministic fallback tasks');
|
|
677
|
+
return { result: fallback };
|
|
502
678
|
}
|
|
503
|
-
//
|
|
679
|
+
// Non-LLM result means decomposer fell back to heuristic mode.
|
|
680
|
+
// Prefer a simplified LLM decomposition, but continue with heuristic fallback when needed.
|
|
504
681
|
if (!result.metadata.llmAssisted) {
|
|
505
|
-
this.logDecision('decomposition', '
|
|
506
|
-
|
|
682
|
+
this.logDecision('decomposition', 'Heuristic decomposition detected — attempting last-resort simplified LLM decomposition', `Model: ${this.config.orchestratorModel}`);
|
|
683
|
+
try {
|
|
684
|
+
const lastResortResult = await this.lastResortDecompose(task);
|
|
685
|
+
if (lastResortResult && lastResortResult.subtasks.length >= 2) {
|
|
686
|
+
this.logDecision('decomposition', `Last-resort decomposition succeeded: ${lastResortResult.subtasks.length} subtasks`, 'Simplified prompt worked');
|
|
687
|
+
return { result: lastResortResult };
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
this.logDecision('decomposition', 'Last-resort decomposition also failed', error.message);
|
|
692
|
+
}
|
|
693
|
+
this.logDecision('decomposition', `Continuing with heuristic decomposition: ${result.subtasks.length} subtasks`, 'Fallback is acceptable; do not abort swarm');
|
|
694
|
+
this.emit({
|
|
695
|
+
type: 'swarm.phase.progress',
|
|
696
|
+
phase: 'decomposing',
|
|
697
|
+
message: `Continuing with heuristic decomposition (${this.classifyDecompositionFailure('heuristic fallback')})`,
|
|
698
|
+
});
|
|
699
|
+
return { result };
|
|
507
700
|
}
|
|
508
701
|
// Flat-DAG detection: warn when all tasks land in wave 0 with no dependencies
|
|
509
702
|
const hasAnyDependency = result.subtasks.some(s => s.dependencies.length > 0);
|
|
510
703
|
if (!hasAnyDependency && result.subtasks.length >= 3) {
|
|
511
704
|
this.logDecision('decomposition', `Flat DAG: ${result.subtasks.length} tasks, zero dependencies`, 'All tasks will execute in wave 0 without ordering');
|
|
512
705
|
}
|
|
513
|
-
return result;
|
|
706
|
+
return { result };
|
|
514
707
|
}
|
|
515
708
|
catch (error) {
|
|
709
|
+
const message = error.message;
|
|
516
710
|
this.errors.push({
|
|
517
711
|
phase: 'decomposition',
|
|
518
|
-
message
|
|
519
|
-
recovered:
|
|
712
|
+
message,
|
|
713
|
+
recovered: true,
|
|
520
714
|
});
|
|
521
|
-
this.
|
|
522
|
-
|
|
715
|
+
const fallback = this.buildEmergencyDecomposition(task, `Decomposition threw an error: ${message}`);
|
|
716
|
+
this.emit({
|
|
717
|
+
type: 'swarm.phase.progress',
|
|
718
|
+
phase: 'decomposing',
|
|
719
|
+
message: `Decomposition fallback due to ${this.classifyDecompositionFailure(message)}`,
|
|
720
|
+
});
|
|
721
|
+
this.logDecision('decomposition', `Decomposition threw error; using emergency scaffold decomposition (${fallback.subtasks.length} subtasks)`, message);
|
|
722
|
+
return { result: fallback };
|
|
523
723
|
}
|
|
524
724
|
}
|
|
725
|
+
classifyDecompositionFailure(message) {
|
|
726
|
+
const m = message.toLowerCase();
|
|
727
|
+
if (m.includes('429') || m.includes('too many requests') || m.includes('rate limit')) {
|
|
728
|
+
return 'rate_limit';
|
|
729
|
+
}
|
|
730
|
+
if (m.includes('402') || m.includes('spend limit') || m.includes('key limit exceeded') || m.includes('insufficient credits')) {
|
|
731
|
+
return 'provider_budget_limit';
|
|
732
|
+
}
|
|
733
|
+
if (m.includes('parse') || m.includes('json') || m.includes('subtasks')) {
|
|
734
|
+
return 'parse_failure';
|
|
735
|
+
}
|
|
736
|
+
if (m.includes('invalid') || m.includes('validation')) {
|
|
737
|
+
return 'validation_failure';
|
|
738
|
+
}
|
|
739
|
+
return 'other';
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Deterministic decomposition fallback when all LLM decomposition paths fail.
|
|
743
|
+
* Keeps swarm mode alive with visible scaffolding tasks instead of aborting.
|
|
744
|
+
*/
|
|
745
|
+
buildEmergencyDecomposition(task, reason) {
|
|
746
|
+
const normalizer = createSmartDecomposer({ detectConflicts: true });
|
|
747
|
+
const taskLabel = task.trim().slice(0, 140) || 'requested task';
|
|
748
|
+
const repoMap = this.config.codebaseContext?.getRepoMap();
|
|
749
|
+
const topFiles = repoMap
|
|
750
|
+
? Array.from(repoMap.chunks.values())
|
|
751
|
+
.sort((a, b) => b.importance - a.importance)
|
|
752
|
+
.slice(0, 10)
|
|
753
|
+
.map(c => c.filePath)
|
|
754
|
+
: [];
|
|
755
|
+
const subtasks = [
|
|
756
|
+
{
|
|
757
|
+
id: 'task-fb-0',
|
|
758
|
+
description: `Scaffold implementation plan and identify target files for: ${taskLabel}`,
|
|
759
|
+
status: 'ready',
|
|
760
|
+
dependencies: [],
|
|
761
|
+
complexity: 2,
|
|
762
|
+
type: 'design',
|
|
763
|
+
parallelizable: true,
|
|
764
|
+
relevantFiles: topFiles.slice(0, 5),
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
id: 'task-fb-1',
|
|
768
|
+
description: `Implement core code changes for: ${taskLabel}`,
|
|
769
|
+
status: 'blocked',
|
|
770
|
+
dependencies: ['task-fb-0'],
|
|
771
|
+
complexity: 5,
|
|
772
|
+
type: 'implement',
|
|
773
|
+
parallelizable: false,
|
|
774
|
+
relevantFiles: topFiles.slice(0, 8),
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
id: 'task-fb-2',
|
|
778
|
+
description: `Add or update tests and run validation for: ${taskLabel}`,
|
|
779
|
+
status: 'blocked',
|
|
780
|
+
dependencies: ['task-fb-1'],
|
|
781
|
+
complexity: 3,
|
|
782
|
+
type: 'test',
|
|
783
|
+
parallelizable: false,
|
|
784
|
+
relevantFiles: topFiles.slice(0, 8),
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
id: 'task-fb-3',
|
|
788
|
+
description: `Integrate results and produce final summary for: ${taskLabel}`,
|
|
789
|
+
status: 'blocked',
|
|
790
|
+
dependencies: ['task-fb-1', 'task-fb-2'],
|
|
791
|
+
complexity: 2,
|
|
792
|
+
type: 'integrate',
|
|
793
|
+
parallelizable: false,
|
|
794
|
+
relevantFiles: topFiles.slice(0, 5),
|
|
795
|
+
},
|
|
796
|
+
];
|
|
797
|
+
const dependencyGraph = normalizer.buildDependencyGraph(subtasks);
|
|
798
|
+
const conflicts = normalizer.detectConflicts(subtasks);
|
|
799
|
+
return {
|
|
800
|
+
originalTask: task,
|
|
801
|
+
subtasks,
|
|
802
|
+
dependencyGraph,
|
|
803
|
+
conflicts,
|
|
804
|
+
strategy: 'adaptive',
|
|
805
|
+
totalComplexity: subtasks.reduce((sum, s) => sum + s.complexity, 0),
|
|
806
|
+
totalEstimatedTokens: subtasks.length * 4000,
|
|
807
|
+
metadata: {
|
|
808
|
+
decomposedAt: new Date(),
|
|
809
|
+
codebaseAware: !!repoMap,
|
|
810
|
+
llmAssisted: false,
|
|
811
|
+
},
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Last-resort decomposition: radically simplified prompt that even weak models can handle.
|
|
816
|
+
* Uses shorter context, no examples, minimal schema, and lower maxTokens to avoid truncation.
|
|
817
|
+
*/
|
|
818
|
+
async lastResortDecompose(task) {
|
|
819
|
+
// Include codebase grounding if repo map is available
|
|
820
|
+
let codebaseHint = '';
|
|
821
|
+
const repoMap = this.config.codebaseContext?.getRepoMap();
|
|
822
|
+
if (repoMap) {
|
|
823
|
+
const topFiles = Array.from(repoMap.chunks.values())
|
|
824
|
+
.sort((a, b) => b.importance - a.importance)
|
|
825
|
+
.slice(0, 10)
|
|
826
|
+
.map(c => c.filePath);
|
|
827
|
+
codebaseHint = `\nKey project files: ${topFiles.join(', ')}\nReference actual files in subtask descriptions.`;
|
|
828
|
+
}
|
|
829
|
+
const simplifiedPrompt = `Break this task into 2-6 subtasks. Return ONLY raw JSON, no markdown.
|
|
830
|
+
|
|
831
|
+
{"subtasks":[{"description":"...","type":"implement","complexity":3,"dependencies":[],"parallelizable":true,"relevantFiles":["src/..."]}],"strategy":"adaptive","reasoning":"..."}
|
|
832
|
+
|
|
833
|
+
Rules:
|
|
834
|
+
- dependencies: integer indices (e.g. [0] means depends on first subtask)
|
|
835
|
+
- type: one of research/implement/test/design/refactor/integrate/merge
|
|
836
|
+
- At least 2 subtasks${codebaseHint}`;
|
|
837
|
+
const response = await this.provider.chat([
|
|
838
|
+
{ role: 'system', content: simplifiedPrompt },
|
|
839
|
+
{ role: 'user', content: task },
|
|
840
|
+
], {
|
|
841
|
+
model: this.config.orchestratorModel,
|
|
842
|
+
maxTokens: 4096, // Short — avoids truncation
|
|
843
|
+
temperature: 0.1, // Very deterministic
|
|
844
|
+
});
|
|
845
|
+
this.trackOrchestratorUsage(response, 'decompose-last-resort');
|
|
846
|
+
const parsed = parseDecompositionResponse(response.content);
|
|
847
|
+
if (parsed.subtasks.length < 2)
|
|
848
|
+
return null;
|
|
849
|
+
// Build a proper SmartDecompositionResult from the parsed LLM output
|
|
850
|
+
const decomposer = createSmartDecomposer({ detectConflicts: true });
|
|
851
|
+
const subtasks = parsed.subtasks.map((s, index) => ({
|
|
852
|
+
id: `task-lr-${index}`,
|
|
853
|
+
description: s.description,
|
|
854
|
+
status: (s.dependencies.length > 0 ? 'blocked' : 'ready'),
|
|
855
|
+
dependencies: s.dependencies.map((d) => `task-lr-${d}`),
|
|
856
|
+
complexity: s.complexity,
|
|
857
|
+
type: s.type,
|
|
858
|
+
parallelizable: s.parallelizable,
|
|
859
|
+
relevantFiles: s.relevantFiles,
|
|
860
|
+
suggestedRole: s.suggestedRole,
|
|
861
|
+
}));
|
|
862
|
+
const dependencyGraph = decomposer.buildDependencyGraph(subtasks);
|
|
863
|
+
const conflicts = decomposer.detectConflicts(subtasks);
|
|
864
|
+
return {
|
|
865
|
+
originalTask: task,
|
|
866
|
+
subtasks,
|
|
867
|
+
dependencyGraph,
|
|
868
|
+
conflicts,
|
|
869
|
+
strategy: parsed.strategy,
|
|
870
|
+
totalComplexity: subtasks.reduce((sum, t) => sum + t.complexity, 0),
|
|
871
|
+
totalEstimatedTokens: subtasks.length * 5000,
|
|
872
|
+
metadata: {
|
|
873
|
+
decomposedAt: new Date(),
|
|
874
|
+
codebaseAware: false,
|
|
875
|
+
llmAssisted: true, // This IS LLM-assisted, just simplified
|
|
876
|
+
},
|
|
877
|
+
};
|
|
878
|
+
}
|
|
525
879
|
// ─── V2: Planning Phase ───────────────────────────────────────────────
|
|
526
880
|
/**
|
|
527
881
|
* Create acceptance criteria and integration test plan.
|
|
@@ -828,6 +1182,13 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
828
1182
|
this.totalCost = checkpoint.stats.totalCost;
|
|
829
1183
|
this.qualityRejections = checkpoint.stats.qualityRejections;
|
|
830
1184
|
this.retries = checkpoint.stats.retries;
|
|
1185
|
+
// Restore shared context & economics state from checkpoint
|
|
1186
|
+
if (checkpoint.sharedContext) {
|
|
1187
|
+
this.sharedContextState.restoreFrom(checkpoint.sharedContext);
|
|
1188
|
+
}
|
|
1189
|
+
if (checkpoint.sharedEconomics) {
|
|
1190
|
+
this.sharedEconomicsState.restoreFrom(checkpoint.sharedEconomics);
|
|
1191
|
+
}
|
|
831
1192
|
// Restore task queue
|
|
832
1193
|
this.taskQueue.restoreFromCheckpoint({
|
|
833
1194
|
taskStates: checkpoint.taskStates,
|
|
@@ -835,14 +1196,17 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
835
1196
|
currentWave: checkpoint.currentWave,
|
|
836
1197
|
});
|
|
837
1198
|
// Reset orphaned dispatched tasks — their workers died with the previous process
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1199
|
+
const resetIds = this.taskQueue.reconcileStaleDispatched({
|
|
1200
|
+
staleAfterMs: 0,
|
|
1201
|
+
activeTaskIds: new Set(),
|
|
1202
|
+
});
|
|
1203
|
+
const resetCount = resetIds.length;
|
|
1204
|
+
for (const taskId of resetIds) {
|
|
1205
|
+
const task = this.taskQueue.getTask(taskId);
|
|
1206
|
+
if (!task)
|
|
1207
|
+
continue;
|
|
1208
|
+
// Preserve at least 1 retry attempt
|
|
1209
|
+
task.attempts = Math.min(task.attempts, Math.max(0, this.config.workerRetries - 1));
|
|
846
1210
|
}
|
|
847
1211
|
if (resetCount > 0) {
|
|
848
1212
|
this.logDecision('resume', `Reset ${resetCount} orphaned dispatched tasks to ready`, 'Workers died with previous process');
|
|
@@ -904,9 +1268,15 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
904
1268
|
this.checkpoint('final');
|
|
905
1269
|
const hasArtifacts = (this.artifactInventory?.totalFiles ?? 0) > 0;
|
|
906
1270
|
this.emit({ type: 'swarm.complete', stats: executionStats, errors: this.errors, artifactInventory: this.artifactInventory });
|
|
1271
|
+
// Success requires completing at least 70% of tasks (not just > 0)
|
|
1272
|
+
const completionRatio = executionStats.totalTasks > 0
|
|
1273
|
+
? executionStats.completedTasks / executionStats.totalTasks
|
|
1274
|
+
: 0;
|
|
1275
|
+
const isSuccess = completionRatio >= 0.7;
|
|
1276
|
+
const isPartialSuccess = !isSuccess && executionStats.completedTasks > 0;
|
|
907
1277
|
return {
|
|
908
|
-
success:
|
|
909
|
-
partialSuccess: !executionStats.completedTasks && hasArtifacts,
|
|
1278
|
+
success: isSuccess,
|
|
1279
|
+
partialSuccess: isPartialSuccess || (!executionStats.completedTasks && hasArtifacts),
|
|
910
1280
|
partialFailure: executionStats.failedTasks > 0,
|
|
911
1281
|
synthesisResult: synthesisResult ?? undefined,
|
|
912
1282
|
artifactInventory: this.artifactInventory,
|
|
@@ -923,7 +1293,16 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
923
1293
|
async executeWaves() {
|
|
924
1294
|
let waveIndex = this.taskQueue.getCurrentWave();
|
|
925
1295
|
const totalWaves = this.taskQueue.getTotalWaves();
|
|
1296
|
+
const dispatchLeaseStaleMs = this.config.dispatchLeaseStaleMs ?? 5 * 60 * 1000;
|
|
926
1297
|
while (waveIndex < totalWaves && !this.cancelled) {
|
|
1298
|
+
const activeTaskIds = new Set(this.workerPool.getActiveWorkerStatus().map(w => w.taskId));
|
|
1299
|
+
const recovered = this.taskQueue.reconcileStaleDispatched({
|
|
1300
|
+
staleAfterMs: dispatchLeaseStaleMs,
|
|
1301
|
+
activeTaskIds,
|
|
1302
|
+
});
|
|
1303
|
+
if (recovered.length > 0) {
|
|
1304
|
+
this.logDecision('lease-recovery', `Recovered ${recovered.length} stale dispatched task(s)`, recovered.join(', '));
|
|
1305
|
+
}
|
|
927
1306
|
const readyTasks = this.taskQueue.getReadyTasks();
|
|
928
1307
|
const queueStats = this.taskQueue.getStats();
|
|
929
1308
|
// F18: Skip empty waves — if no tasks are ready and none are running,
|
|
@@ -1282,6 +1661,10 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1282
1661
|
this.modelUsage.set(model, usage);
|
|
1283
1662
|
this.totalTokens += taskResult.tokensUsed;
|
|
1284
1663
|
this.totalCost += taskResult.costUsed;
|
|
1664
|
+
// Log per-worker budget utilization for orchestrator visibility
|
|
1665
|
+
if (taskResult.budgetUtilization) {
|
|
1666
|
+
this.logDecision('budget-utilization', `${taskId}: token ${taskResult.budgetUtilization.tokenPercent}%, iter ${taskResult.budgetUtilization.iterationPercent}%`, `model=${model}, tokens=${taskResult.tokensUsed}, duration=${durationMs}ms`);
|
|
1667
|
+
}
|
|
1285
1668
|
// V10: Emit per-attempt event for full decision traceability
|
|
1286
1669
|
this.emit({
|
|
1287
1670
|
type: 'swarm.task.attempt',
|
|
@@ -1297,18 +1680,18 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1297
1680
|
});
|
|
1298
1681
|
if (!spawnResult.success) {
|
|
1299
1682
|
// V2: Record model health
|
|
1300
|
-
const
|
|
1301
|
-
const
|
|
1302
|
-
const
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
const
|
|
1683
|
+
const failure = classifySwarmFailure(spawnResult.output, spawnResult.metrics.toolCalls);
|
|
1684
|
+
const { failureClass, retryable, errorType, failureMode, reason } = failure;
|
|
1685
|
+
const isTimeout = failureMode === 'timeout';
|
|
1686
|
+
const isRateLimited = failureClass === 'rate_limited';
|
|
1687
|
+
const isSpendLimit = failureClass === 'provider_spend_limit';
|
|
1688
|
+
const isNonRetryable = !retryable;
|
|
1306
1689
|
this.healthTracker.recordFailure(model, errorType);
|
|
1307
1690
|
this.emit({ type: 'swarm.model.health', record: { model, ...this.getModelHealthSummary(model) } });
|
|
1308
1691
|
// P6: Tag failure mode for cascade threshold awareness
|
|
1309
|
-
task.failureMode =
|
|
1310
|
-
// Feed circuit breaker
|
|
1311
|
-
if (
|
|
1692
|
+
task.failureMode = failureMode;
|
|
1693
|
+
// Feed circuit breaker only for retryable rate limiting
|
|
1694
|
+
if (isRateLimited) {
|
|
1312
1695
|
this.recordRateLimit();
|
|
1313
1696
|
}
|
|
1314
1697
|
// F25a: Consecutive timeout tracking — early-fail after N consecutive timeouts
|
|
@@ -1356,6 +1739,9 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1356
1739
|
maxAttempts: maxDispatches,
|
|
1357
1740
|
willRetry: false,
|
|
1358
1741
|
failureMode: 'timeout',
|
|
1742
|
+
failureClass: 'timeout',
|
|
1743
|
+
retrySuppressed: true,
|
|
1744
|
+
retryReason: 'Consecutive timeout limit reached with no alternative model',
|
|
1359
1745
|
});
|
|
1360
1746
|
this.logDecision('timeout-early-fail', `${taskId}: ${count} consecutive timeouts, no alt model — resilience recovery also failed`, '');
|
|
1361
1747
|
this.taskTimeoutCounts.delete(taskId);
|
|
@@ -1367,8 +1753,8 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1367
1753
|
// Non-timeout failure — reset the counter
|
|
1368
1754
|
this.taskTimeoutCounts.delete(taskId);
|
|
1369
1755
|
}
|
|
1370
|
-
// V2: Model failover on rate limits
|
|
1371
|
-
if (
|
|
1756
|
+
// V2: Model failover on retryable rate limits
|
|
1757
|
+
if (isRateLimited && this.config.enableModelFailover) {
|
|
1372
1758
|
const capability = getTaskTypeConfig(task.type, this.config).capability ?? 'code';
|
|
1373
1759
|
const alternative = selectAlternativeModel(this.config.workers, model, capability, this.healthTracker);
|
|
1374
1760
|
if (alternative) {
|
|
@@ -1384,7 +1770,7 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1384
1770
|
}
|
|
1385
1771
|
}
|
|
1386
1772
|
// V5/V7: Store error context so retry gets different prompt
|
|
1387
|
-
if (!(
|
|
1773
|
+
if (!(isRateLimited || isSpendLimit)) {
|
|
1388
1774
|
// V7: Timeout-specific feedback — the worker WAS working, just ran out of time
|
|
1389
1775
|
const timeoutSeconds = isTimeout ? Math.round(durationMs / 1000) : 0;
|
|
1390
1776
|
task.retryContext = {
|
|
@@ -1397,27 +1783,37 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1397
1783
|
previousFiles: taskResult.filesModified,
|
|
1398
1784
|
swarmProgress: this.getSwarmProgressSummary(),
|
|
1399
1785
|
};
|
|
1786
|
+
// Phase 3.1: Report failure to shared context engine for cross-worker learning
|
|
1787
|
+
this.sharedContextEngine.reportFailure(taskId, {
|
|
1788
|
+
action: task.description.slice(0, 200),
|
|
1789
|
+
error: spawnResult.output.slice(0, 500),
|
|
1790
|
+
});
|
|
1400
1791
|
}
|
|
1401
1792
|
// V7: Reset hollow streak on non-hollow failure (error is not a hollow completion)
|
|
1402
1793
|
this.hollowStreak = 0;
|
|
1403
1794
|
// Worker failed — use higher retry limit for rate limit errors.
|
|
1404
1795
|
// V7: Fixup tasks get capped retries, foundation tasks get +1.
|
|
1405
1796
|
const baseRetries = this.getEffectiveRetries(task);
|
|
1406
|
-
const retryLimit =
|
|
1407
|
-
?
|
|
1408
|
-
:
|
|
1797
|
+
const retryLimit = isNonRetryable
|
|
1798
|
+
? 0
|
|
1799
|
+
: isRateLimited
|
|
1800
|
+
? Math.min(this.config.rateLimitRetries ?? 3, baseRetries + 1)
|
|
1801
|
+
: baseRetries;
|
|
1409
1802
|
const canRetry = this.taskQueue.markFailedWithoutCascade(taskId, retryLimit);
|
|
1803
|
+
if (isNonRetryable) {
|
|
1804
|
+
this.logDecision('retry-suppressed', `${taskId}: ${failureClass}`, reason);
|
|
1805
|
+
}
|
|
1410
1806
|
if (canRetry) {
|
|
1411
1807
|
this.retries++;
|
|
1412
1808
|
// Non-blocking cooldown: set retryAfter timestamp instead of blocking
|
|
1413
|
-
if (
|
|
1809
|
+
if (isRateLimited) {
|
|
1414
1810
|
const baseDelay = this.config.retryBaseDelayMs ?? 5000;
|
|
1415
1811
|
const cooldownMs = Math.min(baseDelay * Math.pow(2, task.attempts - 1), 30000);
|
|
1416
1812
|
this.taskQueue.setRetryAfter(taskId, cooldownMs);
|
|
1417
1813
|
this.logDecision('rate-limit-cooldown', `${taskId}: ${errorType} cooldown ${cooldownMs}ms, model ${model}`, '');
|
|
1418
1814
|
}
|
|
1419
1815
|
}
|
|
1420
|
-
else if (!
|
|
1816
|
+
else if (!isRateLimited) {
|
|
1421
1817
|
// Resilience recovery for non-rate-limit errors (micro-decompose + degraded acceptance)
|
|
1422
1818
|
if (await this.tryResilienceRecovery(task, taskId, taskResult, spawnResult)) {
|
|
1423
1819
|
return;
|
|
@@ -1439,6 +1835,9 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1439
1835
|
toolCalls: spawnResult.metrics.toolCalls,
|
|
1440
1836
|
failoverModel: task.assignedModel !== model ? task.assignedModel : undefined,
|
|
1441
1837
|
failureMode: task.failureMode,
|
|
1838
|
+
failureClass,
|
|
1839
|
+
retrySuppressed: isNonRetryable,
|
|
1840
|
+
retryReason: reason,
|
|
1442
1841
|
});
|
|
1443
1842
|
return;
|
|
1444
1843
|
}
|
|
@@ -1470,6 +1869,11 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1470
1869
|
previousFiles: taskResult.filesModified,
|
|
1471
1870
|
swarmProgress: this.getSwarmProgressSummary(),
|
|
1472
1871
|
};
|
|
1872
|
+
// Phase 3.1: Report hollow completion to shared context engine
|
|
1873
|
+
this.sharedContextEngine.reportFailure(taskId, {
|
|
1874
|
+
action: task.description.slice(0, 200),
|
|
1875
|
+
error: 'Hollow completion: worker produced no meaningful output',
|
|
1876
|
+
});
|
|
1473
1877
|
// Model failover for hollow completions — same pattern as quality failover
|
|
1474
1878
|
if (this.config.enableModelFailover) {
|
|
1475
1879
|
const capability = getTaskTypeConfig(task.type, this.config).capability ?? 'code';
|
|
@@ -1704,6 +2108,11 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1704
2108
|
previousFiles: taskResult.filesModified,
|
|
1705
2109
|
swarmProgress: this.getSwarmProgressSummary(),
|
|
1706
2110
|
};
|
|
2111
|
+
// Phase 3.1: Report quality rejection to shared context engine
|
|
2112
|
+
this.sharedContextEngine.reportFailure(taskId, {
|
|
2113
|
+
action: task.description.slice(0, 200),
|
|
2114
|
+
error: `Quality gate rejection (score ${quality.score}): ${quality.feedback.slice(0, 300)}`,
|
|
2115
|
+
});
|
|
1707
2116
|
// V5: Model failover on quality rejection — but NOT on artifact auto-fails
|
|
1708
2117
|
// P1: Widened from score<=1 to score<threshold so failover triggers on any rejection
|
|
1709
2118
|
if (quality.score < qualityThreshold && this.config.enableModelFailover && !quality.artifactAutoFail) {
|
|
@@ -1853,6 +2262,65 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
1853
2262
|
}
|
|
1854
2263
|
}
|
|
1855
2264
|
}
|
|
2265
|
+
// Final completion guard: block "narrative success" for action tasks.
|
|
2266
|
+
const completionGuard = this.config.completionGuard ?? {};
|
|
2267
|
+
const rejectFutureIntentOutputs = completionGuard.rejectFutureIntentOutputs ?? true;
|
|
2268
|
+
const requireConcreteArtifactsForActionTasks = completionGuard.requireConcreteArtifactsForActionTasks ?? true;
|
|
2269
|
+
const typeConfig = getTaskTypeConfig(task.type, this.config);
|
|
2270
|
+
const artifactReport = checkArtifactsEnhanced(task, taskResult);
|
|
2271
|
+
const filesOnDisk = artifactReport.files.filter(f => f.exists && f.sizeBytes > 0).length;
|
|
2272
|
+
const hasConcreteArtifacts = filesOnDisk > 0 || (taskResult.filesModified?.length ?? 0) > 0;
|
|
2273
|
+
const isActionTask = !!typeConfig.requiresToolCalls;
|
|
2274
|
+
if (rejectFutureIntentOutputs && hasFutureIntentLanguage(taskResult.output ?? '')) {
|
|
2275
|
+
taskResult.qualityScore = 1;
|
|
2276
|
+
taskResult.qualityFeedback = 'Completion rejected: output indicates pending, unexecuted work';
|
|
2277
|
+
const canRetry = this.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
|
|
2278
|
+
if (canRetry) {
|
|
2279
|
+
this.retries++;
|
|
2280
|
+
}
|
|
2281
|
+
else {
|
|
2282
|
+
if (await this.tryResilienceRecovery(task, taskId, taskResult, spawnResult)) {
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
this.taskQueue.triggerCascadeSkip(taskId);
|
|
2286
|
+
}
|
|
2287
|
+
this.emit({
|
|
2288
|
+
type: 'swarm.quality.rejected',
|
|
2289
|
+
taskId,
|
|
2290
|
+
score: 1,
|
|
2291
|
+
feedback: taskResult.qualityFeedback,
|
|
2292
|
+
artifactCount: filesOnDisk,
|
|
2293
|
+
outputLength: taskResult.output.length,
|
|
2294
|
+
preFlightReject: true,
|
|
2295
|
+
filesOnDisk,
|
|
2296
|
+
});
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
if (requireConcreteArtifactsForActionTasks && isActionTask && !hasConcreteArtifacts) {
|
|
2300
|
+
taskResult.qualityScore = 1;
|
|
2301
|
+
taskResult.qualityFeedback = 'Completion rejected: action task produced no concrete artifacts';
|
|
2302
|
+
const canRetry = this.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
|
|
2303
|
+
if (canRetry) {
|
|
2304
|
+
this.retries++;
|
|
2305
|
+
}
|
|
2306
|
+
else {
|
|
2307
|
+
if (await this.tryResilienceRecovery(task, taskId, taskResult, spawnResult)) {
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
this.taskQueue.triggerCascadeSkip(taskId);
|
|
2311
|
+
}
|
|
2312
|
+
this.emit({
|
|
2313
|
+
type: 'swarm.quality.rejected',
|
|
2314
|
+
taskId,
|
|
2315
|
+
score: 1,
|
|
2316
|
+
feedback: taskResult.qualityFeedback,
|
|
2317
|
+
artifactCount: filesOnDisk,
|
|
2318
|
+
outputLength: taskResult.output.length,
|
|
2319
|
+
preFlightReject: true,
|
|
2320
|
+
filesOnDisk,
|
|
2321
|
+
});
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
1856
2324
|
// Task passed — mark completed
|
|
1857
2325
|
this.taskQueue.markCompleted(taskId, taskResult);
|
|
1858
2326
|
this.hollowStreak = 0;
|
|
@@ -2101,6 +2569,8 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
|
|
|
2101
2569
|
decisions: this.orchestratorDecisions,
|
|
2102
2570
|
errors: this.errors,
|
|
2103
2571
|
originalPrompt: this.originalPrompt,
|
|
2572
|
+
sharedContext: this.sharedContextState.toJSON(),
|
|
2573
|
+
sharedEconomics: this.sharedEconomicsState.toJSON(),
|
|
2104
2574
|
});
|
|
2105
2575
|
this.emit({
|
|
2106
2576
|
type: 'swarm.state.checkpoint',
|
|
@@ -2634,7 +3104,12 @@ Return ONLY the JSON object, no other text.`;
|
|
|
2634
3104
|
const toolCalls = spawnResult.metrics.toolCalls ?? 0;
|
|
2635
3105
|
const hadToolCalls = toolCalls > 0 || toolCalls === -1
|
|
2636
3106
|
|| (taskResult.filesModified && taskResult.filesModified.length > 0);
|
|
2637
|
-
|
|
3107
|
+
const isNarrativeOnly = hasFutureIntentLanguage(taskResult.output ?? '');
|
|
3108
|
+
const typeConfig = getTaskTypeConfig(task.type, this.config);
|
|
3109
|
+
const actionTaskNeedsArtifacts = (this.config.completionGuard?.requireConcreteArtifactsForActionTasks ?? true)
|
|
3110
|
+
&& !!typeConfig.requiresToolCalls;
|
|
3111
|
+
const allowDegradedWithoutArtifacts = !actionTaskNeedsArtifacts && hadToolCalls && !isNarrativeOnly;
|
|
3112
|
+
if (hasArtifacts || allowDegradedWithoutArtifacts) {
|
|
2638
3113
|
// Accept with degraded flag — prevents cascade-skip of dependents
|
|
2639
3114
|
taskResult.success = true;
|
|
2640
3115
|
taskResult.degraded = true;
|