Step 378: Add workflow orchestrator core loop
This commit is contained in:
@@ -2281,4 +2281,13 @@ target_link_libraries(step377_test PRIVATE
|
|||||||
tree_sitter_javascript tree_sitter_typescript
|
tree_sitter_javascript tree_sitter_typescript
|
||||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||||
|
|
||||||
|
add_executable(step378_test tests/step378_test.cpp)
|
||||||
|
target_include_directories(step378_test PRIVATE src)
|
||||||
|
target_link_libraries(step378_test PRIVATE
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
unofficial::tree-sitter::tree-sitter
|
||||||
|
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||||
|
tree_sitter_javascript tree_sitter_typescript
|
||||||
|
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||||
|
|
||||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||||
|
|||||||
231
editor/src/WorkflowOrchestrator.h
Normal file
231
editor/src/WorkflowOrchestrator.h
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "WorkflowState.h"
|
||||||
|
#include "RoutingEngine.h"
|
||||||
|
#include "WorkerRegistry.h"
|
||||||
|
#include "ContextAssembler.h"
|
||||||
|
#include "ReviewGate.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct OrchestratorEvent {
|
||||||
|
std::string type;
|
||||||
|
std::string itemId;
|
||||||
|
json detail;
|
||||||
|
std::string timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BlockerInfo {
|
||||||
|
std::string type;
|
||||||
|
std::vector<std::string> itemIds;
|
||||||
|
std::string description;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WorkflowOrchestrator {
|
||||||
|
public:
|
||||||
|
WorkflowOrchestrator(WorkflowState& workflowState,
|
||||||
|
RoutingEngine& routingEngine,
|
||||||
|
WorkerRegistry& workerRegistry,
|
||||||
|
ContextAssembler& contextAssembler,
|
||||||
|
ReviewGate& reviewGate)
|
||||||
|
: state_(&workflowState), routing_(&routingEngine), workers_(&workerRegistry),
|
||||||
|
context_(&contextAssembler), review_(&reviewGate) {}
|
||||||
|
|
||||||
|
void setBuffers(const std::map<std::string, BufferInfo>& buffers) {
|
||||||
|
buffers_ = buffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setReviewPolicy(const ReviewPolicy& policy) {
|
||||||
|
reviewPolicy_ = policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrchestratorEvent step() {
|
||||||
|
auto ready = state_->queue.getReady();
|
||||||
|
if (ready.empty()) {
|
||||||
|
return blockedEvent();
|
||||||
|
}
|
||||||
|
return advanceItem(ready.front()).back();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<OrchestratorEvent> advance() {
|
||||||
|
std::vector<OrchestratorEvent> all;
|
||||||
|
auto ready = state_->queue.getReady();
|
||||||
|
for (const auto& item : ready) {
|
||||||
|
auto events = advanceItem(item);
|
||||||
|
all.insert(all.end(), events.begin(), events.end());
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkflowStats runToCompletion() {
|
||||||
|
while (true) {
|
||||||
|
auto events = advance();
|
||||||
|
if (events.empty()) break;
|
||||||
|
bool anyProgress = false;
|
||||||
|
for (const auto& ev : events) {
|
||||||
|
if (ev.type != "blocked") {
|
||||||
|
anyProgress = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!anyProgress) break;
|
||||||
|
}
|
||||||
|
return state_->getStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkflowStats runUntil(const std::function<bool(const WorkflowState&)>& predicate) {
|
||||||
|
while (!predicate(*state_)) {
|
||||||
|
auto events = advance();
|
||||||
|
if (events.empty()) break;
|
||||||
|
bool anyProgress = false;
|
||||||
|
for (const auto& ev : events) {
|
||||||
|
if (ev.type != "blocked") {
|
||||||
|
anyProgress = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!anyProgress) break;
|
||||||
|
}
|
||||||
|
return state_->getStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BlockerInfo> getBlockers() {
|
||||||
|
std::vector<BlockerInfo> out;
|
||||||
|
|
||||||
|
auto reviewItems = state_->queue.getByStatus(WI_REVIEW);
|
||||||
|
if (!reviewItems.empty()) {
|
||||||
|
BlockerInfo b;
|
||||||
|
b.type = "needs-review";
|
||||||
|
b.description = "Items are waiting in review.";
|
||||||
|
for (const auto& i : reviewItems) b.itemIds.push_back(i.id);
|
||||||
|
out.push_back(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockerInfo human;
|
||||||
|
human.type = "needs-human";
|
||||||
|
human.description = "Items are routed to human execution.";
|
||||||
|
|
||||||
|
BlockerInfo external;
|
||||||
|
external.type = "needs-external-model";
|
||||||
|
external.description = "Items are prepared for SLM/LLM external execution.";
|
||||||
|
|
||||||
|
for (const auto& status : {WI_ASSIGNED, WI_IN_PROGRESS}) {
|
||||||
|
for (const auto& item : state_->queue.getByStatus(status)) {
|
||||||
|
if (item.workerType == "human") {
|
||||||
|
human.itemIds.push_back(item.id);
|
||||||
|
} else if (item.workerType == "slm" || item.workerType == "llm") {
|
||||||
|
external.itemIds.push_back(item.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!human.itemIds.empty()) out.push_back(human);
|
||||||
|
if (!external.itemIds.empty()) out.push_back(external);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WorkflowState* state_ = nullptr;
|
||||||
|
RoutingEngine* routing_ = nullptr;
|
||||||
|
WorkerRegistry* workers_ = nullptr;
|
||||||
|
ContextAssembler* context_ = nullptr;
|
||||||
|
ReviewGate* review_ = nullptr;
|
||||||
|
ReviewPolicy reviewPolicy_ = ReviewPolicy::getDefault();
|
||||||
|
std::map<std::string, BufferInfo> buffers_;
|
||||||
|
|
||||||
|
static OrchestratorEvent makeEvent(const std::string& type,
|
||||||
|
const std::string& itemId,
|
||||||
|
const json& detail = json::object()) {
|
||||||
|
OrchestratorEvent ev;
|
||||||
|
ev.type = type;
|
||||||
|
ev.itemId = itemId;
|
||||||
|
ev.detail = detail;
|
||||||
|
ev.timestamp = workItemTimestamp();
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrchestratorEvent blockedEvent() {
|
||||||
|
auto blockers = getBlockers();
|
||||||
|
if (blockers.empty()) {
|
||||||
|
return makeEvent("blocked", "", json{{"reason", "no-ready-items"}});
|
||||||
|
}
|
||||||
|
return makeEvent("blocked", "", json{{"blockers", toJsonBlockers(blockers)}});
|
||||||
|
}
|
||||||
|
|
||||||
|
static json toJsonBlockers(const std::vector<BlockerInfo>& blockers) {
|
||||||
|
json arr = json::array();
|
||||||
|
for (const auto& b : blockers) {
|
||||||
|
arr.push_back({{"type", b.type},
|
||||||
|
{"itemIds", b.itemIds},
|
||||||
|
{"description", b.description}});
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<OrchestratorEvent> advanceItem(const WorkItem& readyItem) {
|
||||||
|
std::vector<OrchestratorEvent> events;
|
||||||
|
|
||||||
|
auto maybeItem = state_->queue.getItem(readyItem.id);
|
||||||
|
if (!maybeItem.has_value()) return events;
|
||||||
|
WorkItem item = maybeItem.value();
|
||||||
|
|
||||||
|
RoutingDecision decision = routing_->route(item);
|
||||||
|
item.workerType = decision.workerType;
|
||||||
|
item.contextWidth = decision.contextWidth;
|
||||||
|
item.reviewRequired = decision.reviewRequired;
|
||||||
|
|
||||||
|
transitionWorkItem(item, WI_ASSIGNED);
|
||||||
|
transitionWorkItem(item, WI_IN_PROGRESS);
|
||||||
|
state_->queue.updateItem(item.id, item);
|
||||||
|
|
||||||
|
events.push_back(makeEvent("routed", item.id, decision.toJson()));
|
||||||
|
|
||||||
|
WorkerContext ctx = context_->assembleContext(item, buffers_, item.contextWidth,
|
||||||
|
decision.contextBudgetTokens);
|
||||||
|
events.push_back(makeEvent("context-assembled", item.id,
|
||||||
|
json{{"contextWidth", item.contextWidth},
|
||||||
|
{"tokensBudget", decision.contextBudgetTokens}}));
|
||||||
|
|
||||||
|
WorkerInterface* worker = workers_->getWorker(item.workerType);
|
||||||
|
if (!worker) {
|
||||||
|
events.push_back(makeEvent("blocked", item.id,
|
||||||
|
json{{"reason", "worker-not-registered"},
|
||||||
|
{"workerType", item.workerType}}));
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.result = worker->execute(item, ctx);
|
||||||
|
state_->queue.updateItem(item.id, item);
|
||||||
|
events.push_back(makeEvent("executed", item.id,
|
||||||
|
json{{"workerType", item.workerType},
|
||||||
|
{"confidence", item.result.confidence}}));
|
||||||
|
|
||||||
|
if (item.workerType == "human") {
|
||||||
|
events.push_back(makeEvent("blocked", item.id,
|
||||||
|
json{{"reason", "needs-human"}}));
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
if (item.workerType == "slm" || item.workerType == "llm") {
|
||||||
|
events.push_back(makeEvent("blocked", item.id,
|
||||||
|
json{{"reason", "needs-external-model"}}));
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReviewDecision reviewDecision = review_->shouldAutoApprove(item, item.result, reviewPolicy_);
|
||||||
|
if (reviewDecision.approved) {
|
||||||
|
state_->queue.updateItem(item.id, item);
|
||||||
|
state_->queue.complete(item.id);
|
||||||
|
events.push_back(makeEvent("auto-approved", item.id,
|
||||||
|
json{{"rule", reviewDecision.ruleMatched}}));
|
||||||
|
events.push_back(makeEvent("completed", item.id));
|
||||||
|
} else {
|
||||||
|
transitionWorkItem(item, WI_REVIEW);
|
||||||
|
state_->queue.updateItem(item.id, item);
|
||||||
|
events.push_back(makeEvent("sent-to-review", item.id,
|
||||||
|
json{{"reason", reviewDecision.reasoning}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
};
|
||||||
249
editor/tests/step378_test.cpp
Normal file
249
editor/tests/step378_test.cpp
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
// Step 378: WorkflowOrchestrator main loop (12 tests)
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include "WorkflowOrchestrator.h"
|
||||||
|
|
||||||
|
static WorkItem makeItem(const std::string& id,
|
||||||
|
const std::string& name,
|
||||||
|
const std::string& workerType,
|
||||||
|
bool reviewRequired = false) {
|
||||||
|
WorkItem w;
|
||||||
|
w.id = id;
|
||||||
|
w.nodeId = id + "_node";
|
||||||
|
w.nodeName = name;
|
||||||
|
w.nodeType = "Function";
|
||||||
|
w.bufferId = "buf";
|
||||||
|
w.contextWidth = "local";
|
||||||
|
w.workerType = workerType;
|
||||||
|
w.reviewRequired = reviewRequired;
|
||||||
|
w.priority = "medium";
|
||||||
|
w.status = WI_PENDING;
|
||||||
|
w.createdAt = workItemTimestamp();
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int passed = 0;
|
||||||
|
|
||||||
|
// Test 1: single deterministic/template task completes in one step
|
||||||
|
{
|
||||||
|
WorkflowState ws("p1");
|
||||||
|
ws.queue.enqueue(makeItem("wi1", "getName", "template"));
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
|
||||||
|
OrchestratorEvent ev = orch.step();
|
||||||
|
assert(ev.type == "completed");
|
||||||
|
assert(ws.queue.getByStatus(WI_COMPLETE).size() == 1);
|
||||||
|
std::cout << "Test 1 PASSED: single template task completes in one step\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: dependency cascade after completion
|
||||||
|
{
|
||||||
|
WorkflowState ws("p2");
|
||||||
|
WorkItem a = makeItem("a", "getA", "template");
|
||||||
|
WorkItem b = makeItem("b", "getB", "template");
|
||||||
|
b.dependencies.push_back("a");
|
||||||
|
ws.queue.enqueue(a);
|
||||||
|
ws.queue.enqueue(b);
|
||||||
|
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
orch.step();
|
||||||
|
assert(ws.queue.getByStatus(WI_COMPLETE).size() == 1);
|
||||||
|
assert(ws.queue.getByStatus(WI_READY).size() == 1); // b promoted
|
||||||
|
std::cout << "Test 2 PASSED: dependency cascade promotion\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: human task blocks as needs-human
|
||||||
|
{
|
||||||
|
WorkflowState ws("p3");
|
||||||
|
ws.queue.enqueue(makeItem("wi3", "hardTask", "human"));
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
orch.step();
|
||||||
|
auto blockers = orch.getBlockers();
|
||||||
|
bool hasHuman = false;
|
||||||
|
for (const auto& b : blockers) if (b.type == "needs-human") hasHuman = true;
|
||||||
|
assert(hasHuman);
|
||||||
|
std::cout << "Test 3 PASSED: human task blocks\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: SLM task blocks as needs-external-model
|
||||||
|
{
|
||||||
|
WorkflowState ws("p4");
|
||||||
|
ws.queue.enqueue(makeItem("wi4", "fileTask", "slm"));
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
orch.step();
|
||||||
|
auto blockers = orch.getBlockers();
|
||||||
|
bool hasExternal = false;
|
||||||
|
for (const auto& b : blockers) if (b.type == "needs-external-model") hasExternal = true;
|
||||||
|
assert(hasExternal);
|
||||||
|
std::cout << "Test 4 PASSED: agent task blocks for external model\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: runToCompletion processes deterministic tasks
|
||||||
|
{
|
||||||
|
WorkflowState ws("p5");
|
||||||
|
ws.queue.enqueue(makeItem("w1", "getOne", "template"));
|
||||||
|
ws.queue.enqueue(makeItem("w2", "getTwo", "template"));
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
auto stats = orch.runToCompletion();
|
||||||
|
assert(stats.complete == 2);
|
||||||
|
std::cout << "Test 5 PASSED: runToCompletion completes deterministic tasks\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: advance emits stage events
|
||||||
|
{
|
||||||
|
WorkflowState ws("p6");
|
||||||
|
ws.queue.enqueue(makeItem("wi6", "getX", "template"));
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
auto events = orch.advance();
|
||||||
|
bool routed = false, context = false, executed = false, completed = false;
|
||||||
|
for (const auto& e : events) {
|
||||||
|
if (e.type == "routed") routed = true;
|
||||||
|
if (e.type == "context-assembled") context = true;
|
||||||
|
if (e.type == "executed") executed = true;
|
||||||
|
if (e.type == "completed") completed = true;
|
||||||
|
}
|
||||||
|
assert(routed && context && executed && completed);
|
||||||
|
std::cout << "Test 6 PASSED: stage events emitted\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: blockers report review items
|
||||||
|
{
|
||||||
|
WorkflowState ws("p7");
|
||||||
|
ws.queue.enqueue(makeItem("wi7", "getY", "template", true)); // force review
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
orch.step();
|
||||||
|
auto blockers = orch.getBlockers();
|
||||||
|
bool hasReview = false;
|
||||||
|
for (const auto& b : blockers) if (b.type == "needs-review") hasReview = true;
|
||||||
|
assert(hasReview);
|
||||||
|
std::cout << "Test 7 PASSED: review blockers reported\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 8: empty workflow returns immediately
|
||||||
|
{
|
||||||
|
WorkflowState ws("p8");
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
auto stats = orch.runToCompletion();
|
||||||
|
assert(stats.total == 0);
|
||||||
|
auto ev = orch.step();
|
||||||
|
assert(ev.type == "blocked");
|
||||||
|
std::cout << "Test 8 PASSED: empty workflow no-op\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 9: mixed workflow advances what it can
|
||||||
|
{
|
||||||
|
WorkflowState ws("p9");
|
||||||
|
ws.queue.enqueue(makeItem("d1", "getFast", "template"));
|
||||||
|
ws.queue.enqueue(makeItem("h1", "manual", "human"));
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
auto stats = orch.runToCompletion();
|
||||||
|
assert(stats.complete == 1);
|
||||||
|
assert(!orch.getBlockers().empty());
|
||||||
|
std::cout << "Test 9 PASSED: mixed workflow partially advances\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 10: runUntil predicate can stop early
|
||||||
|
{
|
||||||
|
WorkflowState ws("p10");
|
||||||
|
ws.queue.enqueue(makeItem("a1", "getA", "template"));
|
||||||
|
ws.queue.enqueue(makeItem("a2", "getB", "template"));
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
auto stats = orch.runUntil([](const WorkflowState& s) {
|
||||||
|
return s.getStats().complete >= 1;
|
||||||
|
});
|
||||||
|
assert(stats.complete >= 1);
|
||||||
|
std::cout << "Test 10 PASSED: runUntil predicate stop\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 11: blocked event includes blocker detail when applicable
|
||||||
|
{
|
||||||
|
WorkflowState ws("p11");
|
||||||
|
ws.queue.enqueue(makeItem("h2", "manual2", "human"));
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
orch.step(); // process human task first
|
||||||
|
OrchestratorEvent ev = orch.step(); // now no ready, returns blocked
|
||||||
|
assert(ev.type == "blocked");
|
||||||
|
assert(ev.detail.contains("blockers"));
|
||||||
|
std::cout << "Test 11 PASSED: blocked event includes blocker detail\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 12: agent task stores context payload in result
|
||||||
|
{
|
||||||
|
WorkflowState ws("p12");
|
||||||
|
ws.queue.enqueue(makeItem("ll1", "llTask", "llm"));
|
||||||
|
RoutingEngine routing;
|
||||||
|
auto workers = WorkerRegistry::getDefaultRegistry();
|
||||||
|
ContextAssembler assembler;
|
||||||
|
ReviewGate review;
|
||||||
|
WorkflowOrchestrator orch(ws, routing, workers, assembler, review);
|
||||||
|
orch.step();
|
||||||
|
auto item = ws.queue.getItem("ll1");
|
||||||
|
assert(item.has_value());
|
||||||
|
assert(item->result.astJson.is_object());
|
||||||
|
assert(item->result.astJson.contains("workerType"));
|
||||||
|
std::cout << "Test 12 PASSED: agent context payload preserved\n";
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\nResults: " << passed << "/12\n";
|
||||||
|
assert(passed == 12);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
45
progress.md
45
progress.md
@@ -2744,6 +2744,51 @@ Scheme.
|
|||||||
- `editor/tests/step377_test.cpp` remains within project file-size guidance
|
- `editor/tests/step377_test.cpp` remains within project file-size guidance
|
||||||
for test artifacts (`188` lines)
|
for test artifacts (`188` lines)
|
||||||
|
|
||||||
|
### Step 378: Orchestrator Core Main Loop
|
||||||
|
**Status:** PASS (12/12 tests)
|
||||||
|
|
||||||
|
Implemented Sprint 15 orchestration-loop core as a dedicated workflow engine
|
||||||
|
that advances ready work items through routing, context assembly, execution,
|
||||||
|
review, completion, and blocker reporting.
|
||||||
|
|
||||||
|
**Files created:**
|
||||||
|
- `editor/src/WorkflowOrchestrator.h` — orchestration core:
|
||||||
|
- `step()` (advance one ready item one stage-set)
|
||||||
|
- `advance()` (advance all currently ready items)
|
||||||
|
- `runToCompletion()` (progress until no further non-blocked progress)
|
||||||
|
- `runUntil(predicate)` (predicate-controlled progression)
|
||||||
|
- `getBlockers()` with typed blockers:
|
||||||
|
`needs-human`, `needs-review`, `needs-external-model`
|
||||||
|
- event stream via `OrchestratorEvent` (`routed`, `context-assembled`,
|
||||||
|
`executed`, `auto-approved`, `sent-to-review`, `completed`, `blocked`)
|
||||||
|
- `editor/tests/step378_test.cpp` — 12 tests covering:
|
||||||
|
1. single deterministic/template completion in one step
|
||||||
|
2. dependency cascade promotion
|
||||||
|
3. human-task blocker classification
|
||||||
|
4. agent-task external-model blocker classification
|
||||||
|
5. `runToCompletion()` deterministic completion behavior
|
||||||
|
6. stage event emission coverage
|
||||||
|
7. review blocker reporting
|
||||||
|
8. empty-workflow no-op behavior
|
||||||
|
9. mixed workflow partial progress behavior
|
||||||
|
10. `runUntil(...)` predicate stop behavior
|
||||||
|
11. blocked-event detail payload includes blockers
|
||||||
|
12. agent context payload preservation
|
||||||
|
|
||||||
|
**Files modified:**
|
||||||
|
- `editor/CMakeLists.txt` — `step378_test` target
|
||||||
|
|
||||||
|
**Verification run:**
|
||||||
|
- `step378_test` — PASS (12/12) new step coverage
|
||||||
|
- `step377_test` — PASS (8/8) regression coverage
|
||||||
|
- `step376_test` — PASS (12/12) regression coverage
|
||||||
|
- `step375_test` — PASS (12/12) regression coverage
|
||||||
|
- `step331_test` — PASS (8/8) orchestration-foundation regression
|
||||||
|
|
||||||
|
**Architecture gate check:**
|
||||||
|
- `editor/src/WorkflowOrchestrator.h` is within header size limit
|
||||||
|
(`231` lines <= `600`)
|
||||||
|
|
||||||
# Roadmap Planning — Sprints 12-25+
|
# Roadmap Planning — Sprints 12-25+
|
||||||
|
|
||||||
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
||||||
|
|||||||
Reference in New Issue
Block a user