Skip to content

feat(core): Agent Adapter protocol definition - #429

Merged
frankbria merged 2 commits into
mainfrom
feature/issue-409-agent-adapter-protocol
Mar 9, 2026
Merged

feat(core): Agent Adapter protocol definition#429
frankbria merged 2 commits into
mainfrom
feature/issue-409-agent-adapter-protocol

Conversation

@frankbria

@frankbria frankbria commented Mar 9, 2026

Copy link
Copy Markdown
Owner

Summary

Implements #409: [Phase 4] Agent Adapter Protocol Definition

  • Defines AgentAdapter Protocol (runtime_checkable) for structural subtyping — any coding agent just needs the right methods
  • AgentContext dataclass captures all context CodeFrame assembles (PRD, tech stack, preferences, files, gates)
  • AgentResult dataclass for engine outcomes (status, files changed, errors, blockers)
  • AgentEvent dataclass for progress streaming during execution
  • AgentResultStatus enum: completed/failed/blocked/timeout
  • AdapterTokenUsage lightweight dataclass — decoupled from the heavy billing TokenUsage model

Acceptance Criteria

  • AgentAdapter Protocol defined with execute(), stream_events(), name, requires_api_key
  • AgentContext captures all context CodeFrame currently assembles
  • AgentResult captures outcome, modified files, errors, blockers
  • AgentEvent supports progress streaming
  • Existing ReactAgent can be wrapped to satisfy the protocol (design validation via tests)
  • Unit tests for dataclass construction and protocol compliance

Test Plan

  • 23 unit tests written (TDD approach — tests first, then implementation)
  • All 1605 core tests passing
  • Ruff linting clean
  • Code review passed

Implementation Notes

  • Used AdapterTokenUsage instead of importing TokenUsage from models.py — the existing model is a heavy Pydantic BaseModel with billing fields (agent_id, project_id, estimated_cost_usd) that would force adapter implementors to know about CodeFrame internals
  • Skipped __init__.py exports — consumers import directly from codeframe.core.agent_adapter, matching how other core modules are consumed
  • Protocol uses @runtime_checkable enabling isinstance() checks at adapter registration time

Closes #409

Summary by CodeRabbit

  • New Features

    • Added a standardized headless agent adapter so third-party coding agents can integrate with CodeFRAME to execute tasks, stream progress events, report statuses (completed/failed/blocked/timeout), and surface token usage and durations.
  • Tests

    • Added comprehensive tests validating adapter behavior, event streaming, result/status reporting, and data structure semantics.

…409)

Introduces the foundational types for Phase 4 Agent Adapter Architecture:
- AgentAdapter Protocol (runtime_checkable) for structural subtyping
- AgentContext dataclass with all context CodeFrame assembles
- AgentResult dataclass for engine outcomes
- AgentEvent dataclass for progress streaming
- AgentResultStatus enum (completed/failed/blocked/timeout)
- AdapterTokenUsage lightweight dataclass (decoupled from billing model)

Closes #409
@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR adds codeframe/core/agent_adapter.py, defining an AgentAdapter protocol and related enums/dataclasses (AgentResultStatus, AdapterTokenUsage, AgentContext, AgentResult, AgentEvent) and implements runtime-checkable protocol methods/properties; it also adds comprehensive unit tests in tests/core/test_agent_adapter.py.

Changes

Cohort / File(s) Summary
Agent Adapter Protocol Module
codeframe/core/agent_adapter.py
New module: AgentResultStatus enum; AdapterTokenUsage, AgentContext, AgentResult, AgentEvent dataclasses; AgentAdapter runtime-checkable Protocol with execute(task_prompt, workspace_path, context, timeout_ms) -> AgentResult, stream_events() -> Iterator[AgentEvent], name and requires_api_key properties. No HTTP/FastAPI dependencies.
Agent Adapter Tests
tests/core/test_agent_adapter.py
New comprehensive tests: enum values and string behavior, dataclass construction and defaults (including token totals and timestamp handling), protocol conformance via a compliant FakeAdapter, negative conformance tests, and end-to-end checks of execute and stream_events.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through code with gleeful cheer,

