Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions agent/lifecycle/composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,3 @@ async def observe_composition_event(
# 2. Observe owns failure isolation for ordinary plugin listeners; binding
# and caller cancellation failures remain fail-loud at this boundary.
await snapshot.composition_root.context.observe(key, payload)


async def observe_composition_domain_event(event: object) -> None:
"""Bridge one domain event to its request-bound ObserveEventKey."""

# 1. Resolve only the three domain facts that have a stable v3 Observe seam.
from agent.turn_events.observe import (
MEMORY_WRITTEN_EVENT,
RETRIEVAL_COMPLETED_EVENT,
)
from core.memory.events import MemoryWritten, RetrievalCompleted

if isinstance(event, RetrievalCompleted):
await observe_composition_event(RETRIEVAL_COMPLETED_EVENT, event)
return
if isinstance(event, MemoryWritten):
await observe_composition_event(MEMORY_WRITTEN_EVENT, event)
4 changes: 1 addition & 3 deletions agent/lifecycle/phases/after_reasoning.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,9 +593,7 @@ async def run(self, frame: AfterReasoningFrame) -> AfterReasoningFrame:
def default_after_reasoning_modules(
bus: EventBus,
session_services: SessionServices,
plugin_modules: AfterReasoningModules | None = None,
) -> AfterReasoningModules:
legacy_modules = list(plugin_modules or [])
builtins: AfterReasoningModules = [
_BuildAfterReasoningCtxModule(),
_EmitAfterReasoningCtxModule(bus),
Expand All @@ -611,7 +609,7 @@ def default_after_reasoning_modules(
]
return cast(
AfterReasoningModules,
topo_sort_modules(builtins + legacy_modules),
topo_sort_modules(builtins),
)


Expand Down
3 changes: 1 addition & 2 deletions agent/lifecycle/phases/after_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ async def run(self, frame: AfterStepFrame) -> AfterStepFrame:

def default_after_step_modules(
bus: EventBus,
plugin_modules: AfterStepModules | None = None,
) -> AfterStepModules:
builtins: AfterStepModules = [
_CopyInputToCtxModule(),
Expand All @@ -117,5 +116,5 @@ def default_after_step_modules(
]
return cast(
AfterStepModules,
topo_sort_modules(builtins + list(plugin_modules or [])),
topo_sort_modules(builtins),
)
3 changes: 1 addition & 2 deletions agent/lifecycle/phases/after_turn.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,6 @@ def default_after_turn_modules(
bus: EventBus,
outbound: OutboundPort,
context: ContextBuilder,
plugin_modules: AfterTurnModules | None = None,
) -> AfterTurnModules:
builtins: AfterTurnModules = [
_BuildTurnWorkModule(context),
Expand All @@ -402,5 +401,5 @@ def default_after_turn_modules(
]
return cast(
AfterTurnModules,
topo_sort_modules(builtins + list(plugin_modules or [])),
topo_sort_modules(builtins),
)
3 changes: 1 addition & 2 deletions agent/lifecycle/phases/before_reasoning.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ def default_before_reasoning_modules(
tools: ToolRegistry,
session_manager: SessionManager,
context: ContextBuilder,
plugin_modules: BeforeReasoningModules | None = None,
) -> BeforeReasoningModules:
builtins: BeforeReasoningModules = [
_SyncToolContextModule(tools, session_manager),
Expand All @@ -170,5 +169,5 @@ def default_before_reasoning_modules(
]
return cast(
BeforeReasoningModules,
topo_sort_modules(builtins + list(plugin_modules or [])),
topo_sort_modules(builtins),
)
3 changes: 1 addition & 2 deletions agent/lifecycle/phases/before_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ async def run(self, frame: BeforeStepFrame) -> BeforeStepFrame:

def default_before_step_modules(
bus: EventBus,
plugin_modules: BeforeStepModules | None = None,
) -> BeforeStepModules:
builtins: BeforeStepModules = [
_BuildBeforeStepCtxModule(),
Expand All @@ -125,5 +124,5 @@ def default_before_step_modules(
]
return cast(
BeforeStepModules,
topo_sort_modules(builtins + list(plugin_modules or [])),
topo_sort_modules(builtins),
)
4 changes: 1 addition & 3 deletions agent/lifecycle/phases/before_turn.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,6 @@ def default_before_turn_modules(
bus: EventBus,
session_manager: SessionManager,
context_store: ContextStore,
*,
plugin_modules: BeforeTurnModules | None = None,
) -> BeforeTurnModules:
builtins: BeforeTurnModules = [
_AcquireSessionModule(session_manager),
Expand All @@ -183,5 +181,5 @@ def default_before_turn_modules(
]
return cast(
BeforeTurnModules,
topo_sort_modules(builtins + list(plugin_modules or [])),
topo_sort_modules(builtins),
)
3 changes: 1 addition & 2 deletions agent/lifecycle/phases/prompt_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ async def run(self, frame: PromptRenderFrame) -> PromptRenderFrame:
def default_prompt_render_modules(
bus: EventBus,
context: ContextBuilder,
plugin_modules: PromptRenderModules | None = None,
) -> PromptRenderModules:
builtins: PromptRenderModules = [
_BuildPromptRenderCtxModule(),
Expand All @@ -159,7 +158,7 @@ def default_prompt_render_modules(
]
return cast(
PromptRenderModules,
topo_sort_modules(builtins + list(plugin_modules or [])),
topo_sort_modules(builtins),
)


Expand Down
45 changes: 11 additions & 34 deletions agent/migrations/payloads/eventmail_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,19 +492,13 @@ class ContentTransitionResult(TypedDict):
class EventMailV3MigrationStore:
"""Persist Content revisions and expose source- and Wake-scoped transitions."""

def __init__(
self,
path: Path,
*,
data_access: Literal["read_write", "read_only"] = "read_write",
) -> None:
def __init__(self, path: Path) -> None:
self.path = path
self.data_access = data_access

def initialize(self) -> None:
"""Create or validate the exact schema and SQLite file integrity."""

with self._transaction(write=self.data_access == "read_write") as connection:
with self._transaction(write=True) as connection:
self._validate_schema(connection)
result = connection.execute("PRAGMA integrity_check").fetchone()
if result is None or result[0] != "ok":
Expand Down Expand Up @@ -2340,36 +2334,19 @@ def state_counts(self) -> dict[str, int]:

@contextmanager
def _transaction(self, *, write: bool) -> Generator[sqlite3.Connection]:
"""Open one mode-aware SQLite transaction and close it at the boundary."""
"""Open one SQLite transaction and close it at the boundary."""

# 1. Reject every candidate write at the store's single transaction boundary.
if write and self.data_access == "read_only":
raise PermissionError(
"Content read-only candidate cannot write shared data"
)

# 2. Preserve the formal store's serialized transaction and lazy schema setup.
if self.data_access == "read_write":
self.path.parent.mkdir(parents=True, exist_ok=True)
connection = sqlite3.connect(self.path)
else:
database_uri = self.path.resolve(strict=False).as_uri() + "?mode=ro"
connection = sqlite3.connect(database_uri, uri=True)
_ = write
self.path.parent.mkdir(parents=True, exist_ok=True)
connection = sqlite3.connect(self.path)
connection.row_factory = sqlite3.Row
try:
if self.data_access == "read_write":
_ = connection.execute("PRAGMA journal_mode = WAL")
_ = connection.execute("PRAGMA foreign_keys = ON")
_ = connection.execute("BEGIN IMMEDIATE")
self._ensure_schema(connection)
else:
_ = connection.execute("PRAGMA query_only = ON")
_ = connection.execute("BEGIN")
_ = connection.execute("PRAGMA journal_mode = WAL")
_ = connection.execute("PRAGMA foreign_keys = ON")
_ = connection.execute("BEGIN IMMEDIATE")
self._ensure_schema(connection)
yield connection
if self.data_access == "read_write":
connection.commit()
else:
connection.rollback()
connection.commit()
except BaseException:
connection.rollback()
raise
Expand Down
2 changes: 0 additions & 2 deletions agent/plugin_composition/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
Fiber,
FiberHandle,
HealthHandle,
Plugin,
RuntimeScope,
)
from agent.plugin_composition.overlay import (
Expand Down Expand Up @@ -443,7 +442,6 @@
"MobileUiRegistry",
"MobileUiRpcInvalidRequest",
"ObserveEventKey",
"Plugin",
"PluginChannels",
"PluginCommands",
"PluginBackgroundJobs",
Expand Down
70 changes: 41 additions & 29 deletions agent/plugin_composition/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from dataclasses import dataclass
from pathlib import Path
from types import ModuleType
from typing import Any, AsyncGenerator, Literal, Protocol, TypeVar, cast
from typing import Any, AsyncGenerator, TypeVar, cast

from agent.plugin_composition.effect import Effect, EffectSetup
from agent.plugin_composition.diagnostics import (
Expand Down Expand Up @@ -53,10 +53,6 @@
FiberObserver = Callable[["Fiber"], object]


class Plugin(Protocol):
def apply(self, ctx: Context) -> object: ...


class RuntimeScope:
"""Carry one exact snapshot from a source callback into one async operation."""

Expand Down Expand Up @@ -209,12 +205,6 @@ def data_root(self) -> Path:

return self.runtime.data_dir

@property
def data_access(self) -> Literal["read_write", "read_only"]:
"""Return the access mode assigned to this exact plugin Root."""

return self.runtime.data_access

def workspace_root(self, name: str) -> Path:
"""返回 Core 为当前 generation 投影的声明式 workspace root。"""

Expand All @@ -235,20 +225,24 @@ def _set_static_active(self, active: bool) -> None:

async def mount(
self,
plugin: Plugin | PluginApply,
plugin: PluginApply,
*,
name: str | None = None,
inject: Iterable[ServiceKey[object]] | None = None,
required_for_readiness: bool = True,
) -> FiberHandle:
reject_executor_context_access()
if not callable(plugin) or hasattr(plugin, "apply"):
raise TypeError("Context.mount 只接受 child callable")
fiber = await self._root._mount(
parent=self._fiber,
plugin=plugin,
name=name,
inject=inject,
required_for_readiness=required_for_readiness,
runtime=self._fiber.runtime,
plugin_module=self._fiber.plugin_module,
static_active=True,
)
return FiberHandle(fiber)

Expand Down Expand Up @@ -873,7 +867,7 @@ def on_dispose(self, observer: FiberObserver) -> Callable[[], None]:

async def mount(
self,
plugin: Plugin | PluginApply,
plugin: PluginApply,
*,
name: str | None = None,
inject: Iterable[ServiceKey[object]] | None = None,
Expand All @@ -886,6 +880,31 @@ async def mount(
inject=inject,
required_for_readiness=True,
runtime=runtime,
plugin_module=None,
static_active=True,
)

async def _mount_module(
self,
plugin: PluginApply,
*,
name: str,
inject: Iterable[ServiceKey[object]],
runtime: PluginRuntime,
plugin_module: ModuleType,
static_active: bool,
) -> Fiber:
"""Mount one Manager-validated V3 module adapter."""

return await self._mount(
parent=self.root_fiber,
plugin=plugin,
name=name,
inject=inject,
required_for_readiness=True,
runtime=runtime,
plugin_module=plugin_module,
static_active=static_active,
)

async def dispose(self) -> None:
Expand Down Expand Up @@ -1174,11 +1193,13 @@ async def _mount(
self,
*,
parent: Fiber,
plugin: Plugin | PluginApply,
plugin: PluginApply,
name: str | None,
inject: Iterable[ServiceKey[object]] | None,
required_for_readiness: bool,
runtime: PluginRuntime | None,
plugin_module: ModuleType | None,
static_active: bool,
) -> Fiber:
"""Publish only after parent ownership exists, then reconcile."""

Expand All @@ -1200,7 +1221,6 @@ async def _mount(
)

# 2. Parent ownership is visible before publication observers run.
static_active = getattr(plugin, "static_active", True)
if not isinstance(static_active, bool):
raise TypeError("插件 static_active 必须是 bool")
fiber = Fiber(
Expand All @@ -1212,11 +1232,7 @@ async def _mount(
parent=parent,
required_for_readiness=required_for_readiness,
runtime=runtime,
plugin_module=(
module
if isinstance((module := getattr(plugin, "module", None)), ModuleType)
else parent.plugin_module
),
plugin_module=plugin_module,
static_active=static_active,
)
self._next_fiber_id += 1
Expand All @@ -1240,23 +1256,19 @@ async def _mount(

def _resolve_plugin(
self,
plugin: Plugin | PluginApply,
plugin: PluginApply,
*,
name: str | None,
inject: Iterable[ServiceKey[object]] | None,
) -> tuple[PluginApply, str, tuple[ServiceKey[object], ...]]:
if callable(plugin) and not hasattr(plugin, "apply"):
apply = cast(PluginApply, plugin)
else:
candidate = getattr(plugin, "apply", None)
if not callable(candidate):
raise TypeError("插件必须是 callable 或提供 apply(ctx)")
apply = cast(PluginApply, candidate)
if not callable(plugin) or hasattr(plugin, "apply"):
raise TypeError("插件必须是 callable")
apply = plugin
resolved_name = name or str(getattr(plugin, "name", "")).strip()
resolved_name = resolved_name or getattr(apply, "__name__", "plugin")
raw_dependencies = inject
if raw_dependencies is None:
raw_dependencies = getattr(plugin, "inject", ())
raw_dependencies = ()
dependencies = tuple(cast(Iterable[ServiceKey[object]], raw_dependencies))
if len(set(dependencies)) != len(dependencies):
raise ValueError(f"插件依赖重复: {resolved_name}")
Expand Down
3 changes: 1 addition & 2 deletions agent/plugin_composition/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pathlib import Path
from collections.abc import Mapping
from types import MappingProxyType
from typing import Generic, Literal, TypeVar, cast
from typing import Generic, TypeVar, cast

T = TypeVar("T", covariant=True)

Expand Down Expand Up @@ -114,7 +114,6 @@ class PluginRuntime:
config: object
workspace_roots: tuple[str, ...] = ()
workspace_files: tuple[str, ...] = ()
data_access: Literal["read_write", "read_only"] = "read_write"

def workspace_root(self, name: str) -> Path:
"""解析插件声明过的产品级 workspace 顶层目录。"""
Expand Down
Loading
Loading