tenseleyflow/loader / 3c26874

Browse files

Narrow runtime bootstrap source boundary

Authored by espadonne
SHA
3c2687421eec9bd0c1eb8efd2348e9c7394da70d
Parents
854272b
Tree
cee7aac

5 changed files

StatusFile+-
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
88
 
99
 from ..context.project import ProjectContext, detect_project
1010
 from ..llm.base import LLMBackend, Message, Role
11
+from ..runtime.bootstrap import build_runtime_bootstrap_source
1112
 from ..runtime.capabilities import resolve_backend_capability_profile
1213
 from ..runtime.dod import DefinitionOfDoneStore
1314
 from ..runtime.events import AgentEvent, TurnSummary
@@ -338,6 +339,11 @@ class Agent:
338339
 
339340
         self._steering_queue.put_nowait(message)
340341
 
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
+
341347
     def drain_steering_messages(self) -> list[str]:
342348
         """Drain queued runtime steering messages."""
343349
 
@@ -394,7 +400,7 @@ class Agent:
394400
         # Mark agent as running (enables steering)
395401
         self._is_running = True
396402
         try:
397
-            launcher = build_runtime_launcher(self)
403
+            launcher = build_runtime_launcher(self.build_runtime_source())
398404
             return await launcher.run_user_message(
399405
                 user_message,
400406
                 emit,
@@ -457,7 +463,7 @@ class Agent:
457463
                 if inspect.iscoroutine(result):
458464
                     await result
459465
 
460
-        launcher = build_runtime_launcher(self)
466
+        launcher = build_runtime_launcher(self.build_runtime_source())
461467
         self.last_turn_summary = await launcher.run_explore(
462468
             user_message,
463469
             emit,
src/loader/runtime/bootstrap.pymodified
@@ -2,8 +2,10 @@
22
 
33
 from __future__ import annotations
44
 
5
+from collections.abc import Callable
6
+from dataclasses import dataclass, field
57
 from pathlib import Path
6
-from typing import Protocol
8
+from typing import Any, Protocol
79
 
810
 from ..context.project import ProjectContext
911
 from ..llm.base import LLMBackend
@@ -52,12 +54,138 @@ class RuntimeBootstrapSource(Protocol):
5254
         """Refresh the active capability profile."""
5355
 
5456
 
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
+
55181
 def sync_runtime_context(
56182
     context: RuntimeContext,
57183
     source: RuntimeBootstrapSource,
58184
 ) -> None:
59185
     """Synchronize mutable runtime context state from the bootstrap source."""
60186
 
187
+    source = build_runtime_bootstrap_source(source)
188
+
61189
     context.backend = source.backend
62190
     context.registry = source.registry
63191
     context.session = source.session
@@ -74,6 +202,7 @@ def sync_runtime_context(
74202
 def build_runtime_context(source: RuntimeBootstrapSource) -> RuntimeContext:
75203
     """Build a typed runtime context from the shared bootstrap contract."""
76204
 
205
+    source = build_runtime_bootstrap_source(source)
77206
     context: RuntimeContext | None = None
78207
 
79208
     def _set_workflow_mode(workflow_mode: str) -> None:
src/loader/runtime/conversation.pymodified
@@ -3,11 +3,16 @@
33
 from __future__ import annotations
44
 
55
 from collections.abc import Awaitable, Callable
6
-from typing import Any
76
 
87
 from .artifact_invalidation import ArtifactInvalidationAssessor
98
 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
+)
1116
 from .completion_policy import CompletionPolicy
1217
 from .dod import DefinitionOfDoneStore
1318
 from .events import AgentEvent, TurnSummary
@@ -40,9 +45,9 @@ UserQuestionHandler = Callable[[str, list[str] | None], Awaitable[str]] | None
4045
 class ConversationRuntime:
4146
     """Runs one explicit conversation turn against the current session."""
4247
 
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)
4651
         self.tracer = RuntimeTracer()
4752
         self.executor: ToolExecutor | None = None
4853
         self.dod_store = DefinitionOfDoneStore(self.context.project_root)
@@ -142,7 +147,7 @@ class ConversationRuntime:
142147
             original_task=original_task,
143148
             on_user_question=on_user_question,
144149
         )
145
-        sync_runtime_context(self.context, self.agent)
150
+        sync_runtime_context(self.context, self.source)
146151
         self.executor = prepared_turn.executor
147152
         summary = prepared_turn.summary
148153
         dod = prepared_turn.definition_of_done
src/loader/runtime/explore.pymodified
@@ -8,7 +8,11 @@ from collections.abc import Awaitable, Callable
88
 from ..llm.base import Message, Role
99
 from ..runtime.events import AgentEvent, TurnSummary
1010
 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
+)
1216
 from .context import RuntimeContext
1317
 from .executor import ToolExecutionState, ToolExecutor
1418
 from .explore_state import ExploreSnapshot, ExploreStateStore
@@ -75,8 +79,9 @@ Current directory: {cwd}
7579
 class ExploreRuntime:
7680
     """Minimal read-only runtime for lookup-oriented tasks."""
7781
 
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)
8085
         self.registry = create_explore_registry(self.context.project_root)
8186
         explore_rules = PermissionRuleSet(
8287
             deny=list(self.context.permission_policy.rules.deny),
src/loader/runtime/launcher.pymodified
@@ -3,7 +3,11 @@
33
 from __future__ import annotations
44
 
55
 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
+)
711
 from .chat_lane import ConversationalTurnRunner
812
 from .conversation import ConfirmationHandler, ConversationRuntime, EventSink, UserQuestionHandler
913
 from .decomposition_lane import DecompositionTurnRunner
@@ -18,7 +22,7 @@ class RuntimeLauncher:
1822
     """Thin launcher over the shared runtime bootstrap contract."""
1923
 
2024
     def __init__(self, source: RuntimeBootstrapSource) -> None:
21
-        self.source = source
25
+        self.source: RuntimeBootstrapView = build_runtime_bootstrap_source(source)
2226
 
2327
     async def run_conversational(
2428
         self,