Add shared runtime bootstrap seam
- SHA
7421dd461ba479f79f60f0b755a16e9795caf8d9- Parents
-
d2f0e1c - Tree
efe6504
7421dd4
7421dd461ba479f79f60f0b755a16e9795caf8d9d2f0e1c
efe6504| Status | File | + | - |
|---|---|---|---|
| M |
src/loader/agent/loop.py
|
10 | 37 |
| A |
src/loader/runtime/bootstrap.py
|
106 | 0 |
| M |
src/loader/runtime/conversation.py
|
3 | 5 |
| M |
src/loader/runtime/explore.py
|
2 | 1 |
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, ToolCall |
| 11 | +from ..runtime.bootstrap import build_runtime_context | |
| 11 | 12 | from ..runtime.capabilities import resolve_backend_capability_profile |
| 12 | 13 | from ..runtime.context import RuntimeContext |
| 13 | 14 | from ..runtime.conversation import ConversationRuntime |
@@ -20,7 +21,6 @@ from ..runtime.permissions import ( | ||
| 20 | 21 | load_permission_rules, |
| 21 | 22 | ) |
| 22 | 23 | from ..runtime.prompt_history import PromptSnapshot |
| 23 | -from ..runtime.reasoning_service import RuntimeReasoningService | |
| 24 | 24 | from ..runtime.session import ConversationSession |
| 25 | 25 | from ..runtime.workflow import WorkflowMode |
| 26 | 26 | from ..tools.base import ToolRegistry, create_default_registry |
@@ -344,47 +344,20 @@ class Agent: | ||
| 344 | 344 | self._system_message = None |
| 345 | 345 | self._use_react = None |
| 346 | 346 | |
| 347 | - def _build_runtime_context(self) -> RuntimeContext: | |
| 348 | - """Build a typed runtime context over the current agent state.""" | |
| 347 | + def queue_steering_message(self, message: str) -> None: | |
| 348 | + """Queue one runtime steering message.""" | |
| 349 | 349 | |
| 350 | - context: RuntimeContext | None = None | |
| 350 | + self._steering_queue.put_nowait(message) | |
| 351 | 351 | |
| 352 | - def _queue_steering_message(message: str) -> None: | |
| 353 | - self._steering_queue.put_nowait(message) | |
| 352 | + def drain_steering_messages(self) -> list[str]: | |
| 353 | + """Drain queued runtime steering messages.""" | |
| 354 | 354 | |
| 355 | - def _set_workflow_mode(mode: str) -> None: | |
| 356 | - self.set_workflow_mode(mode) | |
| 357 | - if context is not None: | |
| 358 | - context.workflow_mode = self.workflow_mode | |
| 359 | - context.prompt_format = self.prompt_format | |
| 360 | - context.prompt_sections = list(self.prompt_sections) | |
| 355 | + return self._drain_steering_queue() | |
| 361 | 356 | |
| 362 | - def _refresh_capability_profile() -> None: | |
| 363 | - self.refresh_capability_profile() | |
| 364 | - if context is not None: | |
| 365 | - context.capability_profile = self.capability_profile | |
| 357 | + def _build_runtime_context(self) -> RuntimeContext: | |
| 358 | + """Build a typed runtime context over the current agent state.""" | |
| 366 | 359 | |
| 367 | - context = RuntimeContext( | |
| 368 | - project_root=self.project_root, | |
| 369 | - backend=self.backend, | |
| 370 | - registry=self.registry, | |
| 371 | - session=self.session, | |
| 372 | - config=self.config, | |
| 373 | - capability_profile=self.capability_profile, | |
| 374 | - project_context=self.project_context, | |
| 375 | - permission_policy=self.permission_policy, | |
| 376 | - permission_config_status=self.permission_config_status, | |
| 377 | - workflow_mode=self.workflow_mode, | |
| 378 | - safeguards=self.safeguards, | |
| 379 | - reasoning=RuntimeReasoningService(self.backend, self.config), | |
| 380 | - prompt_format=self.prompt_format, | |
| 381 | - prompt_sections=list(self.prompt_sections), | |
| 382 | - set_workflow_mode_callback=_set_workflow_mode, | |
| 383 | - drain_steering_messages_callback=self._drain_steering_queue, | |
| 384 | - queue_steering_message_callback=_queue_steering_message, | |
| 385 | - refresh_capability_profile_callback=_refresh_capability_profile, | |
| 386 | - ) | |
| 387 | - return context | |
| 360 | + return build_runtime_context(self) | |
| 388 | 361 | |
| 389 | 362 | def _get_few_shot_examples(self) -> list[Message]: |
| 390 | 363 | """Get few-shot examples demonstrating proper tool use.""" |
src/loader/runtime/bootstrap.pyadded@@ -0,0 +1,106 @@ | ||
| 1 | +"""Shared runtime bootstrap helpers for typed runtime contexts.""" | |
| 2 | + | |
| 3 | +from __future__ import annotations | |
| 4 | + | |
| 5 | +from pathlib import Path | |
| 6 | +from typing import Protocol | |
| 7 | + | |
| 8 | +from ..context.project import ProjectContext | |
| 9 | +from ..llm.base import LLMBackend | |
| 10 | +from ..tools.base import ToolRegistry | |
| 11 | +from .capabilities import CapabilityProfile | |
| 12 | +from .context import ( | |
| 13 | + RuntimeConfigProtocol, | |
| 14 | + RuntimeContext, | |
| 15 | + RuntimeSafeguardsProtocol, | |
| 16 | +) | |
| 17 | +from .permissions import PermissionConfigStatus, PermissionPolicy | |
| 18 | +from .reasoning_service import RuntimeReasoningService | |
| 19 | +from .session import ConversationSession | |
| 20 | + | |
| 21 | + | |
| 22 | +class RuntimeBootstrapSource(Protocol): | |
| 23 | + """Typed source object that can bootstrap one runtime context.""" | |
| 24 | + | |
| 25 | + project_root: Path | |
| 26 | + backend: LLMBackend | |
| 27 | + registry: ToolRegistry | |
| 28 | + session: ConversationSession | |
| 29 | + config: RuntimeConfigProtocol | |
| 30 | + capability_profile: CapabilityProfile | |
| 31 | + project_context: ProjectContext | None | |
| 32 | + permission_policy: PermissionPolicy | |
| 33 | + permission_config_status: PermissionConfigStatus | |
| 34 | + workflow_mode: str | |
| 35 | + prompt_format: str | None | |
| 36 | + prompt_sections: list[str] | |
| 37 | + safeguards: RuntimeSafeguardsProtocol | |
| 38 | + | |
| 39 | + def set_workflow_mode(self, workflow_mode: str) -> None: | |
| 40 | + """Update the active workflow mode.""" | |
| 41 | + | |
| 42 | + def queue_steering_message(self, message: str) -> None: | |
| 43 | + """Queue one steering message for the runtime.""" | |
| 44 | + | |
| 45 | + def drain_steering_messages(self) -> list[str]: | |
| 46 | + """Drain queued steering messages.""" | |
| 47 | + | |
| 48 | + def refresh_capability_profile(self) -> None: | |
| 49 | + """Refresh the active capability profile.""" | |
| 50 | + | |
| 51 | + | |
| 52 | +def sync_runtime_context( | |
| 53 | + context: RuntimeContext, | |
| 54 | + source: RuntimeBootstrapSource, | |
| 55 | +) -> None: | |
| 56 | + """Synchronize mutable runtime context state from the bootstrap source.""" | |
| 57 | + | |
| 58 | + context.backend = source.backend | |
| 59 | + context.registry = source.registry | |
| 60 | + context.session = source.session | |
| 61 | + context.config = source.config | |
| 62 | + context.capability_profile = source.capability_profile | |
| 63 | + context.project_context = source.project_context | |
| 64 | + context.permission_policy = source.permission_policy | |
| 65 | + context.permission_config_status = source.permission_config_status | |
| 66 | + context.workflow_mode = source.workflow_mode | |
| 67 | + context.prompt_format = source.prompt_format | |
| 68 | + context.prompt_sections = list(source.prompt_sections) | |
| 69 | + | |
| 70 | + | |
| 71 | +def build_runtime_context(source: RuntimeBootstrapSource) -> RuntimeContext: | |
| 72 | + """Build a typed runtime context from the shared bootstrap contract.""" | |
| 73 | + | |
| 74 | + context: RuntimeContext | None = None | |
| 75 | + | |
| 76 | + def _set_workflow_mode(workflow_mode: str) -> None: | |
| 77 | + source.set_workflow_mode(workflow_mode) | |
| 78 | + if context is not None: | |
| 79 | + sync_runtime_context(context, source) | |
| 80 | + | |
| 81 | + def _refresh_capability_profile() -> None: | |
| 82 | + source.refresh_capability_profile() | |
| 83 | + if context is not None: | |
| 84 | + context.capability_profile = source.capability_profile | |
| 85 | + | |
| 86 | + context = RuntimeContext( | |
| 87 | + project_root=source.project_root, | |
| 88 | + backend=source.backend, | |
| 89 | + registry=source.registry, | |
| 90 | + session=source.session, | |
| 91 | + config=source.config, | |
| 92 | + capability_profile=source.capability_profile, | |
| 93 | + project_context=source.project_context, | |
| 94 | + permission_policy=source.permission_policy, | |
| 95 | + permission_config_status=source.permission_config_status, | |
| 96 | + workflow_mode=source.workflow_mode, | |
| 97 | + safeguards=source.safeguards, | |
| 98 | + reasoning=RuntimeReasoningService(source.backend, source.config), | |
| 99 | + prompt_format=source.prompt_format, | |
| 100 | + prompt_sections=list(source.prompt_sections), | |
| 101 | + set_workflow_mode_callback=_set_workflow_mode, | |
| 102 | + drain_steering_messages_callback=source.drain_steering_messages, | |
| 103 | + queue_steering_message_callback=source.queue_steering_message, | |
| 104 | + refresh_capability_profile_callback=_refresh_capability_profile, | |
| 105 | + ) | |
| 106 | + return context | |
src/loader/runtime/conversation.pymodified@@ -7,6 +7,7 @@ from typing import Any | ||
| 7 | 7 | |
| 8 | 8 | from .artifact_invalidation import ArtifactInvalidationAssessor |
| 9 | 9 | from .assistant_turns import AssistantTurnRequester |
| 10 | +from .bootstrap import build_runtime_context, sync_runtime_context | |
| 10 | 11 | from .completion_policy import CompletionPolicy |
| 11 | 12 | from .dod import DefinitionOfDoneStore |
| 12 | 13 | from .events import AgentEvent, TurnSummary |
@@ -41,7 +42,7 @@ class ConversationRuntime: | ||
| 41 | 42 | |
| 42 | 43 | def __init__(self, agent: Any) -> None: |
| 43 | 44 | self.agent = agent |
| 44 | - self.context = agent._build_runtime_context() | |
| 45 | + self.context = build_runtime_context(agent) | |
| 45 | 46 | self.tracer = RuntimeTracer() |
| 46 | 47 | self.executor: ToolExecutor | None = None |
| 47 | 48 | self.dod_store = DefinitionOfDoneStore(self.context.project_root) |
@@ -141,10 +142,7 @@ class ConversationRuntime: | ||
| 141 | 142 | original_task=original_task, |
| 142 | 143 | on_user_question=on_user_question, |
| 143 | 144 | ) |
| 144 | - self.context.capability_profile = self.agent.capability_profile | |
| 145 | - self.context.workflow_mode = self.agent.workflow_mode | |
| 146 | - self.context.prompt_format = self.agent.prompt_format | |
| 147 | - self.context.prompt_sections = list(self.agent.prompt_sections) | |
| 145 | + sync_runtime_context(self.context, self.agent) | |
| 148 | 146 | self.executor = prepared_turn.executor |
| 149 | 147 | summary = prepared_turn.summary |
| 150 | 148 | dod = prepared_turn.definition_of_done |
src/loader/runtime/explore.pymodified@@ -8,6 +8,7 @@ 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 | 12 | from .context import RuntimeContext |
| 12 | 13 | from .executor import ToolExecutionState, ToolExecutor |
| 13 | 14 | from .hooks import build_default_tool_hooks |
@@ -74,7 +75,7 @@ class ExploreRuntime: | ||
| 74 | 75 | """Minimal read-only runtime for lookup-oriented tasks.""" |
| 75 | 76 | |
| 76 | 77 | def __init__(self, agent) -> None: |
| 77 | - self.context: RuntimeContext = agent._build_runtime_context() | |
| 78 | + self.context: RuntimeContext = build_runtime_context(agent) | |
| 78 | 79 | self.registry = create_explore_registry(self.context.project_root) |
| 79 | 80 | explore_rules = PermissionRuleSet( |
| 80 | 81 | deny=list(self.context.permission_policy.rules.deny), |