feat(worker): add PlanningChainCoordinator for sequential subtask execution

Coordinates Waiting -> Queued transitions between sibling subtasks: when a child finishes Done, the next Waiting sibling is promoted to Queued. WorkerHub.QueuePlanningSubtasksAsync exposes this to the UI; TaskRunner advances the chain on completion. Also tightens the planning-session prompt: planner must use MCP tools, not direct edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-04-25 09:36:01 +02:00
parent 288d2ece8b
commit 16e1ddd129
6 changed files with 283 additions and 4 deletions

View File

@@ -3,7 +3,9 @@ using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Config;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Planning;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Worker.Runner;
@@ -16,6 +18,7 @@ public sealed class TaskRunner
private readonly ClaudeArgsBuilder _argsBuilder;
private readonly WorkerConfig _cfg;
private readonly ILogger<TaskRunner> _logger;
private readonly PlanningChainCoordinator _chain;
public TaskRunner(
IClaudeProcess claude,
@@ -24,7 +27,8 @@ public sealed class TaskRunner
WorktreeManager wtManager,
ClaudeArgsBuilder argsBuilder,
WorkerConfig cfg,
ILogger<TaskRunner> logger)
ILogger<TaskRunner> logger,
PlanningChainCoordinator chain)
{
_claude = claude;
_dbFactory = dbFactory;
@@ -33,6 +37,7 @@ public sealed class TaskRunner
_argsBuilder = argsBuilder;
_cfg = cfg;
_logger = logger;
_chain = chain;
}
public async Task RunAsync(TaskEntity task, string slot, CancellationToken ct)
@@ -338,6 +343,23 @@ public sealed class TaskRunner
await _broadcaster.TaskFinished(slot, task.Id, "done", finishedAt);
_logger.LogInformation("Task {TaskId} completed (turns={Turns}, tokens_in={In}, tokens_out={Out})",
task.Id, result.TurnCount, result.TokensIn, result.TokensOut);
// Sequential planning chain: if this task has a parent, flip the next
// Waiting sibling to Queued so the queue pickup loop dispatches it next.
if (task.ParentTaskId is not null)
{
try
{
var advanced = await _chain.OnChildFinishedAsync(
task.Id, TaskStatus.Done, CancellationToken.None);
if (advanced is not null)
await _broadcaster.TaskUpdated(advanced);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "PlanningChain advance failed for {TaskId}", task.Id);
}
}
}
private async Task HandleFailure(string taskId, string taskTitle, string slot, RunResult result)