Skip to content

[ddev/ai/phases]: Introduce AgenticPhase and make Phase an abstract lifecycle base#23663

Merged
AAraKKe merged 13 commits into
loa/openmetrics-ai-genfrom
loa/agent-phase-refactor
May 21, 2026
Merged

[ddev/ai/phases]: Introduce AgenticPhase and make Phase an abstract lifecycle base#23663
AAraKKe merged 13 commits into
loa/openmetrics-ai-genfrom
loa/agent-phase-refactor

Conversation

@luisorofino
Copy link
Copy Markdown
Contributor

@luisorofino luisorofino commented May 11, 2026

What does this PR do?

Refactors the phases layer of the AI framework to separate the lifecycle contract from the agent-driven implementation.

  • Splits Phase into two classes:
    • Phase (abstract): owns the immutable lifecycle skeleton — should_process_message, process_message, on_success, on_error, checkpoint writing, memory persistence. Subclasses must implement async execute(context) -> PhaseOutcome.
    • AgenticPhase(Phase): owns the LLM pipeline — builds the agent and ReActProcess, runs tasks (run_tasks), runs the memory step (_run_memory_step), and exposes before_react / after_react hooks.
  • Introduces a PhaseOutcome dataclass (memory_text, total_input_tokens, total_output_tokens, extra_checkpoint) as the single return value of execute(). The base Phase.process_message consumes it to assemble the checkpoint payload, so subclasses no longer reach into checkpoint internals.
  • Adds Phase.validate_config classmethod (no-op by default) and invokes it from the orchestrator for every phase scheduled in flow:. Orphan phases (defined but not in flow:) are skipped. AgenticPhase.validate_config enforces agent is set, refers to a known agent, and that tasks is non-empty.
  • Makes PhaseConfig.agent and PhaseConfig.tasks optional (with type default \"AgenticPhase\"), so non-agentic Phase subclasses can be configured without dummy agent/tasks entries.
  • Moves the agent_config / anthropic_client constructor parameters off of Phase and onto AgenticPhase, where they belong. The orchestrator only passes them when instantiating an AgenticPhase subclass.
  • Reorganizes tests: agent-driven behaviour now lives in test_agentic_phase.py; test_base.py covers the lifecycle contract via a _StubPhase. Shared mock helpers and fixtures (MockAgent, make_agent_phase, flow_dir, message_queue) moved into phases/conftest.py.

Motivation

Until now, Phase was both the lifecycle contract and the agent-driven implementation, which made it impossible to add a non-LLM Phase subclass (e.g. a deterministic validation phase, a phase that just reads files, etc.) without dragging along the entire agent machinery. Separating the two layers keeps the framework generic and reusable, and allows us to define non-LLM-driven phases.

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
  • Add the `qa/skip-qa` label if the PR doesn't need to be tested during QA.
  • If you need to backport this PR to another branch, you can add the `backport/` label to the PR and it will automatically open a backport PR once this one is merged

@dd-octo-sts dd-octo-sts Bot added the ddev label May 11, 2026
@datadog-official
Copy link
Copy Markdown
Contributor

datadog-official Bot commented May 11, 2026

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 2 Pipeline jobs failed

PR All | test / j44368ad (py3.13-18.0-C) / Postgres-py3.13-18.0-C   View in Datadog   GitHub Actions

🔧 Fix in code (Fix with Cursor). 1 failed test. AssertionError: Expected postgresql.wal_receiver.last_msg_send_age to have a value <= 1, got 4.423284

PR All | test / j06ca546 / SNMP   View in Datadog   GitHub Actions

🛟 This job is unlikely to succeed on retry. Please review your pipeline configuration. 38 test failures due to missing metrics: needed at least 1 candidates for 'snmp.ifNumber' or 'snmp.device.reachable', got 0.

🧪 20 Tests failed in 1 job

PR All | run   GitHub Actions

test_e2e_cisco_legacy_wlc from test_e2e_core.py   View in Datadog (Fix with Cursor)
Needed at least 1 candidates for &#39;snmp.device.reachable&#39;, got 0
Expected:
        MetricStub(name=&#39;snmp.device.reachable&#39;, type=0, value=1, tags=[&#39;agent_host:runnervm9vjqi&#39;, &#39;device_hostname:DDOGWLC&#39;, &#39;device_id:default:172.18.0.2&#39;, &#39;device_ip:172.18.0.2&#39;, &#39;device_namespace:default&#39;, &#39;device_vendor:cisco&#39;, &#39;snmp_device:172.18.0.2&#39;, &#39;snmp_host:DDOGWLC&#39;, &#39;snmp_profile:cisco-legacy-wlc&#39;], hostname=None, device=None, flush_first_value=None)
Difference to closest:
        Expected tag agent_host:runnervm9vjqi
        Found agent_host:None
        Expected tag device_hostname:DDOGWLC
        Found device_hostname:None
        Expected tag device_id:default:172.18.0.2
        Found device_id:None
...
test_e2e_cisco_nexus from test_e2e_core.py   View in Datadog (Fix with Cursor)
Needed at least 1 candidates for &#39;snmp.device.reachable&#39;, got 0
Expected:
        MetricStub(name=&#39;snmp.device.reachable&#39;, type=0, value=1, tags=[&#39;agent_host:runnervm9vjqi&#39;, &#39;device_hostname:Nexus-eu1.companyname.managed&#39;, &#39;device_id:default:172.18.0.2&#39;, &#39;device_ip:172.18.0.2&#39;, &#39;device_namespace:default&#39;, &#39;device_vendor:cisco&#39;, &#39;snmp_device:172.18.0.2&#39;, &#39;snmp_host:Nexus-eu1.companyname.managed&#39;, &#39;snmp_profile:cisco-nexus&#39;], hostname=None, device=None, flush_first_value=None)
Difference to closest:
        Expected tag agent_host:runnervm9vjqi
        Found agent_host:None
        Expected tag device_hostname:Nexus-eu1.companyname.managed
        Found device_hostname:None
        Expected tag device_id:default:172.18.0.2
        Found device_id:None
...

View all 20 test failures

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 99.47%
Overall Coverage: 87.62%

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: a0fae18 | Docs | Datadog PR Page | Give us feedback!

@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

❌ Patch coverage is 99.47090% with 3 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (loa/openmetrics-ai-gen@a33740f). Learn more about missing BASE report.

Additional details and impacted files
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@luisorofino luisorofino added the qa/skip-qa Automatically skip this PR for the next QA label May 12, 2026
@luisorofino luisorofino changed the title Loa/agent phase refactor refactor(ai/phases): introduce AgenticPhase and make Phase an abstract lifecycle base May 12, 2026
@luisorofino luisorofino changed the title refactor(ai/phases): introduce AgenticPhase and make Phase an abstract lifecycle base [ddev/ai/phases]: Introduce AgenticPhase and make Phase an abstract lifecycle base May 12, 2026
@luisorofino luisorofino marked this pull request as ready for review May 12, 2026 12:39
@luisorofino luisorofino requested a review from a team as a code owner May 12, 2026 12:39
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f725277fea

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ddev/src/ddev/ai/phases/base.py
Base automatically changed from loa/ddev-validate to loa/openmetrics-ai-gen May 20, 2026 10:50
Copy link
Copy Markdown
Collaborator

@AAraKKe AAraKKe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Luis! Sorry for the delay. I have some comments in terms of organization and structure. Seems that we are mixing some layers in the implementation that can bite us later.

My Feedback Legend

Here's a quick guide to the prefixes I use in my comments:

praise: no action needed, just celebrate!
note: just a comment/information, no need to take any action.
question: I need clarification or I'm seeking to understand your approach.
nit: A minor, non-blocking issue (e.g., style, typo). Feel free to ignore.
suggestion: I'm proposing an improvement. This is optional but recommended.
request: A change I believe is necessary before this can be merged.

The only blocking comments are request, any other type of comment can be applied at discretion of the developer.

Comment thread ddev/src/ddev/ai/phases/agentic_phase.py Outdated
Comment thread ddev/src/ddev/ai/phases/agentic_phase.py
Comment thread ddev/src/ddev/ai/phases/orchestrator.py Outdated
Comment thread ddev/src/ddev/ai/phases/base.py Outdated
@luisorofino luisorofino requested a review from AAraKKe May 20, 2026 14:24
- Add PhaseOutcome dataclass (memory_text, token counts, extra_checkpoint)
- Add validate_config() classmethod to Phase (no-op default)
- Add execute() method that implements the agent pipeline (later to be overridden by AgentPhase)
- Rewrite process_message() to call execute() and assemble the checkpoint from PhaseOutcome
- Create agent_phase.py with AgentPhase(Phase) that owns the LLM pipeline:
  before_react/after_react hooks, run_tasks, execute()
- Move render_task_prompt and render_memory_prompt to agent_phase.py
- AgentPhase.validate_config enforces agent, known-agent, and non-empty tasks
- Phase.execute() now raises NotImplementedError — subclasses must implement it
- Strip base.py of all agent-specific code and imports
- Split test_base.py into lifecycle-only tests (using _StubPhase) and
  test_agent_phase.py for the agent-driven behaviour tests
- type default: "Phase" → "AgentPhase"
- agent: str (required) → str | None = None
- tasks: list[TaskConfig] (required) → list[TaskConfig] = []
- Remove at_least_one_task field validator (now enforced by AgentPhase.validate_config)
- FlowConfig.cross_references: skip unknown-agent check when agent is None
- orchestrator: guard agent_config lookup against None, import AgentConfig
- test_config.py: update type assertion, remove empty_tasks test, add
  test_flow_config_phase_without_agent_validates
- test_base.py / test_agent_phase.py: drop model_construct workarounds
- Call phase_cls.validate_config(phase_id, config, agents) immediately after
  resolving the phase class in on_initialize — only for phases scheduled in flow:
- Orphan phases (defined but absent from flow:) are skipped before the call
- test_orchestrator.py: drop explicit type: Phase lines from fixtures (use AgentPhase default),
  assert AgentPhase is registered by discovery, add tests for validate_config
  invocation and orphan-skip behaviour
@luisorofino luisorofino force-pushed the loa/agent-phase-refactor branch from f80411e to a0fae18 Compare May 20, 2026 14:48
@dd-octo-sts
Copy link
Copy Markdown
Contributor

dd-octo-sts Bot commented May 20, 2026

Validation Report

All 20 validations passed.

Show details
Validation Description Status
agent-reqs Verify check versions match the Agent requirements file
ci Validate CI configuration and Codecov settings
codeowners Validate every integration has a CODEOWNERS entry
config Validate default configuration files against spec.yaml
dep Verify dependency pins are consistent and Agent-compatible
http Validate integrations use the HTTP wrapper correctly
imports Validate check imports do not use deprecated modules
integration-style Validate check code style conventions
jmx-metrics Validate JMX metrics definition files and config
labeler Validate PR labeler config matches integration directories
legacy-signature Validate no integration uses the legacy Agent check signature
license-headers Validate Python files have proper license headers
licenses Validate third-party license attribution list
metadata Validate metadata.csv metric definitions
models Validate configuration data models match spec.yaml
openmetrics Validate OpenMetrics integrations disable the metric limit
package Validate Python package metadata and naming
readmes Validate README files have required sections
saved-views Validate saved view JSON file structure and fields
version Validate version consistency between package and changelog

View full run

@AAraKKe AAraKKe merged commit 713d4f8 into loa/openmetrics-ai-gen May 21, 2026
340 of 342 checks passed
@AAraKKe AAraKKe deleted the loa/agent-phase-refactor branch May 21, 2026 15:46
luisorofino added a commit that referenced this pull request May 26, 2026
…ifecycle base (#23663)

* refactor(ai/phases): introduce PhaseOutcome and abstract Phase.execute()

- Add PhaseOutcome dataclass (memory_text, token counts, extra_checkpoint)
- Add validate_config() classmethod to Phase (no-op default)
- Add execute() method that implements the agent pipeline (later to be overridden by AgentPhase)
- Rewrite process_message() to call execute() and assemble the checkpoint from PhaseOutcome

* refactor(ai/phases): extract AgentPhase from Phase

- Create agent_phase.py with AgentPhase(Phase) that owns the LLM pipeline:
  before_react/after_react hooks, run_tasks, execute()
- Move render_task_prompt and render_memory_prompt to agent_phase.py
- AgentPhase.validate_config enforces agent, known-agent, and non-empty tasks
- Phase.execute() now raises NotImplementedError — subclasses must implement it
- Strip base.py of all agent-specific code and imports
- Split test_base.py into lifecycle-only tests (using _StubPhase) and
  test_agent_phase.py for the agent-driven behaviour tests

* refactor(ai/phases): make PhaseConfig.agent and .tasks optional

- type default: "Phase" → "AgentPhase"
- agent: str (required) → str | None = None
- tasks: list[TaskConfig] (required) → list[TaskConfig] = []
- Remove at_least_one_task field validator (now enforced by AgentPhase.validate_config)
- FlowConfig.cross_references: skip unknown-agent check when agent is None
- orchestrator: guard agent_config lookup against None, import AgentConfig
- test_config.py: update type assertion, remove empty_tasks test, add
  test_flow_config_phase_without_agent_validates
- test_base.py / test_agent_phase.py: drop model_construct workarounds

* refactor(ai/phases): invoke Phase.validate_config from orchestrator

- Call phase_cls.validate_config(phase_id, config, agents) immediately after
  resolving the phase class in on_initialize — only for phases scheduled in flow:
- Orphan phases (defined but absent from flow:) are skipped before the call
- test_orchestrator.py: drop explicit type: Phase lines from fixtures (use AgentPhase default),
  assert AgentPhase is registered by discovery, add tests for validate_config
  invocation and orphan-skip behaviour

* Rename AgentPhase to AgenticPhase

* Split AgenticPhase's execute into smaller functions and added tests for them

* Move agent and client parameters to AgenticPhase and make Phase abstract

* Add e2e Phase contract test

* Move some tests from agentic phase to conftest

* Phase not registered and improve tests

* Prevent extra_checkpoint from overriding checkpoint_payload

* Make Phase and Orchestrator model-agnostic

* Add Phase.extra_init_kwargs and agent/build.py tests
luisorofino added a commit that referenced this pull request Jun 1, 2026
…ifecycle base (#23663)

* refactor(ai/phases): introduce PhaseOutcome and abstract Phase.execute()

- Add PhaseOutcome dataclass (memory_text, token counts, extra_checkpoint)
- Add validate_config() classmethod to Phase (no-op default)
- Add execute() method that implements the agent pipeline (later to be overridden by AgentPhase)
- Rewrite process_message() to call execute() and assemble the checkpoint from PhaseOutcome

* refactor(ai/phases): extract AgentPhase from Phase

- Create agent_phase.py with AgentPhase(Phase) that owns the LLM pipeline:
  before_react/after_react hooks, run_tasks, execute()
- Move render_task_prompt and render_memory_prompt to agent_phase.py
- AgentPhase.validate_config enforces agent, known-agent, and non-empty tasks
- Phase.execute() now raises NotImplementedError — subclasses must implement it
- Strip base.py of all agent-specific code and imports
- Split test_base.py into lifecycle-only tests (using _StubPhase) and
  test_agent_phase.py for the agent-driven behaviour tests

* refactor(ai/phases): make PhaseConfig.agent and .tasks optional

- type default: "Phase" → "AgentPhase"
- agent: str (required) → str | None = None
- tasks: list[TaskConfig] (required) → list[TaskConfig] = []
- Remove at_least_one_task field validator (now enforced by AgentPhase.validate_config)
- FlowConfig.cross_references: skip unknown-agent check when agent is None
- orchestrator: guard agent_config lookup against None, import AgentConfig
- test_config.py: update type assertion, remove empty_tasks test, add
  test_flow_config_phase_without_agent_validates
- test_base.py / test_agent_phase.py: drop model_construct workarounds

* refactor(ai/phases): invoke Phase.validate_config from orchestrator

- Call phase_cls.validate_config(phase_id, config, agents) immediately after
  resolving the phase class in on_initialize — only for phases scheduled in flow:
- Orphan phases (defined but absent from flow:) are skipped before the call
- test_orchestrator.py: drop explicit type: Phase lines from fixtures (use AgentPhase default),
  assert AgentPhase is registered by discovery, add tests for validate_config
  invocation and orphan-skip behaviour

* Rename AgentPhase to AgenticPhase

* Split AgenticPhase's execute into smaller functions and added tests for them

* Move agent and client parameters to AgenticPhase and make Phase abstract

* Add e2e Phase contract test

* Move some tests from agentic phase to conftest

* Phase not registered and improve tests

* Prevent extra_checkpoint from overriding checkpoint_payload

* Make Phase and Orchestrator model-agnostic

* Add Phase.extra_init_kwargs and agent/build.py tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ddev qa/skip-qa Automatically skip this PR for the next QA team/agent-integrations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants