Record the direction and build plan for replacing the upstream google-cloud-solution-architecture skill with a portable agent. - ADR-0002: Agent Skills are the portable unit of behaviour, loaded by the framework's native skill runtime; discovery and grounding use each Cloud provider's hosted remote MCP servers. - ADR-0003: LangGraph holds Execution state, checkpoints and interrupts; a ReAct Orchestrator and Specialists run as ADK LlmAgents with SkillToolset. Supersedes ADR-0001. - CONTEXT.md: domain glossary (Execution, Phase, Approval, Revision, Orchestrator, Specialist, Deliverable, Dependency, Cloud provider). - .scratch/solution-architect-agent/spec.md: build spec with the dependency graph, Revision rules, tool tiers, MCP allowlists, A2A interaction, tests and nine build increments. - .scratch/phase-pipeline/spec.md: superseded; kept as decision log. - CLAUDE.md and docs/agents/: agent skill configuration (local markdown issue tracker, triage labels, domain docs). - README.md: direction note; flags current ADK classes as stubs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
28 KiB
Spec: Phase pipeline
Status: superseded by .scratch/solution-architect-agent/spec.md. Don't implement. This file remains the decision log (D1 to D46), and its still-valid cleanup items moved to increment 9 of the new spec.
Collapse the three orchestrations of the five Phases into one deep Phase pipeline module on LangGraph, with a single phase table as the source of truth.
- Origin: architecture review candidate 01 (2026-09-15), settled through a grilling session.
- Glossary:
CONTEXT.md. Decision records: ADR-0001 (reopened), ADR-0002.
Reopened by ADR-0002
The agent is meant to replace the upstream google-cloud-solution-architecture skill. It should run Agent Skills through a framework's own skill runtime (ADK SkillToolset, or Deep Agents skills=), stay portable across Cloud providers, and target Google Cloud first. The decisions below were made before that goal was stated.
| Decision | Why it's in question |
|---|---|
D1 LangGraph StateGraph sequences Phases |
If the Skill describes the workflow and a skill runtime executes it, code-owned sequencing duplicates the Skill. The framework choice shrinks to "which skill runtime first". Note that Deep Agents compiles to a LangGraph graph, so the KAB workflow standard can still be met. |
| D2 Phase bodies stay deterministic templates | A Skill's instructions only matter if a model executes them. Templates bypass the Skill entirely. |
| D4 No approval gate | The upstream Skill requires explicit user approval of every Deliverable, and permission before running any script. |
| D6 Phase row binds one Skill per Phase | The upstream Skill is one Skill covering all four Phases, with references/ and assets/. It sits badly with one-Skill-per-Phase, and our app/skills/* don't follow the specification (snake_case names, custom phase: key). |
D7 step(state, ctx) with an injected skill_prompt |
Skill runtimes use progressive disclosure: the model calls load_skill / load_skill_resource. They don't take prompt injection. |
| D5, D11 to D13, D15, D16 | These depend on who sequences Phases. |
| Phase list | Upstream has four Phases, with current state folded into requirements discovery. Ours has five, with a separate source_discover. |
Settled so far (round 5)
| # | Decision |
|---|---|
| D19 | Hybrid sequencing. Code owns Phase boundaries and the Approvals between them. The Skill, run through a skill runtime, owns the work within each Phase. |
| D20 | Interactive by default, with multi-turn Approvals. A pre-approved mode serves batch and eval runs. The /generate one-shot contract (D3) is no longer the primary interface. |
| D21 | Vendor the upstream Skill unmodified and pinned to a commit. Our tooling gets its own Skills. The five app/skills/* are retired. |
| D22 | A Cloud provider is a configured bundle of Skills and tools. No provider interface yet, and no provider prefix on core module names. |
| D23 | Not linear. A hierarchy of agents follows the dependencies between Deliverables, and every agent runs a ReAct loop. This supersedes the linear phase table (D5, D6) and the round 6 questions on the engine (Q23), Approval granularity (Q25) and Phase completion (Q26), which are re-asked once the hierarchy is settled. |
Settled in rounds 6 and 7
| # | Decision |
|---|---|
| D24 (Q23) | Engine. LangGraph holds Execution state, checkpoints and interrupt(). The Orchestrator and Specialists are ADK LlmAgents with SkillToolset, behind one agent-runtime interface. Deep Agents is a later second implementation. See ADR-0003. |
| D25 (Q24) | Upstream's 4 Phases, as groupings of Deliverables. The environment scan becomes a tool the requirements analyst can call. |
| D26 (Q25) | Approvals. Code enforces and records Approval at Phase boundaries. Within a Phase, approval is conversational and driven by the Skill. Every user question is an interrupt(). |
| D27 (Q26) | Completion. An agent finishes its work by calling a submit tool, and code checks the submission against the Deliverable contract. If anything is missing, the tool tells the agent what. |
| D28 (Q27) | Transport. A2A for interactive Executions (input-required covers questions and Approvals). /generate is kept for pre-approved Executions. |
| D29 (Q28) | Hierarchy. An Orchestrator sits above one Specialist per Deliverable. A Specialist fans out only for independent parts, such as the 6 WAF pillar advisors under the design advisor. |
| D30 (Q29) | Dependency graph declared in code. The ReAct Orchestrator chooses work through tools (ready_work, delegate, request_approval) that refuse work whose dependencies aren't met. Independent work may run in parallel. |
| D31 (Q30) | Per-agent ReAct step budgets, configurable. Hitting a budget hands control back to the user with a summary. |
Settled in round 8
| # | Decision |
|---|---|
| D32 (Q31) | Two thresholds. A Dependency inside a Phase is met once the Deliverable is submitted, and a Specialist submits only after the user's conversational OK. A Dependency across Phases is met only by that Phase's Approval. |
| D33 (Q32) | One voice. Only the Orchestrator talks to the user. A Specialist asks through needs_input(question), and questions from parallel Specialists are combined. |
| D34 (Q33) | Stateless Specialists. Each delegation starts fresh from its Dependencies, any feedback and the user's answers. Only the LangGraph checkpoint persists. |
| D35 (Q34) | Revision replaces the Review round. Reopening a Deliverable returns it and everything that depends on it to draft, and revokes affected Phase Approvals. Revisions per Deliverable are capped, and at the cap the Orchestrator escalates to the user. |
| D36 (Q35) | Models are configurable per agent role via ADK LiteLLM, with a Gemini default. |
| D37 (Q36) | New spec at .scratch/solution-architect-agent/spec.md. This spec becomes superseded, and its still-valid parts become a cleanup section there. |
Settled in round 9
| # | Decision |
|---|---|
| D38 (Q37) | The dependency graph is as proposed: 9 Deliverables across 4 Phases, deployment guidance depends on design recommendations, and runtime validation is out of scope for v1. |
| D39 (Q38) | Shared SkillToolset plus a role file per agent (agents/<role>.md, framework-neutral). Pillar advisors are scoped to their own WAF Skill. |
| D40 (Q39) | Vendor the upstream solution-architecture Skill and the 6 WAF Skills into skills/vendor/google/, pinned to 81a31a6, with VENDOR.md, the Apache-2.0 licence and a sync script. |
| D41 (Q40) | Tool tiers. Offline tools run freely. Tools that read live cloud resources need permission once per Execution (allow_cloud_read in pre-approved mode). Nothing that changes cloud resources is exposed, and run_skill_script is disabled. |
| D42 (Q41) | Deliverables live in Execution state and the DB, and are written only to deliverables/executions/{id}/ after Phase 4 Approval. No shared-path copies. |
| D43 (Q42) | A fake agent runtime for deterministic tests. The real-model eval runs pre-approved with scripted user answers. |
| D44 (Q43) | Build alongside the current code, then one cleanup increment after the eval passes. |
| D45 | Discovery and grounding use the Cloud provider's own remote MCP servers (user direction, 2026-09-15). The servers are declared in the provider bundle, with reviewed read-only allowlists. All customer-reading MCP tools are cloud_read. GCPEnvironmentScanner and the hand-rolled Developer Knowledge client are retired. |
| D46 | Call remote MCP servers directly, with no platform MCP gateway for now (user decision, 2026-09-15). |
The spec needs rewriting around the Orchestrator and Specialists once the open questions are settled. The current design section still describes the superseded linear Phase pipeline.
Unaffected: delete the fake ADK compat layer (D10's deletions), clean Sessions (D9), and build once at startup (D14). The candidate 02 to 05 findings stand. Grounding and scanning ports (candidate 02) become more important, because they are the tooling a Skill calls on.
- Branch:
refactor/phase-pipeline, pushed to the Gitea remote; the PR is opened by a human.
Problem
The five Phases are declared in seven places: app/adk/agents.py, app/workflows/gcp_architecture_graph.py, app/nodes/__init__.py, each node's active_skills bookkeeping, app/card.py (twice), and workflow.yaml (which already omits source_discover). They are run by three orchestrators: a LangGraph StateGraph, an "ADK" SequentialAgent inside a LoopAgent, and an "ADK" Workflow. None of these uses the real google-adk package. HAS_NATIVE_ADK is never read, so every "ADK" class is a local stub. The five agent classes are pass-throughs. Every node builds a skill prompt and discards it. The review loop reruns all five Phases without passing validation findings back. And passing a session_id merges a previous Execution's outputs into the next one.
Goals
- One module owns Phase ordering, state merging, Phase contracts, the Review round, Skill binding and
active_skills. - One small interface:
async run(workflow_request) -> Execution. - Tests exercise the pipeline only through that interface.
- Adding or reordering a Phase is a one-row change to the phase table.
Non-goals (other review candidates)
- Grounding and environment scan ports (candidate 02). Steps keep constructing
GCPEnvironmentScannerand callingget_mcp_client(). - The Execution store (candidate 03).
DatabaseManagerstays as it is, dual-dialect branches included. - The Deliverable layout (candidate 04). The state-key → path mapping moves unchanged, and output paths in
workflow.yamlare left alone. - LangChain
@toolpass-throughs inapp/tools/(candidate 05).validatekeeps calling.invoke. - Typed Phase outputs (candidate 06). Outputs stay markdown strings.
- Making any Phase call an LLM. The interface gets ready for it; the step bodies keep their templates.
- A human approval gate, checkpointing or resume.
- Moving the transport to aiohttp A2A on :8000.
Decisions
| # | Decision |
|---|---|
| D1 | LangGraph StateGraph orchestrates Phases. The ADK compat layer is deleted. Real ADK may be used inside a step later (ADR-0001). |
| D2 | Target direction: discover, design and package become LLM-driven; source_discover and validate stay deterministic. This work only readies the interface. |
| D3 | Stable: the /generate response shape and the Deliverable paths. /card is derived from the phase table. workflow.yaml is test-checked against it. Agent and log names may change. |
| D4 | No approval gate. There is a single run. |
| D5 | Review round: a conditional edge validate → design while validation fails and rounds < MAX_REVIEW_ROUNDS. Findings reach design as review_feedback. |
| D6 | Phase row: Phase(name, skill, step, requires, produces). The pipeline enforces contracts, maintains active_skills and current_phase, and binds Skills by name. |
| D7 | Step: async step(state, ctx: PhaseContext) -> dict. ctx has skill_prompt and model=None. |
| D8 | The pipeline does no DB or file I/O. The caller records the Execution. |
| D9 | A Session groups Executions. Every Execution starts from clean state. |
| D10 | app/adk/ is removed. app/executions.py records Executions for the route and the eval harness. |
| D11 | Module: app/workflows/gcp_architecture_pipeline.py. Steps stay in app/nodes/. The state schema stays in app/states/. |
| D12 | The phase table is a constructor argument, so tests inject a fake table. Old orchestrator tests are deleted. |
| D13 | The card is derived at import. workflow.yaml gains source_discover, and a test asserts its phase ids match. |
| D14 | The pipeline is built once in the Starlette lifespan and stored on app.state.pipeline. |
| D15 | When Review rounds run out with validation still failing, the pipeline packages anyway: validation_passed=false, status completed_with_warnings. |
| D16 | A contract breach raises PhaseContractError. The Execution is recorded failed, the route returns HTTP 500, and no Deliverables are written. |
| D17 | Blocking I/O in source_discover and design is offloaded with asyncio.to_thread. |
| D18 | Also: remove google-adk from requirements.txt, rewrite the README's ADK sections, delete the unreachable duplicate return in routes.py. |
Design
Module: app/workflows/gcp_architecture_pipeline.py
Replaces app/workflows/gcp_architecture_graph.py.
@dataclass(frozen=True)
class PhaseContext:
skill_prompt: str
model: Any | None = None # D2: populated when a Phase becomes LLM-driven
Step = Callable[[Mapping[str, Any], PhaseContext], Awaitable[dict[str, Any]]]
@dataclass(frozen=True)
class Phase:
name: str # e.g. "design"
skill: str # Skill name, e.g. "architecture_design"
step: Step
requires: tuple[str, ...] # state keys that must be present (not None) before the step
produces: tuple[str, ...] # state keys the step must return
@dataclass(frozen=True)
class ReviewRound:
number: int # 1-based
passed: bool
findings: tuple[str, ...]
@dataclass(frozen=True)
class Execution:
execution_id: str
workflow_request: str
outputs: Mapping[str, Any] # final state values for every Phase's `produces`
validation_passed: bool
review_rounds: tuple[ReviewRound, ...]
active_skills: tuple[str, ...]
current_phase: str # last Phase run ("package" on success)
class PhaseContractError(Exception):
phase: str
missing: tuple[str, ...]
kind: Literal["requires", "produces"]
class PhasePipeline:
def __init__(self, phases: Sequence[Phase], skills: SkillLoader, max_review_rounds: int) -> None: ...
async def run(self, workflow_request: str) -> Execution: ...
PHASES: tuple[Phase, ...] = (...) # production table, below
Construction invariants. Each of these raises ValueError from __init__:
- phase names are not unique
- a Phase's
skillis missing from theSkillLoader - there is no Phase named
designorvalidate(the Review round needs both)
The StateGraph is compiled once, in __init__.
Graph. Linear edges follow table order. The exception is one conditional edge leaving validate:
- Loop back: if
validation_passedis false and the rounds so far are fewer thanmax_review_rounds, go todesign. - Otherwise: continue to the Phase after
validate.
Per-Phase node wrapper. The pipeline generates one per row, and it does the following in order:
- Check inputs. Every
requireskey must be present and not None in state. Otherwise raisePhaseContractError(kind="requires"). - Run the step. Build
PhaseContext(skill_prompt=<that Skill formatted for prompt>)and awaitstep(read-only state view, ctx). - Check outputs. Every
produceskey must be in the returned dict. Otherwise raisePhaseContractError(kind="produces"). - Merge only declared keys. Keys the step returns that aren't in
producesare dropped, with a warning logged. - Update bookkeeping.
- Set
current_phaseto the Phase name. - Append the Skill to
active_skillsif it isn't already there. - For
design: incrementreview_round. - For
validate: append aReviewRound(review_round, validation_passed, errors)toreview_rounds. On failure, setreview_feedback = errors.
- Set
Key presence. A key is present if it is not None. False, "" and [] all count as present.
run.
- Initial state:
{execution_id: uuid4, workflow_request, active_skills: [], review_round: 0, review_rounds: [], review_feedback: []}. Nothing is carried over from a Session (D9). - Execution:
ainvokethe compiled graph, then build theExecution. - Errors:
PhaseContractErrorpropagates to the caller.
State: app/states/state.py
GCPArchitectureState changes:
- Added:
execution_id: str,review_round: int,review_rounds: list[ReviewRound],review_feedback: list[str]. - Removed:
target_dir. Nothing reads it;/generatestill accepts the field and ignores it, as it effectively does today.
Production phase table
| Phase | Skill | requires | produces |
|---|---|---|---|
source_discover |
source_discovery |
workflow_request |
source_discovery_doc, source_mermaid_diagram |
discover |
requirements_discovery |
workflow_request |
requirements_doc, product_selection_deferred |
design |
architecture_design |
requirements_doc |
architecture_doc, mermaid_diagram, terraform_code, product_selection_deferred |
validate |
validation_rules |
mermaid_diagram, terraform_code |
validation_results, validation_passed, errors |
package |
packaging_guide |
source_discovery_doc, source_mermaid_diagram, requirements_doc, architecture_doc, mermaid_diagram, terraform_code, validation_results |
solution_guide, status_summary |
design doesn't read requirements_doc today. The table declares the dependency the Phase is meant to have (see workflow.yaml). design may also read review_feedback, which is always present.
Steps: app/nodes/*.py
- New signature: each
*_node(state, skill_loader)becomesasync def *_step(state, ctx) -> dict(D7). Bodies keep their current templates. - Removed from steps: each step returns only its
produceskeys. Theskill_loader.format_skills_for_prompt(...)call and theactive_skills/current_phasebookkeeping are deleted. - Blocking I/O (D17):
source_discover:scan_result = await asyncio.to_thread(scanner.scan_environment). The scanner is also constructed inside the thread, because its__init__can shell out togcloud.design:await asyncio.to_thread(mcp_client.search_documents, ...).
- Skills:
SkillLoadergainsformat_skill_for_prompt(name) -> str, which gives the single-Skill form offormat_skills_for_prompt.
Recording: app/executions.py
A plain module that replaces ADKAgentRunner, PostgresSessionService, PostgresArtifactRepository and ADKEvaluator's persistence. It uses get_db_manager() for now (candidate 03 replaces that).
record(execution, session_id, base_dir) -> None:- Writes Deliverables using the state-key → path mapping moved verbatim from
app/adk/artifacts.py. That covers current paths, per-execution paths anddocs/mirrors. - Saves one
adk_artifactsrow per written file. - Saves the session row (
agent_name = settings.AGENT_NAME,state_data= the Execution as JSON). - Saves a
workflow_executionsrow with statuscompletedorfailedfromvalidation_passed,loop_count = len(review_rounds), and the results JSON as today. - Writes one
orchestrator_review_logsrow perReviewRound:reviewer_agent="validate",review_statusAPPROVEDorNEEDS_REVISION,feedback= the joined findings.
- Writes Deliverables using the state-key → path mapping moved verbatim from
record_failure(execution_id, session_id, workflow_request, error: PhaseContractError) -> None: saves aworkflow_executionsrow with statusfailed,current_phase = error.phaseand the error details in the results JSON. It writes no Deliverables.to_generate_response(execution, session_id) -> dictbuilds the stable/generateshape:execution_id,session_id,validation_passed,current_phase,active_skillsstatus:successorcompleted_with_warningstotal_loop_iterations:len(review_rounds)artifacts: the same 8 keys as today
HTTP: app/agent.py, app/workflows/routes.py
- Lifespan: runs
get_db_manager()as today, thenapp.state.pipeline = PhasePipeline(PHASES, loaded SkillLoader, settings.MAX_REVIEW_ROUNDS)(D14). POST /generate:await request.app.state.pipeline.run(workflow_request), thenexecutions.record(...), then returnto_generate_response(...).- On
PhaseContractError: callexecutions.record_failure(...)and return HTTP 500 with{"error": str(e), "phase": e.phase, "missing": [...]}. - A missing or invalid
session_idin the body gets a new uuid, as today.
- On
GET /sessions/{id}: reads throughget_db_manager().get_session. The response shape is unchanged, butagent_namebecomesgcp_solution_architecture_agent.GET /health: drops"adk_framework".POST /validate: unchanged. Delete the unreachable duplicatereturn.
Card and manifest
app/card.py:capabilities.phases = [p.name for p in PHASES]andcapabilities.local_skills = [p.skill for p in PHASES]. All other fields are unchanged.workflow.yaml: add asource_discoverphase entry beforediscover. Renumberingstepis left to the implementer, as long as order matchesPHASES.
Settings: app/config.py, .env.example
MAX_REVIEW_ROUNDS: int = 5, which also reads the legacy env nameADK_MAX_LOOP_ITERATIONS(pydanticAliasChoices).- Delete
ADK_MAX_LOOP_ITERATIONSandADK_ENABLE_ARTIFACT_STORE(the latter is never read). - Update
.env.exampleto match.
Eval: eval/eval_harness.py
EvalHarness.run_eval_suitebecomesasync.EvalHarnessbuilds aPhasePipeline, awaitsrun(case["workflow_request"]), scoresexecution.outputswitheval/metrics.evaluate_case_run, and saves withget_db_manager().save_evaluation.main()usesasyncio.run.- This removes the
app → evalimport thatapp/adk/evaluation.pycreated.
Deleted
app/adk/(all 9 files)app/workflows/gcp_architecture_graph.pytests/test_graph_workflow.pygoogle-adkfromrequirements.txt
Behaviour changes (call out in the PR)
- A
session_idno longer seeds an Execution with a previous Execution's state. - A failing validation now re-runs only
design → validate, not all five Phases. - A Phase contract breach returns HTTP 500 and records a
failedExecution. Before, the pipeline silently continued with empty strings. /sessions/{id}returnsagent_name: "gcp_solution_architecture_agent", notOrchestratorLoopAgent./healthno longer includesadk_framework.total_loop_iterationscounts Review rounds, not whole-pipeline passes. Its value is1on the happy path, the same as today.workflow.yamllistssource_discover.
Testing
Tests go through PhasePipeline.run() and the HTTP interface only, using pytest-asyncio.
New: tests/test_phase_pipeline.py
These use a fake phase table of tiny async steps and a stub SkillLoader fixture (or a tmp skills dir):
- Phase order: Phases run in table order, and
current_phaseends on the last Phase. - Merging: only declared
produceskeys are merged, and extra keys are dropped. - Requires check: a missing
requireskey raisesPhaseContractError(kind="requires", phase=...). - Produces check: a step that omits a
produceskey raisesPhaseContractError(kind="produces", phase=...). - Presence:
Falseand""count as present. - Review round, passes on round 2:
validatefails once and then passes.designruns twice and receivesreview_feedbackon round 2;review_roundshas 2 entries andvalidation_passedis true. - Review round, never passes:
validatealways fails. Rounds stop atmax_review_rounds, the Phase aftervalidatestill runs, andvalidation_passedis false. - Skills:
ctx.skill_promptholds the bound Skill's content, andactive_skillslists each Skill once, even across Review rounds. - Clean runs: two
run()calls on one pipeline share no state. - Construction: duplicate names, an unknown Skill, or a missing
design/validateeach raiseValueError.
Also new:
- End to end:
run()with the realPHASESandapp/skills. Everyproduceskey is present,validation_passedis true, there are 5active_skillsand 1 review round. - Manifest check: the phase ids in
workflow.yamlequal[p.name for p in PHASES], and/cardphases and skills equal the table.
Changed
tests/test_agent_api.py: usewith TestClient(app) as client, because the lifespan must run soapp.state.pipelineexists. Existing assertions stay and guard the stable/generatecontract. Add one test for the 500 response on a contract breach, with a pipeline built from a breaching fake table swapped ontoapp.state.tests/test_adk_architecture.py:- Delete:
test_adk_tools,test_adk_postgres_session_service,test_adk_orchestrator_loop_agent,test_adk_workflow_composition,test_adk_agent_runner,test_adk_evaluator. - Keep:
test_database_manager_operations, moved totests/test_database.py. - Move: the session endpoint test into
test_agent_api.py, asserting the newagent_name. - Then delete the file.
- Delete:
tests/test_mcp_developer_knowledge.py: deletetest_adk_mcp_function_toolsand itsapp.adk.toolsimport.eval/test_eval_harness.py:test_eval_harness_benchmarkawaits the now-asyncEvalHarness.run_eval_suite()(@pytest.mark.asyncio), and its assertions are unchanged.test_skill_optimizer_recommendationsis untouched.
Known issue, left out of scope: /generate tests write Deliverables into the working tree and the local SQLite file (candidates 03 and 04).
Implementation increments
These are ordered. Each one leaves the suite green and is one or more commits on refactor/phase-pipeline. They are called increments to avoid clashing with the glossary's Phase.
- Baseline environment.
- Create a venv and install
requirements.txtandrequirements-dev.txt. - Run the current suite and record the baseline pass/fail list in this spec's
## Comments. No Python environment exists on the dev machine yet. - Create the branch.
- Create a venv and install
- Pipeline core, test-first.
- Add
Phase,PhaseContext,ReviewRound,Execution,PhaseContractError,PhasePipelineandSkillLoader.format_skill_for_prompt. - Write every fake-table test in
test_phase_pipeline.pyfirst. - Production code is untouched apart from these additions.
- Add
- Port the steps and the table.
- Convert the five nodes to async steps (including D17), define
PHASES, extendGCPArchitectureStateand add the end-to-end test. - Delete
gcp_architecture_graph.pyandtest_graph_workflow.py, and updateapp/workflows/__init__.pyandapp/nodes/__init__.pyexports. - The
app/adk/agents still call the old node names at this point, so either keep thin old-signature shims until increment 5 or do increments 3 and 5 together. Prefer shims, deleted in 5.
- Convert the five nodes to async steps (including D17), define
- Recording and HTTP wiring.
- Add
app/executions.py, the lifespan pipeline, the/generatesuccess and 500 paths,/sessionsviaDatabaseManager, and/health. - Rename settings, with the alias, and update
.env.example. - Update
test_agent_api.py(lifespan client, 500 test, session endpoint test).
- Add
- Remove ADK.
- Move the eval harness onto the pipeline.
- Delete
app/adk/, the old-signature shims, the ADK tests andgoogle-adkfromrequirements.txt. - Move the DB test to
tests/test_database.py.
- Card and manifest. Derive the card from
PHASES, addsource_discovertoworkflow.yaml, and add the manifest consistency test. - Docs and cleanup.
- Rewrite the README's ADK sections around the Phase pipeline.
- Delete the duplicate
returninroutes.py. - Run a final full suite plus
ruffandmypyon the touched modules. - Push the branch.
Acceptance
rg -n "app\.adk|google\.adk|OrchestratorLoopAgent|compat" app eval testsreturns nothing.- The phase list appears only in
PHASES, andworkflow.yamlis test-checked against it. - Every test listed under Testing exists and passes. The rest of the baseline suite is no worse than increment 1's record.
- The
/generateresponse keys match today's exactly:execution_id,session_id,status,validation_passed,total_loop_iterations,current_phase,artifacts(8 keys) andactive_skills. - The Deliverable paths written by
/generateare unchanged. - The README no longer describes ADK orchestration, and ADR-0001 is linked.