Architecture
`adk-loop-lab` is built around a deterministic controller, not a free-running chat loop. The controller owns lifecycle order, checkpoints, and stop decisions, while adjacent modules for context, evaluation, memory, and tool governance provide reusable building blocks that are only partially integrated into the generic runtime today.
Seven-Phase Lifecycle
The lifecycle mirrors the controller contract described in docs/architecture/overview.md: observe the current world, select the next bounded move, verify it, persist state, and only then evaluate whether continuing is justified. The architecture modules support richer context reconstruction and memory-aware execution, but the generic controller still takes a simpler path in a few places.
Three-Plane Separation
- Control plane: deterministic Python in
loop/handles phase ordering, budgets, and checkpointing. - Execution plane:
adk/,context/, andtools/translate a bounded action into model and tool work. - Data plane:
state/,memory/, andevents/preserve authoritative facts, reusable lessons, and traces.
Module Map
The package split is intentionally plain:
loop/owns the lifecycle, policies, recovery, and checkpoints.adk/absorbs ADK version churn through agents, runner helpers, workflow helpers, and a compatibility layer.context/implements reusable prompt reconstruction from goal, constraints, state, memory, failures, and budget status, though the generic controller currently assembles simpler PLAN and REFLECT prompts directly.memory/,state/, andevents/keep long-lived records inspectable and local-first.evaluation/includes deterministic validators and composite policy helpers, although the generic controller currently uses inline average-score and all-pass logic rather than the richer composite helper.
Deterministic Shell Principle
That principle gives the repo its core trust boundaries:
- current facts come from state, not prompt memory
- promotion helpers require evidence before lessons are verified
- irreversible operations can be separated from routine reads and reversible writes
- traces can be redacted without losing lifecycle visibility
Budget Enforcement
Budget exhaustion is a deterministic stop condition rather than a model judgment.
| Budget | Why it exists | Terminal outcome |
|---|---|---|
| Iterations | Prevent unbounded looping | BUDGET_EXHAUSTED |
| Model calls | Keep agent spend explicit | BUDGET_EXHAUSTED |
| Tool calls | Bound side effects and churn | BUDGET_EXHAUSTED |
| Elapsed duration | Avoid stale or hanging runs | BUDGET_EXHAUSTED |
| Stagnation threshold | Stop repeated non-progress | STAGNATED |
In the current controller, model and tool counters are recorded after those calls complete and exhaustion is applied in stopping logic. The default stopping order is still explicit: fatal error, success, budget exhaustion, stagnation, duration exhaustion, otherwise continue.
Key Design Decisions
The architecture pages and ADRs line up around three durable choices:
- Google ADK is the execution surface. ADR 001 keeps agent construction and workflow composition in
adk/so the controller stays ADK-agnostic. - SQLite FTS5 is the default memory backend. ADR 002 favors local inspectability and evidence-gated promotion over a mandatory vector store.
- Async-shaped
sqlite3adapters replaceaiosqlite. ADR 003 preserves the async call shape without adding runtime fragility in local and sandboxed environments.
From there, the architecture stays conservative: keep the shell deterministic, keep state authoritative, keep verification in front of promotion, and keep every loop bounded.