Skip to content

refactor: extract LLM and loop runtime contracts - #1

Merged
zhanghanduo merged 3 commits into
mainfrom
refactor/runtime-contracts
Aug 31, 2026
Merged

zhanghanduo merged 3 commits into
mainfrom
refactor/runtime-contracts

Conversation

@zhanghanduo

Copy link
Copy Markdown
Collaborator

Summary

  • add provider-neutral LLMClient, LLMResponse, and StreamDelta contracts
  • extract loop configuration, lifecycle contexts, observer protocol, intervention merging, and dispatch helpers
  • keep execution-context storage outside AgentCore through a metadata-only deadline helper
  • add standalone LLM/loop contract tests and update the migration README

Validation

  • Ruff format and lint
  • strict Pyright: 0 errors
  • 63 tests passed
  • sdist and wheel build passed

This is the next slice after the foundation package consumed by ApodexHarness #502 and FrontierAgentInternal #93.

zhanghanduo and others added 2 commits August 31, 2026 13:51
`deadline_remaining_s` accepts either a renewable-lease view exposing
`remaining_s()` or a bare monotonic instant, and the probe is deliberately
structural: importing the concrete lease type to run an `isinstance` check
would pull a product dependency into this frozen contract module and break
the `agent_core` import closure. The port compressed that reasoning away,
leaving the next reader with an apparent cleanup opportunity that is
actually a constraint. Restore it.

Also export the three dispatch helpers a host must call — `notify_tool_call`,
`notify_tool_result`, `drain_background_observers` — which `__all__` omitted
while listing `notify_observers` and `merge_interventions`, and repair the
README scope list punctuation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zhanghanduo

Copy link
Copy Markdown
Collaborator Author

Review notes — I checked all six review findings against ApodexHarness/miroharness/core/ before acting on any of them.

Five are pre-existing defects ported verbatim, not caused by this refactor, so they are tracked separately rather than blocking this PR (which is an equivalence-preserving move; mixing behavioural fixes in would make the diff unreviewable):

#2 and #5 are live bugs in the products, not contract tidiness.

A sixth finding claimed agent_core/__init__.py contradicts the README by not re-exporting the loop types. That one is a false positive: tokens.py and runtime/loop/compact.py are in the README scope list and are likewise not top-level exports, and the README never promises re-export. Scope list ≠ export surface is the existing convention here.

Addressed in this PR (8462a85 → latest), both items genuinely introduced by the port:

  1. deadline_remaining_s lost the rationale for its structural remaining_s() probe. The original spelled out that importing the concrete renewable-lease type to run an isinstance check would pull a product dependency into this frozen module and break the agent_core import closure. Without that note the probe reads like an obvious cleanup opportunity when it is actually a constraint — restored, with an explicit "do not clean this up" line.
  2. __all__ listed notify_observers and merge_interventions but omitted the other three dispatch helpers a host must call (notify_tool_call, notify_tool_result, drain_background_observers). The upstream __all__ has the same omission, but since this file is new here and the fix is three lines, it is folded in rather than deferred.

Plus a README list-punctuation repair (the new bullets were appended after a list item ending in a period).

Re-verified after the change: ruff format clean, ruff check clean, pyright strict 0 errors, 63 tests passed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR extracts and formalizes provider-neutral contracts for LLM clients/responses and agent-loop runtime/observer types, along with standalone tests and README updates to support ongoing migration into agent_core.

Changes:

  • Added provider-agnostic LLMClient, LLMResponse, and StreamDelta contracts.
  • Added loop runtime contract types plus observer dispatch/merge helpers (LoopConfig, contexts, interventions, observer protocol).
  • Added contract-focused tests for LLM and loop types; updated README migration notes.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
agent_core/llm.py Introduces provider-neutral LLM protocol + response/stream delta containers.
agent_core/loop_types.py Adds loop contract datatypes and observer dispatch + intervention merge helpers.
agent_core/__init__.py Re-exports new LLM contract types at package top-level.
tests/test_llm.py Adds runtime-protocol and mutable-default regression tests for LLM contracts.
tests/test_loop_types.py Adds tests for intervention merging, observer dispatch, tool hook semantics, and deadline helper.
README.md Updates migration boundary documentation to reflect newly shared contracts.
Suppressed comments (1)

agent_core/loop_types.py:492

  • merge_interventions currently treats inject_messages=[] the same as None (the truthiness check skips the empty list and the return converts an empty all_messages back to None). That contradicts the contract note that None and [] must not be conflated; if any observer explicitly requests an empty list, the merged intervention should preserve [].
    for iv in interventions:
        if iv.inject_messages:
            all_messages.extend(iv.inject_messages)
        if stop_reason is None and iv.stop_reason is not None:

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread agent_core/loop_types.py Outdated
Comment on lines +51 to +53
if not isinstance(deadline, (int, float)):
return None
return float(deadline) - time.monotonic()
Comment thread agent_core/llm.py
Comment on lines +84 to +89
"""Stream a completion as a sequence of incremental ``StreamDelta``s.

The terminal ``LLMResponse`` (with assembled content + finalised
tool_calls + usage) is accessible via :meth:`last_response` after the
stream is exhausted.
"""
Comment thread tests/test_loop_types.py
Comment on lines +47 to +50
def test_no_inject_messages_stays_none_not_empty_list():
"""``None`` and ``[]`` must not be conflated: the loop treats a list as
'inject this', so an empty list would be a request to inject nothing."""
assert merge_interventions([Intervention(), Intervention()]).inject_messages is None
Scope passive observer task draining per agent loop and flush cancellation hooks. Preserve explicit empty injections, ignore empty stop reasons, reject boolean deadlines, formalize optional observer protocols, and correct the streaming contract documentation.\n\nRefs #2, #3, #4, #5, #6.
@zhanghanduo
zhanghanduo merged commit 0948394 into main Aug 31, 2026
1 check passed
@zhanghanduo
zhanghanduo deleted the refactor/runtime-contracts branch August 31, 2026 07:12
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.

2 participants