Built an adapter for agents near,
Protocol tidy, events that sing,
Tests to prove each little thing,
A carrot for CI — hop, compile, cheer! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding the Agent Adapter protocol definition to the core module.
Linked Issues check ✅ Passed All requirements from issue #409 are met: AgentAdapter Protocol with required methods/properties, AgentContext and AgentResult dataclasses, AgentEvent for streaming, @runtime_checkable decorator, and comprehensive unit tests for protocol compliance.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the Agent Adapter protocol definition as specified in #409; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/issue-409-agent-adapter-protocol

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

claude Bot commented Mar 9, 2026

Copy link
Copy Markdown

Code Review - PR 429: Agent Adapter Protocol Definition

This is a clean, well-scoped Phase 4 foundation. The design rationale is sound - a @runtime_checkable Protocol with lightweight supporting dataclasses that any external engine can satisfy without knowing CodeFrame internals.

Issues / Suggestions

1. AgentEvent.metadata missing type parameter

Bare dict reduces type-checker signal for consumers building adapters. Suggest dict[str, Any] with Any imported from typing.

2. stream_events() / execute() relationship is undocumented

The Protocol does not clarify when stream_events() is valid to call - before execute() returns (caller drives iteration in a thread), or after (pull events from a buffer)? This ambiguity will bite the first real adapter implementor. A short docstring on both methods would prevent that.

3. requires_api_key dict contract is undocumented

What do the keys and values represent? The FakeAdapter in tests returns {}, which does not help real implementors. A one-line docstring on the property (e.g. keys are env var names, values are human descriptions) would resolve this.

4. attempt field semantics

attempt: int = 0 - the default of 0 reads as zeroth attempt but could also be read as not set. A comment clarifying 0 = first attempt and N > 0 = Nth retry would remove ambiguity.

5. Intra-method imports in tests

All 23 tests import from within each test method rather than at module top. Module-level imports are standard practice and make test failures easier to distinguish from import failures. Consider consolidating.

Minor Observations (non-blocking)

  • files_deleted is not tracked in AgentResult - subprocess-based adapters (e.g. Codex CLI) may delete files. Worth a TODO comment for Phase 4 follow-up.
  • AgentEvent.type as bare str - a small Literal type would make valid event types discoverable. Acceptable to defer.
  • Sync-only interface - execute() returning a plain value is fine for subprocess-based adapters. Async wrappers will need a thread-executor bridge when that time comes.

What is Working Well

  • AdapterTokenUsage decoupling from the heavy Pydantic TokenUsage model is the right call - keeps adapter implementors free of CodeFrame billing internals.
  • @runtime_checkable Protocol enables isinstance() checks at registration time - exactly right for dynamic plugin-style adapter loading.
  • AgentContext.attempt + previous_errors give retry-aware adapters what they need to avoid repeating failed strategies.
  • test_list_defaults_are_independent is a nice catch - validates default_factory is correctly applied.
  • 23 tests covering construction, defaults, protocol compliance (positive and negative), and streaming iteration - solid coverage for a pure data/interface layer.

