Narrow runtime bootstrap source boundary
- SHA
3c2687421eec9bd0c1eb8efd2348e9c7394da70d- Parents
-
854272b - Tree
cee7aac
3c26874
3c2687421eec9bd0c1eb8efd2348e9c7394da70d854272b
cee7aac| Status | File | + | - |
|---|---|---|---|
| M |
src/loader/agent/loop.py
|
8 | 2 |
| M |
src/loader/runtime/bootstrap.py
|
130 | 1 |
| M |
src/loader/runtime/conversation.py
|
11 | 6 |
| M |
src/loader/runtime/explore.py
|
8 | 3 |
| M |
src/loader/runtime/launcher.py
|
6 | 2 |
src/loader/agent/loop.pymodified@@ -8,6 +8,7 @@ from pathlib import Path | ||
| 8 | 8 | |
| 9 | 9 | from ..context.project import ProjectContext, detect_project |
| 10 | 10 | from ..llm.base import LLMBackend, Message, Role |
| 11 | +from ..runtime.bootstrap import build_runtime_bootstrap_source | |
| 11 | 12 | from ..runtime.capabilities import resolve_backend_capability_profile |
| 12 | 13 | from ..runtime.dod import DefinitionOfDoneStore |
| 13 | 14 | from ..runtime.events import AgentEvent, TurnSummary |
@@ -338,6 +339,11 @@ class Agent: | ||
| 338 | 339 | |
| 339 | 340 | self._steering_queue.put_nowait(message) |
| 340 | 341 | |
| 342 | + def build_runtime_source(self): | |
| 343 | + """Build the explicit runtime bootstrap source for public entrypoints.""" | |
| 344 | + | |
| 345 | + return build_runtime_bootstrap_source(self) | |
| 346 | + | |
| 341 | 347 | def drain_steering_messages(self) -> list[str]: |
| 342 | 348 | """Drain queued runtime steering messages.""" |
| 343 | 349 | |
@@ -394,7 +400,7 @@ class Agent: | ||
| 394 | 400 | # Mark agent as running (enables steering) |
| 395 | 401 | self._is_running = True |
| 396 | 402 | try: |
| 397 | - launcher = build_runtime_launcher(self) | |
| 403 | + launcher = build_runtime_launcher(self.build_runtime_source()) | |
| 398 | 404 | return await launcher.run_user_message( |
| 399 | 405 | user_message, |
| 400 | 406 | emit, |
@@ -457,7 +463,7 @@ class Agent: | ||
| 457 | 463 | if inspect.iscoroutine(result): |
| 458 | 464 | await result |
| 459 | 465 | |
| 460 | - launcher = build_runtime_launcher(self) | |
| 466 | + launcher = build_runtime_launcher(self.build_runtime_source()) | |
| 461 | 467 | self.last_turn_summary = await launcher.run_explore( |
| 462 | 468 | user_message, |
| 463 | 469 | emit, |
src/loader/runtime/bootstrap.pymodified@@ -2,8 +2,10 @@ | ||
| 2 | 2 | |
| 3 | 3 | from __future__ import annotations |
| 4 | 4 | |
| 5 | +from collections.abc import Callable | |
| 6 | +from dataclasses import dataclass, field | |
| 5 | 7 | from pathlib import Path |
| 6 | -from typing import Protocol | |
| 8 | +from typing import Any, Protocol | |
| 7 | 9 | |
| 8 | 10 | from ..context.project import ProjectContext |
| 9 | 11 | from ..llm.base import LLMBackend |
@@ -52,12 +54,138 @@ class RuntimeBootstrapSource(Protocol): | ||
| 52 | 54 | """Refresh the active capability profile.""" |
| 53 | 55 | |
| 54 | 56 | |
| 57 | +@dataclass(slots=True) | |
| 58 | +class RuntimeBootstrapView: | |
| 59 | + """Explicit runtime-facing bootstrap view over public shell state.""" | |
| 60 | + | |
| 61 | + project_root: Path | |
| 62 | + backend: LLMBackend | |
| 63 | + registry: ToolRegistry | |
| 64 | + session: ConversationSession | |
| 65 | + config: RuntimeConfigProtocol | |
| 66 | + project_context: ProjectContext | None | |
| 67 | + permission_policy: PermissionPolicy | |
| 68 | + permission_config_status: PermissionConfigStatus | |
| 69 | + safeguards: RuntimeSafeguardsProtocol | |
| 70 | + _get_capability_profile: Callable[[], CapabilityProfile] | |
| 71 | + _get_workflow_mode: Callable[[], str] | |
| 72 | + _set_workflow_mode: Callable[[str], None] | |
| 73 | + _get_current_task: Callable[[], str | None] | |
| 74 | + _set_current_task: Callable[[str | None], None] | |
| 75 | + _get_last_turn_summary: Callable[[], TurnSummary | None] | |
| 76 | + _set_last_turn_summary: Callable[[TurnSummary | None], None] | |
| 77 | + _get_prompt_format: Callable[[], str | None] | |
| 78 | + _get_prompt_sections: Callable[[], list[str]] | |
| 79 | + _queue_steering_message: Callable[[str], None] | |
| 80 | + _drain_steering_messages: Callable[[], list[str]] | |
| 81 | + _refresh_capability_profile: Callable[[], None] | |
| 82 | + metadata: dict[str, Any] = field(default_factory=dict) | |
| 83 | + | |
| 84 | + @property | |
| 85 | + def capability_profile(self) -> CapabilityProfile: | |
| 86 | + """Return the live capability profile from the public shell.""" | |
| 87 | + | |
| 88 | + return self._get_capability_profile() | |
| 89 | + | |
| 90 | + @property | |
| 91 | + def workflow_mode(self) -> str: | |
| 92 | + """Return the active workflow mode from the public shell.""" | |
| 93 | + | |
| 94 | + return self._get_workflow_mode() | |
| 95 | + | |
| 96 | + @property | |
| 97 | + def current_task(self) -> str | None: | |
| 98 | + """Return the current top-level task from the public shell.""" | |
| 99 | + | |
| 100 | + return self._get_current_task() | |
| 101 | + | |
| 102 | + @current_task.setter | |
| 103 | + def current_task(self, value: str | None) -> None: | |
| 104 | + self._set_current_task(value) | |
| 105 | + | |
| 106 | + @property | |
| 107 | + def last_turn_summary(self) -> TurnSummary | None: | |
| 108 | + """Return the latest turn summary from the public shell.""" | |
| 109 | + | |
| 110 | + return self._get_last_turn_summary() | |
| 111 | + | |
| 112 | + @last_turn_summary.setter | |
| 113 | + def last_turn_summary(self, value: TurnSummary | None) -> None: | |
| 114 | + self._set_last_turn_summary(value) | |
| 115 | + | |
| 116 | + @property | |
| 117 | + def prompt_format(self) -> str | None: | |
| 118 | + """Return the live prompt-format metadata from the public shell.""" | |
| 119 | + | |
| 120 | + return self._get_prompt_format() | |
| 121 | + | |
| 122 | + @property | |
| 123 | + def prompt_sections(self) -> list[str]: | |
| 124 | + """Return the live prompt-section metadata from the public shell.""" | |
| 125 | + | |
| 126 | + return list(self._get_prompt_sections()) | |
| 127 | + | |
| 128 | + def set_workflow_mode(self, workflow_mode: str) -> None: | |
| 129 | + """Update the active workflow mode via the public shell callback.""" | |
| 130 | + | |
| 131 | + self._set_workflow_mode(workflow_mode) | |
| 132 | + | |
| 133 | + def queue_steering_message(self, message: str) -> None: | |
| 134 | + """Queue one steering message through the public shell callback.""" | |
| 135 | + | |
| 136 | + self._queue_steering_message(message) | |
| 137 | + | |
| 138 | + def drain_steering_messages(self) -> list[str]: | |
| 139 | + """Drain steering messages through the public shell callback.""" | |
| 140 | + | |
| 141 | + return self._drain_steering_messages() | |
| 142 | + | |
| 143 | + def refresh_capability_profile(self) -> None: | |
| 144 | + """Refresh capabilities through the public shell callback.""" | |
| 145 | + | |
| 146 | + self._refresh_capability_profile() | |
| 147 | + | |
| 148 | + | |
| 149 | +def build_runtime_bootstrap_source(source: RuntimeBootstrapSource | Any) -> RuntimeBootstrapView: | |
| 150 | + """Coerce a public shell object into the explicit runtime bootstrap view.""" | |
| 151 | + | |
| 152 | + if isinstance(source, RuntimeBootstrapView): | |
| 153 | + return source | |
| 154 | + | |
| 155 | + return RuntimeBootstrapView( | |
| 156 | + project_root=source.project_root, | |
| 157 | + backend=source.backend, | |
| 158 | + registry=source.registry, | |
| 159 | + session=source.session, | |
| 160 | + config=source.config, | |
| 161 | + project_context=source.project_context, | |
| 162 | + permission_policy=source.permission_policy, | |
| 163 | + permission_config_status=source.permission_config_status, | |
| 164 | + safeguards=source.safeguards, | |
| 165 | + _get_capability_profile=lambda: source.capability_profile, | |
| 166 | + _get_workflow_mode=lambda: source.workflow_mode, | |
| 167 | + _set_workflow_mode=source.set_workflow_mode, | |
| 168 | + _get_current_task=lambda: source.current_task, | |
| 169 | + _set_current_task=lambda value: setattr(source, "current_task", value), | |
| 170 | + _get_last_turn_summary=lambda: source.last_turn_summary, | |
| 171 | + _set_last_turn_summary=lambda value: setattr(source, "last_turn_summary", value), | |
| 172 | + _get_prompt_format=lambda: source.prompt_format, | |
| 173 | + _get_prompt_sections=lambda: list(source.prompt_sections), | |
| 174 | + _queue_steering_message=source.queue_steering_message, | |
| 175 | + _drain_steering_messages=source.drain_steering_messages, | |
| 176 | + _refresh_capability_profile=source.refresh_capability_profile, | |
| 177 | + metadata={"owner_type": type(source).__name__}, | |
| 178 | + ) | |
| 179 | + | |
| 180 | + | |
| 55 | 181 | def sync_runtime_context( |
| 56 | 182 | context: RuntimeContext, |
| 57 | 183 | source: RuntimeBootstrapSource, |
| 58 | 184 | ) -> None: |
| 59 | 185 | """Synchronize mutable runtime context state from the bootstrap source.""" |
| 60 | 186 | |
| 187 | + source = build_runtime_bootstrap_source(source) | |
| 188 | + | |
| 61 | 189 | context.backend = source.backend |
| 62 | 190 | context.registry = source.registry |
| 63 | 191 | context.session = source.session |
@@ -74,6 +202,7 @@ def sync_runtime_context( | ||
| 74 | 202 | def build_runtime_context(source: RuntimeBootstrapSource) -> RuntimeContext: |
| 75 | 203 | """Build a typed runtime context from the shared bootstrap contract.""" |
| 76 | 204 | |
| 205 | + source = build_runtime_bootstrap_source(source) | |
| 77 | 206 | context: RuntimeContext | None = None |
| 78 | 207 | |
| 79 | 208 | def _set_workflow_mode(workflow_mode: str) -> None: |
src/loader/runtime/conversation.pymodified@@ -3,11 +3,16 @@ | ||
| 3 | 3 | from __future__ import annotations |
| 4 | 4 | |
| 5 | 5 | from collections.abc import Awaitable, Callable |
| 6 | -from typing import Any | |
| 7 | 6 | |
| 8 | 7 | from .artifact_invalidation import ArtifactInvalidationAssessor |
| 9 | 8 | from .assistant_turns import AssistantTurnRequester |
| 10 | -from .bootstrap import build_runtime_context, sync_runtime_context | |
| 9 | +from .bootstrap import ( | |
| 10 | + RuntimeBootstrapSource, | |
| 11 | + RuntimeBootstrapView, | |
| 12 | + build_runtime_bootstrap_source, | |
| 13 | + build_runtime_context, | |
| 14 | + sync_runtime_context, | |
| 15 | +) | |
| 11 | 16 | from .completion_policy import CompletionPolicy |
| 12 | 17 | from .dod import DefinitionOfDoneStore |
| 13 | 18 | from .events import AgentEvent, TurnSummary |
@@ -40,9 +45,9 @@ UserQuestionHandler = Callable[[str, list[str] | None], Awaitable[str]] | None | ||
| 40 | 45 | class ConversationRuntime: |
| 41 | 46 | """Runs one explicit conversation turn against the current session.""" |
| 42 | 47 | |
| 43 | - def __init__(self, agent: Any) -> None: | |
| 44 | - self.agent = agent | |
| 45 | - self.context = build_runtime_context(agent) | |
| 48 | + def __init__(self, source: RuntimeBootstrapSource) -> None: | |
| 49 | + self.source: RuntimeBootstrapView = build_runtime_bootstrap_source(source) | |
| 50 | + self.context = build_runtime_context(self.source) | |
| 46 | 51 | self.tracer = RuntimeTracer() |
| 47 | 52 | self.executor: ToolExecutor | None = None |
| 48 | 53 | self.dod_store = DefinitionOfDoneStore(self.context.project_root) |
@@ -142,7 +147,7 @@ class ConversationRuntime: | ||
| 142 | 147 | original_task=original_task, |
| 143 | 148 | on_user_question=on_user_question, |
| 144 | 149 | ) |
| 145 | - sync_runtime_context(self.context, self.agent) | |
| 150 | + sync_runtime_context(self.context, self.source) | |
| 146 | 151 | self.executor = prepared_turn.executor |
| 147 | 152 | summary = prepared_turn.summary |
| 148 | 153 | dod = prepared_turn.definition_of_done |
src/loader/runtime/explore.pymodified@@ -8,7 +8,11 @@ from collections.abc import Awaitable, Callable | ||
| 8 | 8 | from ..llm.base import Message, Role |
| 9 | 9 | from ..runtime.events import AgentEvent, TurnSummary |
| 10 | 10 | from ..tools.base import create_explore_registry |
| 11 | -from .bootstrap import build_runtime_context | |
| 11 | +from .bootstrap import ( | |
| 12 | + RuntimeBootstrapSource, | |
| 13 | + build_runtime_bootstrap_source, | |
| 14 | + build_runtime_context, | |
| 15 | +) | |
| 12 | 16 | from .context import RuntimeContext |
| 13 | 17 | from .executor import ToolExecutionState, ToolExecutor |
| 14 | 18 | from .explore_state import ExploreSnapshot, ExploreStateStore |
@@ -75,8 +79,9 @@ Current directory: {cwd} | ||
| 75 | 79 | class ExploreRuntime: |
| 76 | 80 | """Minimal read-only runtime for lookup-oriented tasks.""" |
| 77 | 81 | |
| 78 | - def __init__(self, agent) -> None: | |
| 79 | - self.context: RuntimeContext = build_runtime_context(agent) | |
| 82 | + def __init__(self, source: RuntimeBootstrapSource) -> None: | |
| 83 | + self.source = build_runtime_bootstrap_source(source) | |
| 84 | + self.context: RuntimeContext = build_runtime_context(self.source) | |
| 80 | 85 | self.registry = create_explore_registry(self.context.project_root) |
| 81 | 86 | explore_rules = PermissionRuleSet( |
| 82 | 87 | deny=list(self.context.permission_policy.rules.deny), |
src/loader/runtime/launcher.pymodified@@ -3,7 +3,11 @@ | ||
| 3 | 3 | from __future__ import annotations |
| 4 | 4 | |
| 5 | 5 | from ..llm.base import Message, Role |
| 6 | -from .bootstrap import RuntimeBootstrapSource | |
| 6 | +from .bootstrap import ( | |
| 7 | + RuntimeBootstrapSource, | |
| 8 | + RuntimeBootstrapView, | |
| 9 | + build_runtime_bootstrap_source, | |
| 10 | +) | |
| 7 | 11 | from .chat_lane import ConversationalTurnRunner |
| 8 | 12 | from .conversation import ConfirmationHandler, ConversationRuntime, EventSink, UserQuestionHandler |
| 9 | 13 | from .decomposition_lane import DecompositionTurnRunner |
@@ -18,7 +22,7 @@ class RuntimeLauncher: | ||
| 18 | 22 | """Thin launcher over the shared runtime bootstrap contract.""" |
| 19 | 23 | |
| 20 | 24 | def __init__(self, source: RuntimeBootstrapSource) -> None: |
| 21 | - self.source = source | |
| 25 | + self.source: RuntimeBootstrapView = build_runtime_bootstrap_source(source) | |
| 22 | 26 | |
| 23 | 27 | async def run_conversational( |
| 24 | 28 | self, |