Skip to content

Vagueness in Loop Agent configuration. #2

Description

@neo-fetch

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):

  1. types.ts: add "loop" to NodeType; interface LoopConfig { description?: string; maxIterations: number; generator: AgentConfig; critic: AgentConfig }; LoopNode = BaseNode<"loop", LoopConfig>; add to GraphNode.
  2. schema/ir.schema.json: loopConfig def (maxIterations integer ≥ 1; generator/criticagentConfig); 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions