ADR-003: Graph-Based Workflow and Conversational Root¶
Status: Accepted Date: 2026-06-07 Author: Taoufiq Supersedes: ADR-002
Context¶
ADK 2.0 introduced the Workflow Runtime — a graph-based execution engine where agents, functions, and tools are nodes connected by edges. The legacy workflow agents (SequentialAgent, ParallelAgent, LoopAgent) are deprecated in 2.0 ("Please use Workflow instead") and emit DeprecationWarning on construction.
The previous root (ADR-002) was an LLM orchestrator (orrery_assistant, an LlmAgent) that: - exposed six specialist agents as AgentTools and let the LLM route by intent, and - kept a deterministic incident_triage_agent (SequentialAgent) as a sub-agent.
The Workflow class provides deterministic routing and parallel execution without deprecation warnings. However, ADK 2.0 enforces a strict constraint: a conversational LlmAgent (multi-turn, mode='task'|'chat') cannot be embedded as a routed static node inside a Workflow graph, because the graph scheduler overwrites node_input on resume, destroying conversational context.
Decision driver: We need both a conversational orchestrator for free-form user interaction and a deterministic, auditable incident-response pipeline (triage → remediate → report) built on the new Workflow graph.
Validated API facts (ADK 2.2.0)¶
LlmAgentis aBaseNode, so existingcreate_agent()results are valid nodes.- A
Workflowcannot be passed toAgentToolor used as asub_agentinside anLlmAgentbecause it does not subclassBaseAgent. - Conversational agents (
LlmAgentwithmode='chat'|'task') lose their memory/context when placed as static nodes inside aWorkflowbecause the graph runner overwrites their inputs upon resuming. - Edge forms: sequential chain
("START", a, b, c); parallel fan-out("START", (a, b, c)); conditional/loop viaRoutingMap(node, {"k": x, ...}). JoinNode(_requires_all_predecessors = True) is the fan-in barrier: it waits for all parallel predecessors before firing the successor.
Decision¶
We maintain two separate entry points that reuse the same underlying node agents:
- The Interactive Root (
orrery_chat_agent): AnLlmAgentacting as the conversational orchestrator. It usesAgentTools to route requests to specialists, and exposes a single-turnincident_triage_agentfor broad health sweeps. - The Deterministic Graph (
orrery_triage_workflow): A standalone ADK 2.0Workflowgraph used for batch or scheduled incident responses (viamake run-triage).
graph TD
subgraph root["Interactive root — adk web / CLI / Slack / Chat"]
CHAT["orrery_chat_agent<br/>chat-mode LlmAgent"]
CHAT -->|AgentTool| SPEC["kafka · k8s · observability<br/>elasticsearch · docker · ops_journal"]
CHAT -->|AgentTool| TRIAGE_T["incident_triage_agent<br/>single-turn health sweep"]
CHAT --> MEM["PreloadMemoryTool"]
end
subgraph wf["Batch workflow — make run-triage"]
START([START]) --> HC["parallel: 5 health checkers"]
HC --> HJ["health_join<br/>JoinNode"]
HJ --> SUM["triage_summarizer"]
SUM --> JW["journal_writer"]
JW --> TR{"triage_route"}
TR -->|remediate| ACT["remediation_actor"]
ACT <--> VER["remediation_verifier"]
VER --> VR{"verify_route"}
VR -->|retry| ACT
VR -->|done| RSUM["summarizer"]
RSUM --> FR["final_report"]
TR -->|resolved| FR
end Mapping from the deprecated agents¶
| Old construct | New construct |
|---|---|
health_check_agent (ParallelAgent) | fan-out tuple ("START", (…5 checkers…)) + health_join (JoinNode) |
incident_triage_agent (SequentialAgent) | chain health_join → triage_summarizer → journal_writer |
remediation_loop (LoopAgent, max=3) | actor → verifier → verify_route with RoutingMap {"retry": actor, "done": summarizer}, bounded by remediation_iteration counter in state |
remediation_pipeline (SequentialAgent) | the remediation subgraph above |
exit_loop tool (actions.escalate) | mark_remediation_resolved tool + verify_route reading state |
Root LlmAgent + 6 AgentTools | preserved as orrery_chat_agent — the interactive root. |
Routing decisions are deterministic FunctionNodes¶
In the orrery_triage_workflow, triage_summarizer emits a structured incident_severity (healthy | degraded | critical) into state alongside its prose triage_report. triage_route and verify_route are pure Python FunctionNodes — unit-testable without an LLM. The MAX_REMEDIATION_ITERATIONS = 3 cap is enforced by a state counter, replacing LoopAgent.max_iterations.
Consequences¶
Positive¶
- Conversational routing preserved natively —
orrery_chat_agentretains full chat memory and free-form LLM delegation. - No deprecation warnings — the deprecated workflow agents are gone from the codebase.
- Deterministic & auditable automation — the triage/remediation path is a fixed graph (
orrery_triage_workflow); routing logic lives in testable functions, not LLM discretion. - Same building blocks — health-checkers, summarizer, actor, verifier, and specialist
LlmAgents are reused across both the interactive root and the batch workflow. - Plugins/RBAC/metrics unchanged — they attach at the
App/Runnerlevel. The Slack and Google Chat confirmation walkers traversegraph.nodesso guarded destructive tools still fire interactive approvals when the graph is run.
Negative / breaking¶
- Split Entry Points — Conversational interactions and full deterministic DAG executions now start from different orchestrators.
planner_routingeval removed — it asserted LLM→AgentTool routing against the old root module.- ADR-002 superseded — the AgentTool-vs-sub-agent decision no longer solely governs the system shape (kept for historical context).
Neutral¶
- Specialist agents still build with planners; planner wiring is unaffected.
runner.py/server.pyaccept anAgent | Workflowroot, allowing integration tests against either.
Implementation¶
agents/orrery-assistant/orrery_assistant/agent.py— define node agents,orrery_chat_agent(conversational LLM + AgentTools + memory), andorrery_triage_workflow(Workflow); exportorrery_chat_agentasroot_agent.agents/orrery-assistant/run_triage.py— standalone script to execute the deterministic triage graph.agents/orrery-assistant/orrery_assistant/remediation.py— expose actor/verifier/summarizer nodes +verify_route+mark_remediation_resolved; dropLoopAgent/SequentialAgent.agents/{slack-bot,google-chat-bot}— confirmation walkers traversegraph.nodes(for integration with workflow executions).core/orrery_core— removed the deprecatedcreate_sequential/parallel/loop_agentfactories;run_persistent/create_appacceptAgent | Workflow.- Tests:
test_graph_flow.py(end-to-end execution of routing nodes),test_planner_wiring.py(graph vs conversational root structure).