I want to integrate the the validation and reviser loop "Graphically"
Here's the problem: we've now cycled through three different mechanisms for #3, each asserted confidently, none verified locally. And critically, my router-cycle proposal runs into the same official-docs wall I just hit: [adk.dev/graphs/dynamic](https://adk.dev/graphs/dynamic/) says loops/recursion are "difficult or impossible to represent in static graphs" and belong in dynamic workflows, and [adk.dev/graphs](https://adk.dev/graphs/) warns graph workflows aren't for "iterative loops." So:
- LoopAgent-in-
edges — I (me, Mayank Deshpande) earlier confirmed was itself unverified; and yes, it's a superseded template agent → real risk it doesn't compose.
- Router back-edge cycle — the docs actively say static graphs can't loop, and my own note concedes the
max_iterations counter needs a hacky injected global/state counter. So "guaranteed to compile" isn't established — it's contradicted by the docs.
Both are plausible; both are unproven; the docs cast doubt on each. I'm not going to write contested codegen on a guess — that's how we get the failed-slice-and-revert situation. Per the project's own ADR-0021 discipline, I'll just run it.
So I decided to install google-adk==2.0.0 here and probe both mechanisms directly.
First, I set up an isolated venv and install ADK 2.0.0 (Python 3.14 may be too new for it — we'll see):
I installed google-adk==2.0.0 and probed both mechanisms directly. Results:
(A) LoopAgent as a node → PASS, but: DeprecationWarning: "LoopAgent is
deprecated and will be removed... Please use Workflow instead."
(B) cyclic edges (router→upstream) → PASS — Workflow accepted the cycle.
Workflow docstring: "_run_impl() IS the graph orchestration loop"
So my LoopAgent recommendation was wrong — it works today but ADK 2.0 itself deprecates it and points me to Workflow. Generating soon-to-be-removed code is exactly what this tool shouldn't do. And my router-cycle constructs cleanly and is the 2.0-native path. We pivot to the unrolled cyclic graph.
A part of my proposal — keep the loop node as the IR abstraction, change only codegen — has a bonus that I want to highlight: since the cycle lives only in the generated Workflow edges, the IR graph stays a DAG (the loop node is one node, one-in/one-out). So invariant 4 survives untouched — no validator cycle-surgery.
One honest caveat: I verified construction (both Workflow(...) calls succeed and the cyclic edge set is accepted); I did not run the loop to completion (that needs a live model/API key). Construction-acceptance + the deprecation signal + the "orchestration loop" engine make the cyclic graph the correct codegen target; full loop-runtime stays a user export-time check. This is a huge part I am putting this github issue.
One small fix to the role mapping: an unrolled cycle's two agents are naturally generator (which also revises on feedback) + critic — the REVISE edge loops back to the generator. So LoopConfig becomes { maxIterations, generator, critic } (codegen synthesizes the router + CriticEvaluation + back-edge + the max_iterations counter, since graph routers have no built-in cap).
So I rewrote the design doc to the verified mechanism, and produced the revised #3-B.
Slice #3-B — loop node type → unrolled native cyclic Workflow (packages). Read CLAUDE.md, docs/IR-SCHEMA.md, and docs/PHASE-SUBAGENTS-DESIGN.md (the design — note the verified mechanism: emit a cyclic Workflow, not the deprecated LoopAgent). You're on main; branch first. This slice deliberately edits the frozen packages/* core — golden tests + validator spec + fidelity are the gate. End green + committed; --ff-only merge; delete branch; append ADR-0039.
Goal: a first-class loop node (one node, one-in/one-out — IR stays a DAG) that codegen unrolls into a native ADK 2.0 cyclic graph: generator → critic → router, with the router's REVISE edge looping back to the generator and PASS going to the loop node's downstream target. (Verified against google-adk==2.0.0 in /tmp/adk_probe_venv: cyclic Workflow constructs; LoopAgent is deprecated — do not use it.)
IR (packages/ir):
types.ts: add "loop" to NodeType; interface LoopConfig { description?: string; maxIterations: number; generator: AgentConfig; critic: AgentConfig }; LoopNode = BaseNode<"loop", LoopConfig>; add to GraphNode.
schema/ir.schema.json: loopConfig def (maxIterations integer ≥ 1; generator/critic → agentConfig); add "loop" to the node type enum + allOf branch.
Validator (new codes; NO cycle-support needed — invariant 4 unchanged): LOOP_BAD_MAX_ITERATIONS (int ≥ 1); validate generator/critic as agents (reuse agent checks). Reserve the synthesized names <N>_generator, <N>_critic, <N>_router, schema <N>_CriticEvaluation against user-name collision in the flat global namespace (ADR-0017). The loop node passes the existing reachability/one-in-one-out/DAG checks like any node (the IR has no back-edge — the cycle is codegen-only).
Codegen (packages/codegen):
3. schemas.py: auto-emit <N>_CriticEvaluation (is_valid: bool, feedback: str).
4. agents.py: emit <N>_generator + <N>_critic via renderAgent; force the critic's output_schema=<N>_CriticEvaluation.
5. functions.py: synthesize <N>_router(node_input) — if node_input.is_valid: return Event(route="PASS", payload=node_input) else return Event(route="REVISE", payload=node_input.feedback), plus a max-iterations guard that forces PASS past maxIterations (v1: module-level counter, with a reentrancy caveat comment; note session-state counter as the cleaner follow-up).
6. Edges compiler ([edges.ts](packages/codegen/src/edges.ts)) — the crux: when compiling edges, a loop node N expands rather than emitting one symbol: inbound → <N>_generator; chain <N>_generator → <N>_critic → <N>_router; and the route-map row (<N>_router, {"REVISE": <N>_generator, "PASS": <N's downstream target>}). The loop node's IR out-edge target becomes the router's PASS destination.
Fixtures + tests:
7. Golden packages/ir/fixtures/critic-loop.ir.json (START → loop N → a downstream node) → assert the CriticEvaluation schema, the two agents (critic with the eval output_schema), the <N>_router with the is_valid branch + iteration guard, and workflow.py showing the back-edge (<N>_router, {"REVISE": <N>_generator, "PASS": <downstream>}). Add to check:ir + golden set.
8. Validator spec: bad maxIterations, name collision, valid loop passes, DAG check still green.
9. Fidelity (note for the user / reuse /tmp/adk_probe_venv): the generated critic-loop project constructs as a cyclic Workflow under real google-adk==2.0.0.
Verify: npm test green. No apps/web changes (editor is #3-C). Record ADR-0039. End green, --ff-only merge, delete branch.
Scope discipline: fixed generator + critic (router synthesized); no LoopAgent; no general N-ary sub-agents; module-counter cap is fine for v1; var-chips inside sub-agents out of scope.
However, I still need someone's review on this. Is this really the best way to implement this? Could there be something better? Any suggestions or feedback would be greatly appreciated.
I want to integrate the the validation and reviser loop "Graphically"
Here's the problem: we've now cycled through three different mechanisms for #3, each asserted confidently, none verified locally. And critically, my router-cycle proposal runs into the same official-docs wall I just hit: [adk.dev/graphs/dynamic](https://adk.dev/graphs/dynamic/) says loops/recursion are "difficult or impossible to represent in static graphs" and belong in dynamic workflows, and [adk.dev/graphs](https://adk.dev/graphs/) warns graph workflows aren't for "iterative loops." So:
edges— I (me, Mayank Deshpande) earlier confirmed was itself unverified; and yes, it's a superseded template agent → real risk it doesn't compose.max_iterationscounter needs a hacky injected global/state counter. So "guaranteed to compile" isn't established — it's contradicted by the docs.Both are plausible; both are unproven; the docs cast doubt on each. I'm not going to write contested codegen on a guess — that's how we get the failed-slice-and-revert situation. Per the project's own ADR-0021 discipline, I'll just run it.
So I decided to install
google-adk==2.0.0here and probe both mechanisms directly.First, I set up an isolated venv and install ADK 2.0.0 (Python 3.14 may be too new for it — we'll see):
I installed
google-adk==2.0.0and probed both mechanisms directly. Results:So my LoopAgent recommendation was wrong — it works today but ADK 2.0 itself deprecates it and points me to
Workflow. Generating soon-to-be-removed code is exactly what this tool shouldn't do. And my router-cycle constructs cleanly and is the 2.0-native path. We pivot to the unrolled cyclic graph.A part of my proposal — keep the
loopnode as the IR abstraction, change only codegen — has a bonus that I want to highlight: since the cycle lives only in the generatedWorkflowedges, the IR graph stays a DAG (the loop node is one node, one-in/one-out). So invariant 4 survives untouched — no validator cycle-surgery.One honest caveat: I verified construction (both
Workflow(...)calls succeed and the cyclic edge set is accepted); I did not run the loop to completion (that needs a live model/API key). Construction-acceptance + the deprecation signal + the "orchestration loop" engine make the cyclic graph the correct codegen target; full loop-runtime stays a user export-time check. This is a huge part I am putting this github issue.One small fix to the role mapping: an unrolled cycle's two agents are naturally generator (which also revises on feedback) + critic — the
REVISEedge loops back to the generator. SoLoopConfigbecomes{ maxIterations, generator, critic }(codegen synthesizes the router +CriticEvaluation+ back-edge + themax_iterationscounter, since graph routers have no built-in cap).So I rewrote the design doc to the verified mechanism, and produced the revised #3-B.
However, I still need someone's review on this. Is this really the best way to implement this? Could there be something better? Any suggestions or feedback would be greatly appreciated.