orrery-assistant¶
A multi-agent orchestrator that routes user requests to specialist agents. The root is a conversational chat-mode LlmAgent (orrery_chat_agent) that holds real conversation history and delegates to specialists via AgentTool. A separate graph Workflow (orrery_triage_workflow) provides a deterministic, parallel, bounded-loop incident-response pipeline for batch / scheduled runs.
This inverts the original design (LLM-routing root + deterministic SequentialAgent sub-pipeline). See ADR-003, which supersedes ADR-002: the deprecated SequentialAgent / ParallelAgent / LoopAgent wrappers were replaced by a native ADK 2.0 graph.
Architecture¶
orrery_chat_agent (chat-mode LlmAgent, ROOT — interactive: web / CLI / Slack / Chat)
├── [AgentTool] kafka_health_agent — Kafka cluster health, topics, lag
├── [AgentTool] k8s_health_agent — K8s nodes, pods, deploys, scale/restart/rollback
├── [AgentTool] observability_agent — Prometheus/Loki/Alertmanager
├── [AgentTool] elasticsearch_agent — ES health, indices, shards, ILM, ECK CRs
├── [AgentTool] docker_agent — Containers, stats, logs, compose
├── [AgentTool] ops_journal_agent — Notes, preferences, session tracking
├── [AgentTool] incident_triage_agent — Single-turn full health sweep across ALL systems
└── LoadMemoryTool — Cross-session memory recall (model-invoked)
orrery_triage_workflow (graph Workflow, ROOT — batch: `make run-triage`)
START ─▶ [parallel] kafka / k8s / docker / observability / elasticsearch checkers
─▶ health_join (JoinNode, waits for all 5)
─▶ triage_summarizer (record_triage_verdict → incident_severity)
─▶ journal_writer ─▶ triage_route
├─("remediate")▶ remediation_actor ⇄ remediation_verifier
│ └▶ verify_route ─("retry")▶ actor
│ └("done")▶ remediation_summarizer ─▶ final_report
└─("resolved")────────────────────────────────────────────────────────▶ final_report
A Workflow is not a BaseAgent, so it cannot be a sub-agent or AgentTool of the chat coordinator, and a chat-mode agent cannot be a routed node inside a graph — hence the two roots are separate entrypoints that reuse the same node agents. For interactive "run a triage" requests, the coordinator delegates to the single-turn incident_triage_agent AgentTool instead.

Specialist Agents (AgentTool)¶
Each specialist is the standalone agent reused as an AgentTool:
| AgentTool | Source | Handles |
|---|---|---|
kafka_health_agent | kafka-health | Cluster health, topics, consumer groups, lag, Strimzi CRs |
k8s_health_agent | k8s-health | Nodes, pods, deployments, logs, events, scale/restart/rollback (guarded) |
observability_agent | observability | Prometheus metrics/alerts, Loki logs, Alertmanager |
elasticsearch_agent | elasticsearch | Cluster/index/shard health, ILM, snapshots, ECK CRs |
docker_agent | docker-agent | Containers, stats, logs, compose status |
ops_journal_agent | ops-journal | Notes, preferences, session tracking, bookmarks |
After a significant investigation, the coordinator proactively suggests saving findings via ops_journal_agent. Relevant context from past sessions is recalled on demand via LoadMemoryTool — the coordinator calls load_memory when past context would help (e.g. before diagnosing a symptom or when the user references an earlier incident) rather than searching memory on every turn.
How Delegation Works¶
Conversational routing (interactive root)¶
orrery_chat_agent is the LLM coordinator. It keeps conversation history (mode="chat") and picks the right AgentTool based on intent:
- "what's the consumer lag?" →
kafka_health_agent - "list all pods in staging" →
k8s_health_agent - "is the cluster green?" →
elasticsearch_agent - "is everything healthy?" / "run a triage" →
incident_triage_agent(full sweep) - "save a note about this incident" →
ops_journal_agent
Deterministic triage Workflow (batch root)¶
orrery_triage_workflow is the graph-native pipeline for scheduled / batch runs:
- Parallel: five health checkers (Kafka, K8s, Docker, Observability, Elasticsearch) run concurrently, each writing its status to session state via
output_key. - Join + summarize:
health_joinwaits for all five, thentriage_summarizersynthesizes a report and callsrecord_triage_verdict(setsincident_severity). - Journal:
journal_writersaves the report as a note taggedincident-triage. - Route:
triage_routereads the verdict — degraded/critical → remediation, healthy → finish. If the LLM emitted no structured verdict it infers severity from the per-system reports and flagstriage_verdict_missing(fail-safe — never silently "resolved"). - Closed-loop remediation:
remediation_actor → remediation_verifier → verify_routeretries act→verify up toMAX_REMEDIATION_ITERATIONS(3), bounded by a state counter. The verifier callsmark_remediation_resolvedto stop early.
Running¶
cd agents/orrery-assistant
uv run adk web # ADK Dev UI (interactive chat root)
uv run adk run orrery_assistant # Terminal mode
Or from the repo root:
make run-dev # ADK Dev UI (in-memory state)
make run-cli # Terminal mode (in-memory state)
make run-cli PERSIST=1 # Terminal with persistence (PostgreSQL via DATABASE_URL, else in-memory)
make run-api # FastAPI front door + web console on :8000 (JWT auth)
make run-api SSO=1 # …with Keycloak SSO instead of a pasted token
make run-triage # Run the deterministic triage Workflow once (batch)
Persistent Mode¶
By default (adk web), state resets on restart. Use persistent mode to keep user:* and app:* state across sessions:
Without DATABASE_URL this runs in-memory; set a PostgreSQL DATABASE_URL and DatabaseSessionService keeps notes and preferences across restarts. Type new to start a fresh session while keeping long-term state.