Verdict: Solid foundation. Key actionable items are the stream_events/requires_api_key docstrings and the metadata type annotation. The intra-method imports are a style nit that can be fixed here or in a follow-up cleanup PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@codeframe/core/agent_adapter.py`:
- Around line 82-94: The AgentAdapter protocol doesn't match implementations:
change the protocol to declare run(...) instead of execute(...), remove
stream_events() and requires_api_key from the core AgentAdapter, and create two
small optional protocols—StreamableAgent (with stream_events() ->
Iterator[AgentEvent]) and ApiKeyAgent (with requires_api_key: bool or def
requires_api_key() -> bool)—so BuiltinReactAdapter, BuiltinPlanAdapter, and
SubprocessAdapter will be recognized as implementing AgentAdapter (and can opt
into StreamableAgent/ApiKeyAgent if they support those features); update any
type checks or isinstance usages to reference the new optional protocols where
streaming or API-key behavior is required.

In `@tests/core/test_agent_adapter.py`:
- Around line 10-13: This module is missing the required v2 pytest marker; add
an import for pytest and declare a module-level marker variable named pytestmark
set to pytest.mark.v2 (i.e., add "import pytest" and "pytestmark =
pytest.mark.v2") so the entire test file is marked as v2; locate this in
tests/core/test_agent_adapter.py near the top-level imports to apply the marker
for the whole module.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ecfc7a1-d52d-4d3b-9382-9e46e602df60

📥 Commits

Reviewing files that changed from the base of the PR and between f0ea4bf and 492e701.

📒 Files selected for processing (2)
  • codeframe/core/agent_adapter.py
  • tests/core/test_agent_adapter.py

Comment on lines +82 to +94
@runtime_checkable
class AgentAdapter(Protocol):
"""Interface for any coding agent that CodeFrame can orchestrate."""

def execute(
self,
task_prompt: str,
workspace_path: Path,
context: AgentContext,
timeout_ms: int = 3_600_000,
) -> AgentResult: ...

def stream_events(self) -> Iterator[AgentEvent]: ...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
from typing import Protocol, runtime_checkable

`@runtime_checkable`
class BaseAdapter(Protocol):
    def execute(self) -> int: ...
    def stream_events(self): ...

class NoStream:
    def execute(self) -> int:
        return 1

class WithStream:
    def execute(self) -> int:
        return 1
    def stream_events(self):
        return iter(())

print("NoStream:", isinstance(NoStream(), BaseAdapter))
print("WithStream:", isinstance(WithStream(), BaseAdapter))
PY

Repository: frankbria/codeframe

Length of output: 95


🏁 Script executed:

# First, locate and read the agent_adapter.py file to verify the current protocol
fd agent_adapter.py --type f

Repository: frankbria/codeframe

Length of output: 210


🏁 Script executed:

# Search for AgentAdapter implementations to see if any omit stream_events
rg "class.*AgentAdapter" -A 10 --type py

Repository: frankbria/codeframe

Length of output: 3286


🏁 Script executed:

# Check how stream_events is used across the codebase
rg "stream_events" --type py -B 2 -A 2

Repository: frankbria/codeframe

Length of output: 2599


🏁 Script executed:

# Read the actual agent_adapter.py file to see the complete protocol definition
cat -n codeframe/core/agent_adapter.py | head -100

Repository: frankbria/codeframe

Length of output: 3469


🏁 Script executed:

# Check the test that validates protocol compliance without stream_events
cat -n tests/core/test_agent_adapter.py | grep -A 15 "PartialAdapter"

Repository: frankbria/codeframe

Length of output: 1144


🏁 Script executed:

# Look at how AgentAdapter is actually used in the codebase
rg "isinstance.*AgentAdapter" --type py -B 2 -A 2

Repository: frankbria/codeframe

Length of output: 6108


🏁 Script executed:

# Check if any existing adapter implementations skip stream_events
rg "class.*Adapter" codeframe/core/adapters --type py -A 30 | grep -E "(class|def stream_events)" | head -40

Repository: frankbria/codeframe

Length of output: 816


🏁 Script executed:

# Look at how stream_events is actually called in the codebase
rg "\.stream_events\(\)" --type py -B 3 -A 3

Repository: frankbria/codeframe

Length of output: 546


🏁 Script executed:

# Check the adapters subdirectory structure
ls -la codeframe/core/adapters/

Repository: frankbria/codeframe

Length of output: 630


🏁 Script executed:

# Check if there's any design documentation or objectives about streaming being optional
fd -e md -e txt | xargs rg -l "streaming.*optional|optional.*stream" 2>/dev/null

Repository: frankbria/codeframe

Length of output: 72


🏁 Script executed:

# Check if there are any comments in the adapters about streaming being optional
rg "optional|streaming" codeframe/core/adapters --type py -B 2 -A 2 | head -50

Repository: frankbria/codeframe

Length of output: 1364


🏁 Script executed:

# Read the README to check for design objectives about optional streaming
cat README.md | head -100

Repository: frankbria/codeframe

Length of output: 3939


🏁 Script executed:

# Also check if there's a CLAUDE.md or design doc that mentions streaming
fd -e md | xargs grep -l "objective\|design\|streaming" 2>/dev/null | head -10

Repository: frankbria/codeframe

Length of output: 385


🏁 Script executed:

# Check the actual implementation of stream_events across existing adapters
for file in codeframe/core/adapters/*.py; do echo "=== $file ==="; grep -A 5 "def stream_events" "$file" 2>/dev/null || echo "No stream_events found"; done

Repository: frankbria/codeframe

Length of output: 560


🏁 Script executed:

# Search for stream_events implementations more broadly
rg "def stream_events" --type py -B 3 -A 5

Repository: frankbria/codeframe

Length of output: 1106


🏁 Script executed:

# Check the builtin adapter more carefully for stream_events
cat -n codeframe/core/adapters/builtin.py | head -150

Repository: frankbria/codeframe

Length of output: 6191


🏁 Script executed:

# Check the subprocess adapter which is the base class
cat -n codeframe/core/adapters/subprocess_adapter.py | head -100

Repository: frankbria/codeframe

Length of output: 4019


🏁 Script executed:

# Check if BuiltinReactAdapter actually implements stream_events anywhere
cat -n codeframe/core/adapters/builtin.py | tail -100

Repository: frankbria/codeframe

Length of output: 4288


🏁 Script executed:

# Check if these adapters implement requires_api_key property
rg "requires_api_key" --type py -B 2 -A 3

Repository: frankbria/codeframe

Length of output: 3102


🏁 Script executed:

# Look at a test that checks isinstance for these actual adapters
cat -n tests/core/adapters/test_builtin.py | head -80

Repository: frankbria/codeframe

Length of output: 3549


🏁 Script executed:

# Check if BuiltinReactAdapter implements execute method (not just run)
rg "def execute" codeframe/core/adapters/builtin.py

Repository: frankbria/codeframe

Length of output: 45


🏁 Script executed:

# Check if there are any methods we're missing in the builtin adapters
cat -n codeframe/core/adapters/builtin.py | grep -E "def |@property"

Repository: frankbria/codeframe

Length of output: 520


🏁 Script executed:

# Run the actual test to see if it passes
cd codeframe && python -m pytest tests/core/adapters/test_builtin.py::TestBuiltinReactAdapter::test_conforms_to_protocol -v 2>&1 || echo "Test failed or pytest not available"

Repository: frankbria/codeframe

Length of output: 1035


🏁 Script executed:

# Find the actual test file location
find . -name "*test_builtin.py" -type f

Repository: frankbria/codeframe

Length of output: 100


🏁 Script executed:

# Check if there's an execute method defined somewhere else or if run is being aliased
rg "def execute|execute.*=" codeframe/core/adapters/ --type py

Repository: frankbria/codeframe

Length of output: 45


🏁 Script executed:

# Look for where AgentAdapter methods are actually being used in the tests
cat -n tests/core/adapters/test_builtin.py | grep -A 5 "conforms_to_protocol"

Repository: frankbria/codeframe

Length of output: 705


🏁 Script executed:

# Check if BuiltinReactAdapter has stream_events and requires_api_key somewhere
grep -n "stream_events\|requires_api_key\|execute" codeframe/core/adapters/builtin.py

Repository: frankbria/codeframe

Length of output: 45


🏁 Script executed:

# Run the actual test to see what happens
python -m pytest ./tests/core/adapters/test_builtin.py::TestBuiltinReactAdapter::test_conforms_to_protocol -xvs 2>&1 | head -50

Repository: frankbria/codeframe

Length of output: 2625


🏁 Script executed:

# Check if maybe there's a base class or mixin being used
head -20 codeframe/core/adapters/builtin.py

Repository: frankbria/codeframe

Length of output: 761


🏁 Script executed:

# Check if BuiltinReactAdapter is missing execute, stream_events, requires_api_key
python3 << 'EOF'
from typing import Protocol, runtime_checkable, Iterator

`@runtime_checkable`
class AgentAdapter(Protocol):
    def execute(self): ...
    def stream_events(self) -> Iterator: ...
    `@property`
    def name(self) -> str: ...
    `@property`
    def requires_api_key(self) -> dict: ...

class BuiltinReactAdapterMock:
    `@property`
    def name(self) -> str:
        return "react"
    def run(self): 
        pass
    # Missing: execute, stream_events, requires_api_key

adapter = BuiltinReactAdapterMock()
print("BuiltinReactAdapter conforms to AgentAdapter:", isinstance(adapter, AgentAdapter))
print("\nMissing members:")
print("- execute:", not hasattr(adapter, 'execute'))
print("- stream_events:", not hasattr(adapter, 'stream_events'))
print("- requires_api_key:", not hasattr(adapter, 'requires_api_key'))
EOF

Repository: frankbria/codeframe

Length of output: 195


🏁 Script executed:

# Let's check what the actual protocol definition looks like and what adapters implement
python3 << 'EOF'
import sys
sys.path.insert(0, '/home/jailuser/git')

# Check what methods BuiltinReactAdapter actually has
from codeframe.core.adapters.builtin import BuiltinReactAdapter

print("BuiltinReactAdapter methods and properties:")
for attr in dir(BuiltinReactAdapter):
    if not attr.startswith('_'):
        print(f"  {attr}")

# Check specifically for protocol required members
required = ['execute', 'stream_events', 'name', 'requires_api_key']
actual = set(dir(BuiltinReactAdapter))
for member in required:
    status = "✓" if member in actual else "✗"
    print(f"{status} {member}")
EOF

Repository: frankbria/codeframe

Length of output: 801


🏁 Script executed:

# Check if there's any dynamic method assignment or aliasing happening
rg "execute.*=|setattr.*execute" codeframe/core/adapters/ --type py

Repository: frankbria/codeframe

Length of output: 45


🏁 Script executed:

# Check if the adapters actually define execute or if they're supposed to alias run
cat -n codeframe/core/adapters/subprocess_adapter.py | grep -A 20 "def run"

Repository: frankbria/codeframe

Length of output: 901


🏁 Script executed:

# Let's check what the protocol actually expects vs what's being used
rg "\.execute\(" codeframe/core --type py -B 2 -A 2 | head -40

Repository: frankbria/codeframe

Length of output: 1785


🏁 Script executed:

# Look at the complete protocol definition to see if there's something with aliases or __getattr__
cat -n codeframe/core/agent_adapter.py

Repository: frankbria/codeframe

Length of output: 3469


🏁 Script executed:

# Check if there are two different AgentAdapter definitions or if one is importing from another
find codeframe -name "*.py" -type f -exec grep -l "class AgentAdapter" {} \;

Repository: frankbria/codeframe

Length of output: 135


🏁 Script executed:

# Look at what the comment about the "linked objective" might refer to - check for design docs
cat CLAUDE.md 2>/dev/null | grep -i "stream\|optional" | head -20

Repository: frankbria/codeframe

Length of output: 1293


The AgentAdapter protocol requires methods that no adapter implementations actually provide.

The protocol at lines 82–100 mandates execute(), stream_events(), name, and requires_api_key. However, all actual adapters (BuiltinReactAdapter, BuiltinPlanAdapter, SubprocessAdapter) implement run() instead of execute(), and are missing stream_events() and requires_api_key entirely. This means isinstance(adapter, AgentAdapter) would return False for every adapter in the codebase, despite test claims of protocol conformance. The protocol definition needs to match actual implementations—either rename execute() to run(), make stream_events() optional by splitting it into a separate protocol, or update adapters to provide the required surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/agent_adapter.py` around lines 82 - 94, The AgentAdapter
protocol doesn't match implementations: change the protocol to declare run(...)
instead of execute(...), remove stream_events() and requires_api_key from the
core AgentAdapter, and create two small optional protocols—StreamableAgent (with
stream_events() -> Iterator[AgentEvent]) and ApiKeyAgent (with requires_api_key:
bool or def requires_api_key() -> bool)—so BuiltinReactAdapter,
BuiltinPlanAdapter, and SubprocessAdapter will be recognized as implementing
AgentAdapter (and can opt into StreamableAgent/ApiKeyAgent if they support those
features); update any type checks or isinstance usages to reference the new
optional protocols where streaming or API-key behavior is required.

