Run several delegations as one task graph - #85
Merged
Conversation
지금까지는 위임이 한 건씩만 돌았음. 여럿을 엮어 "A·B 동시에 돌리고 둘 다 끝나면 C 에게 결과 넘기기" 를 만들 자리가 없었음. Promise·타이머 안 씀. 부르는 쪽이 손으로 돌리는 상태 기계 — ready/start/settle. 시간이 안 흐르는 테스트에서 경합을 그대로 재현할 수 있고 engine/ 의 import 0 도 지킴. 동시 상한은 계속 policy.ts 가 관장. 핵심은 안 돈 것을 없던 일로 안 만드는 것: - 의존이 무너지면 뒤엣것들이 이유와 함께 skipped 로 닫힘 - 직접 원인(dep-failed)과 딸려 막힌 것(dep-skipped)을 구분 — 스무 개가 막혀도 원인은 하나 - 돌았는데 아무 말 없는 것(empty)을 성공과 안 섞음 - 답 못 받은 것(timeout)은 실패로 봄. 뒤가 빈 결과 받고 도는 것보다 막히는 게 나음 사이클은 순환에 낀 id 를 실어서 냄. "어딘가 순환" 만으로는 못 고침. 무너진 의존 판정이 satisfied 와 cascade 두 겹인데, cascade 가 먼저 닫아서 테스트로 구분이 안 됨. 지우면 막힌 작업이 pending 인 채 굳어 finished() 가 영영 안 참이 됨 — 그 이유를 코드에 적어 둠.
관리자가 위임 여러 건을 의존 관계까지 얹어 한 번에 올릴 수 있음. 의존 없는 것끼리 동시에 돌고, 앞선 작업의 답은 뒤 작업 씨앗에 자동으로 실림 — 결과 기다렸다 다시 위임할 필요 없음. 정책은 그래프 안에서만 완화. per-turn 상한과 중복 대상 금지는 계획 없이 위임하는 모델을 묶던 것이고 그래프가 그 계획 자체라, 그래프 크기(12개)로 대신함. 동시 실행 상한은 안 품 — 파일 락과 요금이 걸린 진짜 한계임. 못 쓸 그래프는 한 개도 안 태움. 반쯤 돌려 놓고 사이클을 찾으면 되돌릴 수 없음. 사이클은 낀 id 를, 없는 의존은 그 이름을 실어 보냄. startDelegation 이 문자열만 내던 걸 결과까지 같이 내게 고침. 그래프가 성공과 거절을 구분할 방법이 없었음. 거절도 실패 결과로 만들어 뒤엣것들이 이유와 함께 닫힘. 실제 앱 프로브에서 하나 잡음: 같은 에이전트 둘이 겹쳐 돌았음(peak=2). 배치를 고르는 루프와 태우는 루프가 나뉘어 있어 busy 표시가 늦었고, 한 배치 안의 같은 대상 둘이 나란히 통과했음. 겹치면 아래층이 agent-busy 로 거절하고 그게 실패로 번져서 순서만 안 맞았을 뿐인 작업들이 통째로 죽음. 단위 테스트로는 안 잡힘 — Orchestra 는 동시성을 부르는 쪽에 맡기므로 부르는 쪽 실수가 안 보임. PlanResult 를 ok: boolean 에서 kind 문자열로 바꿈. 루트 tsconfig 가 strict:false 라 불리언 유니온이 안 좁혀짐. policy.ts 의 PolicyDecision 이 같은 이유로 같은 모양임. orch 사전은 모델이 읽음. 키가 빠지면 날 키가 그대로 결과로 가고 관리자가 그걸 요약함 — 실패가 실패로 안 보이는 가장 나쁜 모양이라 테스트로 막음.
도구 카드만으로는 뭐가 뭘 기다리는지가 안 보였음. 카드 다섯 장이 따로 떠 있을 뿐이라, 하나가 죽어서 나머지가 안 돈 건지 원래 순서가 그런 건지 구분이 안 됨. planTasks 가 Kahn 돌면서 어차피 만들던 단(wave)을 버리지 않고 냄. 순서만으로는 "이 셋을 같이 돌리고 끝나면 이것" 을 못 그림. 패널 아이콘을 계획 패널과 따로 둠. 아직 안 돈 것(○)과 영영 안 돌 것(⊘)이 같아 보이면 안 되고, 돌았는데 결과 없는 것(◌)도 완료와 갈라야 함. 막힌 줄에는 무엇에 막혔는지를 같이 적음 — 회색으로만 두면 대기 중인 것과 구분이 안 됨. 그래프가 시작하면 흐름 탭으로 옮김. 프로젝트를 열면 트리 탭에 가 있어서, 안 바꾸면 여러 에이전트가 도는 내내 화면이 아무 말도 안 함. 데모 경로가 같은 이유로 같은 일을 함. 프로브에서 다섯 건이 실패했는데 전부 프로브 잘못이었음 — 좌측이 트리 탭이었고, 셀렉터가 카드 대신 헤더를 집었음(헤더도 "작업 그래프" 로 시작). state.graph 를 직접 찍어 보고 배선은 멀쩡하고 도달 경로만 틀렸다는 걸 확인함.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a layer above single delegation: a task graph.
Until now a delegation ran one at a time. There was no place to say "run A and B together, and when both finish hand their results to C" — the manager had to delegate, wait for the round to come back, read the results, and delegate again.
The engine
src/engine/orchestra.tscontains no Promise, no timer, and noawait. It is a state machine the caller drives: ask what may start (ready), say it started (start), say it finished (settle). Every race is therefore reproducible in a test where no time passes, and the directory's import-zero rule holds.The point of the layer is refusing to lose work:
dep-failed) and knocked-over-next (dep-skipped) are distinguished, so one failure blocking twenty tasks still reports one cause rather than twenty.The tool
delegate_graphtakes{id, agent, task, needs}entries. Results from earlier tasks are appended to the seed prompt of later ones automatically.Two policy limits are relaxed inside a graph and one is not. Per-turn count and the duplicate-target ban existed to bound a model delegating without a plan; a graph is that plan, validated before it runs and visible on screen, so graph size (12) replaces them. Concurrency stays capped — that one is file locks and billing, not taste.
startDelegationreturned only a rendered string, so a graph could not tell success from refusal. It now returns the outcome alongside the text, and refusals become failed outcomes that close dependents with a reason.Found while probing the running app
Two tasks for the same agent ran concurrently (
peak=2). Choosing a batch and dispatching it were separate loops, so an agent was marked busy too late and two tasks for it passed side by side within one batch. Overlapping makes the layer below refuse withagent-busy, and that refusal spreads as a failure — killing tasks whose only problem was ordering.Unit tests cannot see this:
Orchestradeliberately leaves concurrency to its caller, so a caller's mistake is invisible toOrchestra's own tests. Reproduced in the running app, fixed, re-confirmed.Also
PlanResultuses a string discriminant rather thanok: boolean— the root tsconfig isstrict:false, where a boolean union does not narrow andr.errorfails to compile.policy.tsdocuments the same trap forPolicyDecision.The
orchdictionary is read by the model, not shown on screen. A missing key would send the raw key name as the result, and the manager would summarize that to the user — failure that does not look like failure. A test pins the keys, all four languages, and matching placeholders.Verification
The panel
Tool cards alone did not show what was waiting on what — five cards sit side by side, and a graph where one task killed the rest looks identical to one that was simply ordered that way.
planTasksnow returns the waves Kahn's algorithm builds anyway rather than discarding them; order alone cannot express "run these three together, then this one".The panel uses its own icon set rather than the plan panel's. Not-yet-run (
○) and never-will-run (⊘) must not look alike, and ran-but-returned-nothing (◌) must not look like done. A blocked row carries what blocked it, inline — greyed out on its own, waiting and blocked read the same.Starting a graph switches the left panel to the workflow tab. Opening a project leaves it on the tree, so without this the screen says nothing at all while several agents run. The demo path already does this for the same reason.
Five probe checks failed during this work and all five were the probe's fault, not the code's: the left panel was on the tree tab, and the selector matched the card's header rather than the card (the header also begins with the panel's label). Printing
state.graphdirectly showed the wiring was correct and only the path to the screen was wrong.Probe now at 18/18, including the tab switch — the probe no longer clicks the tab itself.