tenseleyflow/loader / 7421dd4

Browse files

Add shared runtime bootstrap seam

Authored by espadonne
SHA
7421dd461ba479f79f60f0b755a16e9795caf8d9
Parents
d2f0e1c
Tree
efe6504

4 changed files

StatusFile+-
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
88
 
99
 from ..context.project import ProjectContext, detect_project
1010
 from ..llm.base import LLMBackend, Message, Role, ToolCall
11
+from ..runtime.bootstrap import build_runtime_context
1112
 from ..runtime.capabilities import resolve_backend_capability_profile
1213
 from ..runtime.context import RuntimeContext
1314
 from ..runtime.conversation import ConversationRuntime
@@ -20,7 +21,6 @@ from ..runtime.permissions import (
2021
     load_permission_rules,
2122
 )
2223
 from ..runtime.prompt_history import PromptSnapshot
23
-from ..runtime.reasoning_service import RuntimeReasoningService
2424
 from ..runtime.session import ConversationSession
2525
 from ..runtime.workflow import WorkflowMode
2626
 from ..tools.base import ToolRegistry, create_default_registry
@@ -344,47 +344,20 @@ class Agent:
344344
             self._system_message = None
345345
         self._use_react = None
346346
 
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."""
349349
 
350
-        context: RuntimeContext | None = None
350
+        self._steering_queue.put_nowait(message)
351351
 
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."""
354354
 
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()
361356
 
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."""
366359
 
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)
388361
 
389362
     def _get_few_shot_examples(self) -> list[Message]:
390363
         """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
77
 
88
 from .artifact_invalidation import ArtifactInvalidationAssessor
99
 from .assistant_turns import AssistantTurnRequester
10
+from .bootstrap import build_runtime_context, sync_runtime_context
1011
 from .completion_policy import CompletionPolicy
1112
 from .dod import DefinitionOfDoneStore
1213
 from .events import AgentEvent, TurnSummary
@@ -41,7 +42,7 @@ class ConversationRuntime:
4142
 
4243
     def __init__(self, agent: Any) -> None:
4344
         self.agent = agent
44
-        self.context = agent._build_runtime_context()
45
+        self.context = build_runtime_context(agent)
4546
         self.tracer = RuntimeTracer()
4647
         self.executor: ToolExecutor | None = None
4748
         self.dod_store = DefinitionOfDoneStore(self.context.project_root)
@@ -141,10 +142,7 @@ class ConversationRuntime:
141142
             original_task=original_task,
142143
             on_user_question=on_user_question,
143144
         )
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)
148146
         self.executor = prepared_turn.executor
149147
         summary = prepared_turn.summary
150148
         dod = prepared_turn.definition_of_done
src/loader/runtime/explore.pymodified
@@ -8,6 +8,7 @@ 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
1112
 from .context import RuntimeContext
1213
 from .executor import ToolExecutionState, ToolExecutor
1314
 from .hooks import build_default_tool_hooks
@@ -74,7 +75,7 @@ class ExploreRuntime:
7475
     """Minimal read-only runtime for lookup-oriented tasks."""
7576
 
7677
     def __init__(self, agent) -> None:
77
-        self.context: RuntimeContext = agent._build_runtime_context()
78
+        self.context: RuntimeContext = build_runtime_context(agent)
7879
         self.registry = create_explore_registry(self.context.project_root)
7980
         explore_rules = PermissionRuleSet(
8081
             deny=list(self.context.permission_policy.rules.deny),