Comment thread tests/core/test_agent_adapter.py
@claude

claude Bot commented Mar 9, 2026

Copy link
Copy Markdown

Code Review: Agent Adapter Protocol Definition

This is a well-scoped and well-executed implementation. The protocol layer is clean and minimal, exactly what Phase 4 needs as a foundation.

Strengths

  • Core-headless compliance: No FastAPI, HTTP, or UI imports, fully aligned with architecture rules.
  • AdapterTokenUsage rationale: Decoupling from the heavy TokenUsage Pydantic model is the right call. Adapter implementors should not need to know about CodeFrame billing internals.
  • default_factory usage: Lists and dicts all use field(default_factory=...) correctly, avoiding shared mutable state. The test test_list_defaults_are_independent validates this explicitly.
  • @runtime_checkable: Enabling isinstance() checks at adapter registration time is the right ergonomic choice.
  • Test coverage: 23 tests covering construction, edge cases, protocol compliance, and streaming. The _make_compliant_class helper keeps protocol tests DRY.

Issues and Suggestions

  1. AgentEvent.metadata uses untyped dict — should be dict[str, Any] for clarity. Without it, type checkers allow arbitrary value types silently.

  2. stream_events() has no cancellation story — The protocol returns a plain Iterator[AgentEvent], fine for now. But for long-running external agents (Claude Code, Codex), callers will need a way to cancel a running stream. Consider whether stream_events() should accept a stop_event parameter, or whether the adapter registration layer (issue 410) will handle cancellation externally. Document the design intent either way.

  3. AgentContext.verification_gates contract is undocumented — The field accepts strings like 'ruff' and 'pytest' in tests, but the actual gate system (core/gates.py) uses a richer structure. Future adapter implementors will not know what strings are valid. A docstring comment on the field would prevent confusion.

  4. AgentResult.duration_ms defaults to 0 which is ambiguous — 0 is both a valid duration and an unreported sentinel. Consider using int | None = None so callers can distinguish 'ran in 0ms' from 'duration not recorded.'

  5. AgentContext has no workspace_path — execute() accepts workspace_path: Path as a direct argument, but AgentContext does not include it. Adapters that need workspace info for file discovery will get it from two different channels. Worth a design note on the intended split.

  6. Test uses Path('/tmp') which is not platform-neutral — In test_execute_returns_agent_result, Path('/tmp') does not exist on Windows and is fragile. Use Path('.') or pytest's tmp_path fixture instead.

