Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
00b0c90
docs: add lifecycle analysis survey and RFC-0042 unified lifecycle ar…
Leoyzen Jul 8, 2026
c4fc100
docs(rfc-0042): R4.4 fixes — glossary dedup, RunState enum, ProtocolB…
Leoyzen Jul 8, 2026
003f1b5
docs(rfc-0042): update revision metadata to 4.4 (Oracle VERIFIED)
Leoyzen Jul 8, 2026
8589cc0
docs(rfc-0042): Appendix D fixes — 13 Oracle issues (4 CRITICAL, 5 MA…
Leoyzen Jul 8, 2026
1eb7450
docs(rfc-0050): add AgentWolf v1 foundation architecture RFC
Leoyzen Jul 9, 2026
0830352
docs(openspec): add M1 foundation restructure change — HostContext, A…
Leoyzen Jul 9, 2026
2cc2fce
docs(openspec): add M2 new lifecycle change — RunLoop + six pluggable…
Leoyzen Jul 9, 2026
925e624
docs(openspec): add M3 capability native change — pydantic-ai Capabil…
Leoyzen Jul 9, 2026
f66a7b4
docs(openspec): add M4 multi-config change — ConfigRegistry, HostRegi…
Leoyzen Jul 9, 2026
dbce81f
docs(openspec): add M5 multi-tenant change — per-tenant Host isolation
Leoyzen Jul 9, 2026
dafc85d
docs(openspec): add M6 polyglot change — EventEnvelope, gRPC, Message…
Leoyzen Jul 9, 2026
10586f4
feat: M1 Foundation Restructure — HostContext, AgentFactory, AgentReg…
Leoyzen Jul 9, 2026
30b4978
feat(lifecycle): add types, protocols, EventEnvelope, StateUpdate event
Leoyzen Jul 9, 2026
f6ba92d
feat(lifecycle): add TriggerSource, Journal, SnapshotStore, EventTran…
Leoyzen Jul 9, 2026
03f3317
feat(lifecycle): add CommChannel dimension with DirectChannel and Pro…
Leoyzen Jul 9, 2026
888354e
feat(lifecycle): add RunLoop core with dimension injection, state mac…
Leoyzen Jul 9, 2026
0e5cb47
feat(lifecycle): add RunLoop steer, followup, close with CommChannel …
Leoyzen Jul 9, 2026
340f2f4
feat(lifecycle): add LifecycleConfig, dimension factory, and agent in…
Leoyzen Jul 9, 2026
1fecdff
feat(lifecycle): add crash recovery with mark_interrupted/retry strat…
Leoyzen Jul 9, 2026
fef69a5
refactor(orchestrator): migrate SessionController and protocol server…
Leoyzen Jul 9, 2026
64c1307
refactor(messaging): deprecate agent_pool backdoor, migrate call site…
Leoyzen Jul 9, 2026
790a3f0
style(lifecycle): fix ruff format in journal.py and protocols.py
Leoyzen Jul 9, 2026
2b9a62e
docs: update AGENTS.md for M2 lifecycle dimensions architecture
Leoyzen Jul 9, 2026
7213c7e
docs(openspec): mark all M2 tasks as complete
Leoyzen Jul 9, 2026
008995f
fix: add get_session_context() to MCPManager, fix async mocks in test…
Leoyzen Jul 9, 2026
47030c7
chore: remove test DB artifacts
Leoyzen Jul 9, 2026
e9a5d72
fix(lifecycle): address all code review feedback from PR #140
Leoyzen Jul 9, 2026
a1d7fe5
style: fix ruff format in run.py
Leoyzen Jul 9, 2026
60f0323
fix(lifecycle): address second round of code review feedback
Leoyzen Jul 9, 2026
9bb84e9
refactor(lifecycle): use single shared lifecycle.db with session_id i…
Leoyzen Jul 9, 2026
2818a31
refactor(lifecycle): share SQLAlchemy engine across DurableJournal in…
Leoyzen Jul 9, 2026
15179ea
feat(capabilities): M3 native capability system rebased onto M2 lifec…
Leoyzen Jul 9, 2026
eef1989
feat(capabilities): complete deferred M3 tasks (15, 16.6, 16.8)
Leoyzen Jul 9, 2026
7577786
fix(capabilities): complete M3 cleanup — fix old ResourceProvider tes…
Leoyzen Jul 9, 2026
1c2ffdb
fix: add missing runtime imports for capability classes (F821 fixes)
Leoyzen Jul 9, 2026
ac6125e
fix: rewrite broken tests for capability-native API
Leoyzen Jul 9, 2026
fdbc291
fix: fix 6 remaining toolset test failures
Leoyzen Jul 9, 2026
ee74752
fix: update filtered_toolset tests for name delegation change
Leoyzen Jul 9, 2026
83af961
refactor: remove ToolManager, fix all mypy/lint/import-linter errors
Leoyzen Jul 10, 2026
2e220f1
fix: rewrite remaining tests for post-ToolManager API
Leoyzen Jul 10, 2026
2dca6b3
fix: complete ToolManager removal — fix all remaining agent.tools ref…
Leoyzen Jul 10, 2026
d8f61ca
fix: fix remaining 10 test failures + uri_resolver SkillProvider guard
Leoyzen Jul 10, 2026
9eed618
fix: fix lifecycle config frozen test + ruff TRY004 fix
Leoyzen Jul 10, 2026
7601d80
fix: remove remaining agent.tools references in test files
Leoyzen Jul 10, 2026
b6cc897
fix(capabilities): use ctx.deps.host.internal_fs for code mode script…
Leoyzen Jul 10, 2026
01a4db3
fix: fix mypy error and WorkersTools duplicate tool registration
Leoyzen Jul 10, 2026
9ac4389
refactor: delete tools/factory.py dead code, add Resource.source fiel…
Leoyzen Jul 10, 2026
577fad1
refactor(m3.5): Complete agent_pool backdoor cleanup (#142)
Leoyzen Jul 10, 2026
7d7cf95
feat: implement extension-source-architecture (#143)
Leoyzen Jul 12, 2026
138f092
feat(openspec): add m3-5-backdoor-cleanup change
Leoyzen Jul 10, 2026
3043838
refactor(openspec): remove SkillService from cleanup, defer to separa…
Leoyzen Jul 10, 2026
634be7c
fix(capabilities): convert MCP FunctionTool to pydantic-ai Tool befor…
Leoyzen Jul 12, 2026
b686ede
fix(capabilities,lifecycle): address PR #144 review comments
Leoyzen Jul 12, 2026
646a7f1
Merge develop/agentic into refactor/agentwolf_v1
Leoyzen Jul 12, 2026
d3802aa
fixup! Merge develop/agentic into refactor/agentwolf_v1
Leoyzen Jul 12, 2026
6f07fd3
feat(skills): restore skill system capabilities after M3 refactor
Leoyzen Jul 12, 2026
63a2c42
docs(openspec): add restore-skill-capabilities change with specs and RFC
Leoyzen Jul 12, 2026
4a3cdf5
test(skills): add 30 tests for skill tool registration and e2e scenarios
Leoyzen Jul 12, 2026
8863e3d
fix(skills): address code review feedback from PR #145
Leoyzen Jul 12, 2026
f61a66f
fix(skills): remove dead skill_commands references from server code a…
Leoyzen Jul 12, 2026
ad93d44
fix(skills): resolve CI lint, mypy, and test failures
Leoyzen Jul 12, 2026
982bc33
fix(skills): address CI test failure and gemini code review feedback
Leoyzen Jul 12, 2026
8ff8b89
refactor(skills): remove provider field from ResolvedSkillURI (D9 fla…
Leoyzen Jul 12, 2026
87b283f
fix(docs): fix load_skill docstring formatting
Leoyzen Jul 12, 2026
661456c
fix(resolver): fall back to provider search when ExtensionRegistry fails
Leoyzen Jul 12, 2026
6c73118
fix(registry): use ResolvedSkillURI.parse() in ExtensionRegistry.reso…
Leoyzen Jul 12, 2026
23c530f
feat(registry): propagate real skill_path through ExtensionRegistry t…
Leoyzen Jul 12, 2026
3016311
chore(archive): commit archived restore-skill-capabilities spec artif…
Leoyzen Jul 12, 2026
9aa6c1c
fix(tests): update tests for Skill return type from resolve_uri() and…
Leoyzen Jul 12, 2026
6553302
fix(server): initialize command_store from pool.skills for OpenCode s…
Leoyzen Jul 12, 2026
a61b004
feat(server): subscribe to ChangeEvent stream for dynamic CommandStor…
Leoyzen Jul 12, 2026
6e0c19d
fix(lint): remove duplicate from __future__ import annotations
Leoyzen Jul 12, 2026
e0437d6
fix(tests): update security and scratchpad tests for D9 flat URI + Sk…
Leoyzen Jul 12, 2026
712f4ba
fix: address gemini code review feedback (7 comments)
Leoyzen Jul 12, 2026
87a9346
fixup! fix: address gemini code review feedback (7 comments)
Leoyzen Jul 12, 2026
9f16c2c
chore(openspec): archive 13 stale/completed changes
Leoyzen Jul 12, 2026
f34d351
docs(openspec): add pre-m4-protocol-cleanup change + RFC-0053
Leoyzen Jul 13, 2026
9fdb5ff
refactor(acp): replace hasattr patterns with typed interfaces
Leoyzen Jul 13, 2026
6b8d6b2
refactor(lifecycle): add set_replaying() to CommChannel protocol
Leoyzen Jul 13, 2026
4986e32
refactor(lifecycle): replace isinstance check with publishes_to_event…
Leoyzen Jul 13, 2026
0fb9a62
refactor(orchestrator): hold direct dimension refs, remove type: igno…
Leoyzen Jul 13, 2026
e4dd21d
fixup! refactor(orchestrator): hold direct dimension refs, remove typ…
Leoyzen Jul 13, 2026
602b4ad
Revert "refactor(acp): replace hasattr patterns with typed interfaces"
Leoyzen Jul 13, 2026
ccca963
Revert "refactor(lifecycle): add set_replaying() to CommChannel proto…
Leoyzen Jul 13, 2026
69d5e62
Revert "refactor(lifecycle): replace isinstance check with publishes_…
Leoyzen Jul 13, 2026
33ce6d4
Revert "refactor(orchestrator): hold direct dimension refs, remove ty…
Leoyzen Jul 13, 2026
b31e89e
Revert "fixup! refactor(orchestrator): hold direct dimension refs, re…
Leoyzen Jul 13, 2026
814398c
refactor: pre-M4 protocol cleanup — ACP path unification, legacy remo…
Leoyzen Jul 13, 2026
7797e98
chore: remove dot omo directory.
Leoyzen Jul 13, 2026
3c68bb4
fix(skills): remove duplicate MCP tool registration from SkillManagerCap
Leoyzen Jul 13, 2026
c269d12
fix(elicitation): timeout default null, checkpoint save upsert, crash…
Leoyzen Jul 13, 2026
15b29ed
fix(elicitation): RunErrorEvent on timeout + event consumer restart o…
Leoyzen Jul 13, 2026
26f02ee
fix(elicitation): route resume through pool lifecycle, not standalone
Leoyzen Jul 14, 2026
254f22a
fix(elicitation): preserve conversation history on cancel via StreamC…
Leoyzen Jul 14, 2026
5b94cf9
fix: ruff lint and format fixes for elicitation cancel changes
Leoyzen Jul 14, 2026
bd30cca
fix: resolve all pre-existing CI lint, format, and type errors
Leoyzen Jul 14, 2026
633dd0c
fix: remove unused type: ignore on combined_toolset.py line 183
Leoyzen Jul 14, 2026
19dd406
fix: handle MessageHistory objects in _create_run_handle
Leoyzen Jul 14, 2026
53695e3
fix: eliminate phantom 'acp-default' session from ACP server startup
Leoyzen Jul 14, 2026
e4b943a
fix: reset session status from 'closed' to 'active' on ACP resume
Leoyzen Jul 14, 2026
2a92159
fix: preserve history when agentlet.iter() exit fails after turn comp…
Leoyzen Jul 14, 2026
2ba00e9
feat: remove llmling-models & upgrade pydantic-ai to v2.9.0 (#153)
Million-mo Jul 14, 2026
def2a99
docs: sync v2-message-id-infrastructure OpenSpec with RFC-0054 Phase 4
Leoyzen Jul 15, 2026
cdcb407
Revert "docs: sync v2-message-id-infrastructure OpenSpec with RFC-005…
Leoyzen Jul 15, 2026
5e3330c
feat: v2-message-id-infrastructure (#157)
Leoyzen Jul 15, 2026
b361237
docs: add Python 3.12+ best practices and telemetry/span instrumentat…
Leoyzen Jul 15, 2026
e0451ba
fix: add span instrumentation for critical-path methods (fix-span-ins…
Leoyzen Jul 16, 2026
dd43821
fix: update integration tests for deps param and EventBus subscription
Leoyzen Jul 16, 2026
ca7ad1a
fix: pass deps=None in SessionPool.send_message() (#166)
Leoyzen Jul 16, 2026
c038621
Revert "fix: add span instrumentation for critical-path methods (fix-…
Leoyzen Jul 16, 2026
eeb4f94
fix: wire ACPSkillBridge to expose skills as ACP slash commands (#164)
Leoyzen Jul 16, 2026
ef35691
chore: add openspec to gitignore
Leoyzen Jul 16, 2026
e070bb7
feat: span instrumentation for critical-path methods (#167)
Leoyzen Jul 16, 2026
a56b104
refactor: session debt cleanup — state machine, storage ISP, creation…
Leoyzen Jul 17, 2026
454232c
fix: preserve ThinkingPart in OpenCode session restore and OpenAI API…
Leoyzen Jul 17, 2026
ac8de78
fix: persist MCP connections across turns by eager-entering MCPToolse…
Leoyzen Jul 17, 2026
94dc4b1
refactor: remove SessionStore, migrate to StorageProvider (#180)
Leoyzen Jul 17, 2026
b8fded3
fix: restore persisted sessions from store in list_sessions (#181)
Leoyzen Jul 17, 2026
7012d0e
fix: get_or_load_session must check SessionController registration
Leoyzen Jul 17, 2026
e97c91a
fixup! refactor: session debt cleanup — state machine, storage ISP, c…
Leoyzen Jul 17, 2026
2cbb75b
fix(storage): increase title generation retries and reduce log noise
Leoyzen Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ jobs:
- name: Install dependencies
run: uv sync --all-extras --no-group lint
- name: Run core tests
run: uv run pytest -m "not unit and not integration and not slow and not acp_snapshot" --tb=short --no-cov --timeout=180 -q
run: uv run pytest -m "not unit and not integration and not slow and not acp_snapshot" -n auto --tb=short --no-cov --timeout=180 -q
- uses: actions/upload-artifact@v4
if: failure()
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,6 @@ test_configs/

# Git worktrees
.worktrees/

# openspec
**/openspec/
407 changes: 0 additions & 407 deletions .omo/plans/fix-mcp-session-lifecycle.md

This file was deleted.

376 changes: 332 additions & 44 deletions AGENTS.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion diagnostic_pool_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ async def main():
print("Pool entered successfully")
print(f" skill_resolver: {pool.skill_resolver is not None}")
print(f" skill_provider: {pool.skill_provider is not None}")
print(f" skill_commands: {pool.skill_commands is not None}")
print()

if pool.skill_resolver:
Expand Down
536 changes: 536 additions & 0 deletions docs/design/lifecycle-analysis.md

Large diffs are not rendered by default.

3,671 changes: 3,671 additions & 0 deletions docs/rfcs/draft/RFC-0042-unified-lifecycle-architecture.md

Large diffs are not rendered by default.

2,484 changes: 2,484 additions & 0 deletions docs/rfcs/draft/RFC-0050-agentwolf-v1-foundation-architecture.md

Large diffs are not rendered by default.

1,939 changes: 1,939 additions & 0 deletions docs/rfcs/draft/RFC-0051-extension-source-architecture.md

Large diffs are not rendered by default.

380 changes: 380 additions & 0 deletions docs/rfcs/draft/RFC-0052-restore-skill-capabilities.md

Large diffs are not rendered by default.

910 changes: 910 additions & 0 deletions docs/rfcs/draft/RFC-0053-pre-m4-protocol-debt-cleanup.md

Large diffs are not rendered by default.

867 changes: 867 additions & 0 deletions docs/rfcs/implemented/RFC-0054-v2-message-id-infrastructure.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
# Agent Pool Backdoor Cleanup — Pre-M4 Baseline

## Problem

`MessageNode.agent_pool` is a direct reference to the full `AgentPool` god-class on every agent. RFC #135 identifies this as the **#1 architectural risk**: it spans Layers 2–5, blocks tenant isolation, makes layer boundaries permeable, and prevents clean dependency injection.

M1 introduced `HostContext` as the replacement — a frozen dataclass carrying only infrastructure service references. M2 Task 11 was supposed to migrate all call sites but was marked `[x]` while only completing Phase 1 (core agents). Protocol servers (ACP, OpenCode) were never migrated. Additionally, `HostContext.pool: AgentPool | None` was added as a back-reference, re-creating the exact backdoor the architecture was designed to eliminate.

### Current State

- **~64 external `.agent_pool` property accesses** across 15 files (AGENTS.md's "18 references" is stale)
- **`HostContext.pool` back-reference** defeats immutability — any consumer can reach the full mutable pool
- **`AgentFactory` holds its own `self._pool`** and also reads `host_context.pool` (redundant)
- **Skill orchestration logic** (`is_skill_visible_to_node`, `get_skill_instructions_for_node`, `skill_capabilities`, `skill_provider`, `skill_commands`) lives on `AgentPool` with no service abstraction
- **Protocol servers** (ACP, OpenCode) directly receive `AgentPool` in constructors, bypassing the DI design

### Root Cause

The original M1 design was correct: `HostContext` = immutable DI bundle, `AgentPool` = lifecycle owner. The implementation drifted in three ways:

1. `pool` back-reference added to `HostContext` as temporary escape hatch, never removed
2. Skill business logic not extracted into a service, left on `AgentPool`
3. Protocol servers never migrated to receive `HostContext` (M2 Phase 2 deferred, never completed)

## Design

### Architecture Alignment (RFC #135)

The six-layer architecture defines clear boundaries:

```
Layer 1: ConfigRegistry — versioned config storage
Layer 2: AgentHost — owns mutable infrastructure, constructs HostContext
Layer 3: AgentFactory — compiles (manifest, host_context) → AgentRegistry
Layer 4: RunLoop — drives idle → running → idle cycle
Layer 5: Agent Core — MessageNode, receives AgentContext at runtime
Layer 6: ProtocolServer — translates wire protocols ↔ RunLoop
```

`HostContext` is the **Layer 2 → Layer 3/4/5 dependency injection bundle**. It carries service references (not raw pool attributes). Each field is an independent service object with its own responsibilities.

`AgentContext` (Layer 4 → 5) carries per-run state. `AgentContext.host: HostContext | None` is "rarely needed, for advanced tools" per RFC #135.

### Changes

#### 1. Extract `SkillService` Protocol

Skill orchestration logic on `AgentPool` forms a cohesive cluster that should be a service, not pool attributes:

**State**: `skill_capabilities`, `skill_provider`, `skill_commands`, `_skill_resolver`
**Query methods**: `is_visible_to_node(skill, node_name)`, `get_instructions_for_node(skill_name, node_name)`

```python
# capabilities/skill_service.py — NEW FILE

from __future__ import annotations
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable

if TYPE_CHECKING:
from agentpool_toolsets.builtin.combined_toolset import CombinedToolsetCapability
from agentpool.skills.command_registry import SkillCommandRegistry

@runtime_checkable
class SkillService(Protocol):
"""Pool-scoped skill operations service.

Encapsulates skill capability management, scope-based visibility,
and instruction loading without exposing the full AgentPool.
AgentPool implements this protocol; HostContext carries the reference.
"""

@property
def capabilities(self) -> list[Any]:
"""Pool-scoped SkillCapability instances created during __aenter__."""
...

@property
def provider(self) -> CombinedToolsetCapability | None:
"""Combined toolset for skill URI resolution."""
...

@property
def commands(self) -> SkillCommandRegistry | None:
"""Skill command registry for slash commands."""
...

def is_visible_to_node(self, skill: Any, node_name: str | None) -> bool:
"""Check if a skill is visible to a node's package scope."""
...

async def get_instructions_for_node(self, skill_name: str, node_name: str) -> str:
"""Load skill instructions using a target node's package scope."""
...
```

Write operations (`register_skill_provider`, `unregister_skill_provider`) are **excluded** — they only run during pool `__aenter__`, never through `HostContext` at runtime.

#### 2. Extend `HostContext`

Add two fields:

```python
# host/context.py — MODIFIED

@dataclass(frozen=True)
class HostContext:
# ... existing 17 fields unchanged ...
main_agent_name: str | None = None # NEW: from pool constructor param
skill_service: SkillService | None = None # NEW: skill orchestration service
# pool: AgentPool[Any] | None = None # REMOVED in Phase 4
```

- `main_agent_name`: resolved from pool constructor param or `manifest.default_agent`. Simple string, same category as `config_file_path`.
- `skill_service`: `AgentPool` implements `SkillService`. HostContext carries the reference. Same pattern as `mcp: MCPManager`, `storage: StorageManager`.

Config-derived data that does **not** need new fields:
- `agent_configs` → callers use `ctx.manifest.agents`
- `compaction_pipeline` → callers use `ctx.manifest.get_compaction_pipeline()`
- `main_agent_config` → callers use `ctx.manifest.agents[ctx.main_agent_name]`

#### 3. Update `AgentPool.get_context()`

```python
def get_context(self) -> HostContext:
self._host_context = HostContext(
# ... existing fields ...
main_agent_name=self.main_agent_name,
skill_service=self, # AgentPool implements SkillService
# pool=self, # REMOVED in Phase 4
)
return self._host_context
```

#### 4. Add `MessageNode._bind_pool()` for internal wiring

```python
# messaging/messagenode.py — ADDED

def _bind_pool(self, pool: AgentPool[Any] | None) -> None:
"""Internal: bind node to pool for host_context access.

Used by Talk wiring to propagate pool reference to callback-created nodes.
This is the only legitimate writer of _agent_pool outside __init__.
"""
self._agent_pool = pool
```

#### 5. Migrate all `.agent_pool` callers to `.host_context`

Complete access mapping:

| Old access | New access | Phase |
|---|---|---|
| `agent_pool.manifest` | `host_context.manifest` | 2-3 |
| `agent_pool.session_pool` | `host_context.session_pool` | 2-3 |
| `agent_pool.storage` | `host_context.storage` | 2-3 |
| `agent_pool.mcp` | `host_context.mcp` | 2-3 |
| `agent_pool.skills` | `host_context.skills_registry` | 2-3 |
| `agent_pool.prompt_manager` | `host_context.prompt_manager` | 2-3 |
| `agent_pool.main_agent_name` | `host_context.main_agent_name` | 2-3 |
| `agent_pool.agent_configs` | `host_context.manifest.agents` | 2-3 |
| `agent_pool.compaction_pipeline` | `host_context.manifest.get_compaction_pipeline()` | 2-3 |
| `agent_pool.skill_capabilities` | `host_context.skill_service.capabilities` | 2 |
| `agent_pool.skill_provider` | `host_context.skill_service.provider` | 2 |
| `agent_pool.skill_commands` | `host_context.skill_service.commands` | 3 |
| `agent_pool.is_skill_visible_to_node()` | `host_context.skill_service.is_visible_to_node()` | 2 |
| `agent_pool.get_skill_instructions_for_node()` | `host_context.skill_service.get_instructions_for_node()` | 2 |

#### 6. Migrate `ACPProtocolHandler` to receive `HostContext`

```python
# handler.py — MODIFIED

class ACPProtocolHandler(ProtocolEventConsumerMixin):
def __init__(
self,
host_context: HostContext, # was: agent_pool: AgentPool[Any]
...
) -> None:
super().__init__()
self._host_context = host_context
...

@property
def event_bus(self) -> EventBus:
session_pool = self._host_context.session_pool
...
```

#### 7. Migrate `ACPSession.agent_pool` property

```python
# session.py — MODIFIED

@property
def host_context(self) -> HostContext: # was: def agent_pool
ctx = self.agent.host_context
if ctx is None:
raise RuntimeError("Agent has no associated pool")
return ctx
```

#### 8. Remove `pool` from `HostContext` (Phase 4)

- `AgentFactory`: 3 uses of `host_context.pool` → `self._pool` (already exists)
- `talk/talk.py`: 2 uses of `ctx.pool` → `self.source._agent_pool` + `other._bind_pool(pool)`
- `host/context.py`: remove `pool` field
- `delegation/pool.py`: remove `pool=self` from `get_context()`

#### 9. Remove `MessageNode.agent_pool` property (Phase 5)

- Remove `agent_pool` property getter + setter from `messagenode.py`
- Keep `_agent_pool` private field (needed for `host_context` property)
- Update `storage` property to go through `host_context`
- Migrate all test files referencing `.agent_pool`

## Phased Implementation

### Dependency Graph

```
Phase 1 (Foundation) ── no behavior change, new structures
├──→ Phase 2 (Core migration) ── agents stop using agent_pool ──┐
└──→ Phase 3 (Server migration) ── servers stop using agent_pool ┤
Phase 4 (Backdoor removal)
Phase 5 (Property removal)
```

Phase 2 and Phase 3 can run in **parallel** — both depend only on Phase 1.

### Phase 1: Foundation

**Goal**: Create new structures alongside existing ones. Zero behavior change.

| Task | File | Type |
|---|---|---|
| Create `SkillService` Protocol | `capabilities/skill_service.py` (new) | New file |
| AgentPool implements SkillService | `delegation/pool.py` | No code change (duck-typed) |
| Add `main_agent_name` + `skill_service` to HostContext | `host/context.py` | Add 2 fields |
| Update `get_context()` to populate new fields | `delegation/pool.py` | Add 2 lines |
| Add `_bind_pool()` method | `messaging/messagenode.py` | Add method |

**Verification**: All tests pass. New fields default to `None`, no behavior change.

### Phase 2: Core Migration

**Goal**: Core agent code (~9 refs, 4 files) stops using `agent_pool` property.

| File | Refs | Key changes |
|---|---|---|
| `agents/native_agent/agent.py` | 3 | `skill_capabilities` → `skill_service.capabilities`; `is_skill_visible_to_node` → `skill_service.is_visible_to_node` |
| `delegation/base_team.py` | 3 | `skill_provider` → `skill_service.provider`; `get_skill_instructions_for_node` → `skill_service.get_instructions_for_node` |
| `agentpool_commands/utils.py` | 2 | `manifest.config_file_path` → `ctx.config_file_path` |
| `shared/model_utils.py` | 1 | `agent.agent_pool` → `agent.host_context` |

**Verification**: Core agent code no longer triggers `DeprecationWarning`. Unit tests pass.

### Phase 3: Server Migration

**Goal**: Protocol servers (~46 refs, 9 files) stop using `agent_pool`.

**3a: ACPProtocolHandler signature** (handler.py, 7 refs)
- Constructor: `agent_pool: AgentPool` → `host_context: HostContext`
- All `self.agent_pool.session_pool` → `self._host_context.session_pool`

**3b: AgentPoolACPAgent** (acp_agent.py, 28 refs)
- `self.agent_pool.manifest.X` → `ctx.manifest.X`
- `self.agent_pool.main_agent_name` → `ctx.main_agent_name`
- `self.agent_pool.session_pool` → `ctx.session_pool`
- `agent.agent_pool` on other objects → `agent.host_context`
- Constructor arg `agent_pool=self.agent_pool` → `host_context=self.host_context`

**3c: ACPSession** (session.py, 11 refs)
- `agent_pool` property → `host_context` property (delegates to `self.agent.host_context`)
- `self.agent_pool.skills` → `ctx.skills_registry`
- `self.agent_pool.agent_configs` → `ctx.manifest.agents`
- `self.agent_pool.skill_commands` → `ctx.skill_service.commands`
- `self.agent_pool.prompt_manager` → `ctx.prompt_manager`

**3d: OpenCode server** (4 files, 8 refs)
- `state.py`: `agent.agent_pool` → `agent.host_context`
- `server.py`: `agent.agent_pool.session_pool` → `ctx.session_pool`
- `session_routes.py`: `agent.agent_pool.compaction_pipeline` → `ctx.manifest.get_compaction_pipeline()`
- `agent_routes.py`: `state.agent.agent_pool` → `state.agent.host_context`

**3e: Debug commands** (debug_commands.py, 1 ref)
- `session.agent_pool.manifest.agents` → `ctx.manifest.agents`

**Verification**: All server code no longer triggers `DeprecationWarning`. ACP snapshot tests + OpenCode integration tests pass.

### Phase 4: Backdoor Removal

**Goal**: `HostContext.pool` removed. No path back to `AgentPool` through `HostContext`.

| File | Change |
|---|---|
| `host/factory.py` (3 refs) | `host_context.pool` → `self._pool` |
| `talk/talk.py` (2 refs) | `ctx.pool` → `self.source._agent_pool`; `other.agent_pool = ...` → `other._bind_pool(...)` |
| `host/context.py` | Remove `pool` field |
| `delegation/pool.py` | Remove `pool=self` from `get_context()` |

**Verification**: `grep -r 'host_context.pool' src/` returns 0. All tests pass.

### Phase 5: Property Removal

**Goal**: `MessageNode.agent_pool` property removed.

| File | Change |
|---|---|
| `messaging/messagenode.py` | Remove `agent_pool` property + setter; update `storage` property |
| `AGENTS.md` | Remove deprecation section |
| Test files | Migrate remaining `.agent_pool` references |

**Verification**: `grep -r '\.agent_pool\b' src/ tests/` returns 0 (excluding `_agent_pool` private field and constructor `agent_pool=` kwargs).

## Pre-M4 Baseline Definition

**Phases 1–4 = clean baseline**:

- ✅ All code accesses infrastructure through `host_context`
- ✅ `HostContext` has no `pool` back-reference
- ✅ `AgentFactory` uses own `self._pool` (not through `HostContext`)
- ✅ `SkillService` extracted as independent Protocol
- ✅ Protocol servers receive `HostContext`
- ⏸ `agent_pool` property still exists (Phase 5 optional, can defer to M4)

Phase 5 is **nice-to-have**: the property remaining doesn't affect M4 development since all internal code has migrated. But removing it is the final clean state.

## Risks

| Risk | Mitigation |
|---|---|
| `SkillService` is mutable (capabilities list rebuilt at runtime) | Acceptable — `HostContext` freezes the reference, not the referenced object's state. Same pattern as `MCPManager`. |
| ACPProtocolHandler constructed before pool `__aenter__` | Not possible — handler is created in `AgentPoolACPAgent.__post_init__`, after pool initialization. |
| Test files reference `.agent_pool` | Phase 5 includes test migration. Phases 1–4 leave property in place, so tests still pass. |
| `AgentFactory` still holds `self._pool` | Acknowledged. Full removal of pool reference from Factory requires config model refactoring (`cfg.get_agent(pool=...)`), which is M4 scope (config split). Pre-M4 baseline accepts Factory holding pool via constructor, not through HostContext. |
Loading
Loading