Overall

Solid foundation for the adapter architecture. Items 1, 4, and 6 are easy fixes. Items 2, 3, and 5 are design questions that can land in follow-up issues (410-417). Ready to approve once item 6 is addressed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/core/test_agent_adapter.py`:
- Around line 257-265: The test currently treats stream_events as a required
attribute but streaming is optional; modify test_partial_implementation_fails so
it verifies failure only when truly required members are missing: keep execute
but remove stream_events from the “missing” list and instead omit a genuinely
required attribute such as name or requires_api_key (or both) on PartialAdapter,
and assert that an instance of that incomplete PartialAdapter is not an
AgentAdapter; reference test_partial_implementation_fails, AgentAdapter,
PartialAdapter, stream_events, name, and requires_api_key when making the
change.
- Around line 243-284: Add a real compatibility test that exercises the existing
ReactAgent adaptation path: import ReactAgent (the concrete agent
implementation), instantiate it, and assert it satisfies the AgentAdapter
protocol just like FakeAdapter does—i.e., assert isinstance(ReactAgent(),
AgentAdapter) and also call execute(...) and stream_events() on the ReactAgent
instance and assert the returned types are AgentResult and AgentEvent
respectively (matching the existing tests
test_compliant_class_satisfies_protocol, test_execute_returns_agent_result, and
test_stream_events_yields_agent_events).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 208a8b1a-0add-4878-a3ee-9178052a6733

📥 Commits

Reviewing files that changed from the base of the PR and between 492e701 and 569c19c.

📒 Files selected for processing (1)
  • tests/core/test_agent_adapter.py

Comment on lines +243 to +284
def test_compliant_class_satisfies_protocol(self):
from codeframe.core.agent_adapter import AgentAdapter
FakeAdapter = self._make_compliant_class()
adapter = FakeAdapter()
assert isinstance(adapter, AgentAdapter)

def test_non_compliant_class_fails(self):
from codeframe.core.agent_adapter import AgentAdapter

class NotAnAdapter:
pass

assert not isinstance(NotAnAdapter(), AgentAdapter)

def test_partial_implementation_fails(self):
from codeframe.core.agent_adapter import AgentAdapter

class PartialAdapter:
def execute(self, task_prompt, workspace_path, context, timeout_ms=0):
pass
# Missing: stream_events, name, requires_api_key

assert not isinstance(PartialAdapter(), AgentAdapter)

def test_execute_returns_agent_result(self):
from codeframe.core.agent_adapter import (
AgentContext, AgentResult, AgentResultStatus,
)
FakeAdapter = self._make_compliant_class()
adapter = FakeAdapter()
ctx = AgentContext(task_id="1", task_title="Test", task_description="Test")
result = adapter.execute("do something", Path("/tmp"), ctx)
assert isinstance(result, AgentResult)
assert result.status == AgentResultStatus.COMPLETED

def test_stream_events_yields_agent_events(self):
from codeframe.core.agent_adapter import AgentEvent
FakeAdapter = self._make_compliant_class()
adapter = FakeAdapter()
events_list = list(adapter.stream_events())
assert len(events_list) == 1
assert isinstance(events_list[0], AgentEvent)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add at least one compatibility test against the real ReactAgent adaptation path.

These assertions only prove the protocol against FakeAdapter. The acceptance criteria for this PR explicitly call out ensuring the existing ReactAgent can be adapted, and that can still regress silently with the current suite.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/test_agent_adapter.py` around lines 243 - 284, Add a real
compatibility test that exercises the existing ReactAgent adaptation path:
import ReactAgent (the concrete agent implementation), instantiate it, and
assert it satisfies the AgentAdapter protocol just like FakeAdapter does—i.e.,
assert isinstance(ReactAgent(), AgentAdapter) and also call execute(...) and
stream_events() on the ReactAgent instance and assert the returned types are
AgentResult and AgentEvent respectively (matching the existing tests
test_compliant_class_satisfies_protocol, test_execute_returns_agent_result, and
test_stream_events_yields_agent_events).

Comment on lines +257 to +265
def test_partial_implementation_fails(self):
from codeframe.core.agent_adapter import AgentAdapter

class PartialAdapter:
def execute(self, task_prompt, workspace_path, context, timeout_ms=0):
pass
# Missing: stream_events, name, requires_api_key

assert not isinstance(PartialAdapter(), AgentAdapter)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This test hard-codes stream_events as mandatory.

Issue #409 describes streaming support as optional, but this negative case rejects adapters that omit stream_events. That bakes the wrong contract into the suite and will fail valid non-streaming adapters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/test_agent_adapter.py` around lines 257 - 265, The test currently
treats stream_events as a required attribute but streaming is optional; modify
test_partial_implementation_fails so it verifies failure only when truly
required members are missing: keep execute but remove stream_events from the
“missing” list and instead omit a genuinely required attribute such as name or
requires_api_key (or both) on PartialAdapter, and assert that an instance of
that incomplete PartialAdapter is not an AgentAdapter; reference
test_partial_implementation_fails, AgentAdapter, PartialAdapter, stream_events,
name, and requires_api_key when making the change.

@frankbria
frankbria merged commit 623cfbd into main Mar 9, 2026
15 checks passed
@frankbria
frankbria deleted the feature/issue-409-agent-adapter-protocol branch March 24, 2026 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Phase 4] Agent Adapter Protocol Definition

1 participant