sergent-py-runtime 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sergent_py_runtime/__init__.py +0 -0
- sergent_py_runtime/execution/__init__.py +0 -0
- sergent_py_runtime/execution/deterministic.py +102 -0
- sergent_py_runtime/execution/engine.py +490 -0
- sergent_py_runtime/execution/errors.py +67 -0
- sergent_py_runtime/execution/rebase.py +179 -0
- sergent_py_runtime/execution/scene_state.py +124 -0
- sergent_py_runtime/lifecycle/__init__.py +0 -0
- sergent_py_runtime/lifecycle/concurrency.py +71 -0
- sergent_py_runtime/lifecycle/observe.py +96 -0
- sergent_py_runtime/proposals/__init__.py +0 -0
- sergent_py_runtime/proposals/_contract.py +63 -0
- sergent_py_runtime/proposals/intent_proposals.py +11 -0
- sergent_py_runtime/proposals/intents.py +15 -0
- sergent_py_runtime/py.typed +0 -0
- sergent_py_runtime/run_record/__init__.py +0 -0
- sergent_py_runtime/run_record/run.py +382 -0
- sergent_py_runtime/run_record/run_capture.py +195 -0
- sergent_py_runtime/run_record/run_record_file.py +373 -0
- sergent_py_runtime-0.1.0.dist-info/METADATA +101 -0
- sergent_py_runtime-0.1.0.dist-info/RECORD +23 -0
- sergent_py_runtime-0.1.0.dist-info/WHEEL +4 -0
- sergent_py_runtime-0.1.0.dist-info/licenses/LICENSE +201 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Provide the pure synchronous Patch execution core. @sergent/docs/execution-model.md
|
|
2
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import collections.abc as cabc
|
|
7
|
+
import typing
|
|
8
|
+
|
|
9
|
+
import sergent_py_core.operation as core_operation
|
|
10
|
+
import sergent_py_core.patch as core_patch
|
|
11
|
+
import sergent_py_core.scene as core_scene
|
|
12
|
+
import sergent_py_core.scene_actions as core_scene_actions
|
|
13
|
+
import sergent_py_core.target as core_target
|
|
14
|
+
import sergent_py_runtime.execution.errors as runtime_errors
|
|
15
|
+
|
|
16
|
+
SceneT = typing.TypeVar("SceneT")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _validate_operations(
|
|
20
|
+
scene_actions: core_scene_actions.SceneActions[SceneT],
|
|
21
|
+
scene: SceneT,
|
|
22
|
+
intent: object,
|
|
23
|
+
target: core_target.Target,
|
|
24
|
+
operations: cabc.Sequence[core_operation.Operation],
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Reject the first Operation inadmissible for one bounded pass. @sergent/docs/framework.md
|
|
27
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
28
|
+
for index, operation in enumerate(operations):
|
|
29
|
+
isolated_scene = scene_actions.clone(scene)
|
|
30
|
+
try:
|
|
31
|
+
operation.validate_for(isolated_scene, intent, target)
|
|
32
|
+
except ValueError as exc:
|
|
33
|
+
metadata = {"index": index, "call": operation.call, "operation_id": operation.op_id}
|
|
34
|
+
raise runtime_errors._OperationAdmissibilityError(str(exc), metadata) from exc
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def validate_patch(
|
|
38
|
+
scene: core_scene_actions.SceneActions[SceneT],
|
|
39
|
+
snapshot: SceneT,
|
|
40
|
+
identity: core_scene.SceneIdentity,
|
|
41
|
+
target: core_target.Target,
|
|
42
|
+
patch: core_patch.Patch,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Reject a Patch unsafe for its captured Scene context. @sergent/docs/framework.md
|
|
45
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
46
|
+
if patch.base.scene_id != identity.scene_id:
|
|
47
|
+
raise runtime_errors.PatchValidationError("patch scene mismatch")
|
|
48
|
+
if patch.base.revision != identity.revision:
|
|
49
|
+
raise runtime_errors.StalePatchError(
|
|
50
|
+
"stale patch",
|
|
51
|
+
base_revision=patch.base.revision,
|
|
52
|
+
current_revision=identity.revision,
|
|
53
|
+
)
|
|
54
|
+
if not patch.operations:
|
|
55
|
+
raise runtime_errors.PatchValidationError("no-op patch")
|
|
56
|
+
if not scene.has_target(snapshot, target):
|
|
57
|
+
raise runtime_errors.PatchValidationError("missing target")
|
|
58
|
+
if len(patch.operation_trace) != len(patch.operations):
|
|
59
|
+
raise runtime_errors.PatchValidationError("operation trace must match operations")
|
|
60
|
+
|
|
61
|
+
seen_ops: set[str] = set()
|
|
62
|
+
for trace, operation in zip(patch.operation_trace, patch.operations):
|
|
63
|
+
if not isinstance(operation, core_operation.Operation):
|
|
64
|
+
raise runtime_errors.PatchValidationError("operation must subclass Operation")
|
|
65
|
+
op_id = operation.op_id
|
|
66
|
+
if op_id in seen_ops:
|
|
67
|
+
raise runtime_errors.PatchValidationError("duplicate op_id")
|
|
68
|
+
seen_ops.add(op_id)
|
|
69
|
+
if trace.op_id != op_id:
|
|
70
|
+
raise runtime_errors.PatchValidationError("operation trace op_id mismatch")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def dry_run(
|
|
74
|
+
scene: core_scene_actions.SceneActions[SceneT],
|
|
75
|
+
snapshot: SceneT,
|
|
76
|
+
identity: core_scene.SceneIdentity,
|
|
77
|
+
target: core_target.Target,
|
|
78
|
+
patch: core_patch.Patch,
|
|
79
|
+
) -> SceneT:
|
|
80
|
+
"""Rehearse a Patch without source mutation. @sergent/docs/execution-model.md
|
|
81
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
82
|
+
after = scene.apply(scene.clone(snapshot), target, list(patch.operations))
|
|
83
|
+
report = scene.verify(snapshot, after, target, list(patch.operations))
|
|
84
|
+
if not report.ok:
|
|
85
|
+
raise runtime_errors.DryRunError(
|
|
86
|
+
"dry-run verification failed: " + "; ".join(report.issues),
|
|
87
|
+
metadata={"verification_issues": list(report.issues)},
|
|
88
|
+
)
|
|
89
|
+
if scene.identity(after).scene_id != identity.scene_id:
|
|
90
|
+
raise ValueError("dry-run scene mismatch")
|
|
91
|
+
return after
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def commit(
|
|
95
|
+
snapshot: SceneT,
|
|
96
|
+
target: core_target.Target,
|
|
97
|
+
patch: core_patch.Patch,
|
|
98
|
+
scene: core_scene_actions.SceneActions[SceneT],
|
|
99
|
+
) -> SceneT:
|
|
100
|
+
"""Apply a Patch to an isolated plain Scene. @sergent/docs/execution-model.md
|
|
101
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
102
|
+
return scene.apply(scene.clone(snapshot), target, list(patch.operations))
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"""Execute one bounded run through the Sergent safety pipeline. @sergent/docs/execution-model.md
|
|
2
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import dataclasses
|
|
8
|
+
import typing
|
|
9
|
+
|
|
10
|
+
import pydantic
|
|
11
|
+
|
|
12
|
+
import sergent_py_core.errors as core_errors
|
|
13
|
+
import sergent_py_core.intent as core_intent
|
|
14
|
+
import sergent_py_core.mindbuf as core_mindbuf
|
|
15
|
+
import sergent_py_core.proposals.operation_registry as core_operation_registry
|
|
16
|
+
import sergent_py_core.patch as core_patch
|
|
17
|
+
import sergent_py_core.plan as core_plan
|
|
18
|
+
import sergent_py_core.recipe as core_recipe
|
|
19
|
+
import sergent_py_core.result as core_result
|
|
20
|
+
import sergent_py_core.scene as core_scene
|
|
21
|
+
import sergent_py_core.scene_actions as core_scene_actions
|
|
22
|
+
import sergent_py_core.target as core_target
|
|
23
|
+
import sergent_py_core.identifiers as core_identifiers
|
|
24
|
+
import sergent_py_core.model_calls as core_model_calls
|
|
25
|
+
import sergent_py_runtime.execution.deterministic as runtime_deterministic
|
|
26
|
+
import sergent_py_runtime.execution.errors as runtime_errors
|
|
27
|
+
import sergent_py_runtime.execution.rebase as runtime_rebase
|
|
28
|
+
import sergent_py_runtime.execution.scene_state as runtime_scene_state
|
|
29
|
+
import sergent_py_runtime.run_record.run as runtime_run
|
|
30
|
+
import sergent_py_runtime.lifecycle.concurrency as runtime_concurrency
|
|
31
|
+
import sergent_py_runtime.lifecycle.observe as runtime_observe
|
|
32
|
+
import sergent_py_runtime.proposals._contract as runtime_proposal_contract
|
|
33
|
+
|
|
34
|
+
SceneT = typing.TypeVar("SceneT")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclasses.dataclass(frozen=True)
|
|
38
|
+
class _CommitInput:
|
|
39
|
+
"""Carry trusted Intent and Patch evidence into commit. @sergent/docs/execution-model.md
|
|
40
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
41
|
+
|
|
42
|
+
intent: object
|
|
43
|
+
patch: core_patch.Patch
|
|
44
|
+
patch_summary: dict[str, object]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclasses.dataclass(frozen=True)
|
|
48
|
+
class _RunContext(typing.Generic[SceneT]):
|
|
49
|
+
"""Retain the stable Scene, identity, and exact selected Target. @sergent/docs/framework.md
|
|
50
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
51
|
+
|
|
52
|
+
base_scene: SceneT
|
|
53
|
+
identity: core_scene.SceneIdentity
|
|
54
|
+
target: core_target.Target
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SergentRuntime(typing.Generic[SceneT]):
|
|
58
|
+
"""Drive one run while retaining sole mutation authority. @sergent/docs/execution-model.md
|
|
59
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
client: core_model_calls.ModelClient,
|
|
64
|
+
scene: core_scene_actions.SceneActions[SceneT],
|
|
65
|
+
recipe: core_recipe.SergentRecipe[SceneT, typing.Any, typing.Any],
|
|
66
|
+
*,
|
|
67
|
+
observers: typing.Iterable[runtime_observe.RunObserver] = (),
|
|
68
|
+
) -> None:
|
|
69
|
+
self._client = client
|
|
70
|
+
self._scene = scene
|
|
71
|
+
self._recipe = recipe
|
|
72
|
+
self._proposals = runtime_proposal_contract._capture(recipe)
|
|
73
|
+
self._observers = tuple(observers)
|
|
74
|
+
|
|
75
|
+
def live_state(
|
|
76
|
+
self,
|
|
77
|
+
scene: SceneT,
|
|
78
|
+
*,
|
|
79
|
+
enforce_embedded_identity: bool = False,
|
|
80
|
+
) -> runtime_scene_state.SceneState[SceneT]:
|
|
81
|
+
"""Create revision-checked shared Scene authority. @sergent/docs/framework.md
|
|
82
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
83
|
+
return runtime_scene_state.SceneState(
|
|
84
|
+
self._scene.identity,
|
|
85
|
+
self._scene.clone,
|
|
86
|
+
scene,
|
|
87
|
+
enforce_embedded_identity=enforce_embedded_identity,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
async def run(
|
|
91
|
+
self,
|
|
92
|
+
source: SceneT | runtime_scene_state.SceneState[SceneT],
|
|
93
|
+
mindbuf: core_mindbuf.MindBuf,
|
|
94
|
+
*,
|
|
95
|
+
model_name: str,
|
|
96
|
+
cancel: runtime_concurrency.CancelToken | None = None,
|
|
97
|
+
) -> core_result.SergentResult[SceneT]:
|
|
98
|
+
"""Return a contained result from the bounded pipeline. @sergent/docs/execution-model.md
|
|
99
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
100
|
+
run = self._new_run(model_name)
|
|
101
|
+
return await self._execute(run, source, mindbuf, cancel)
|
|
102
|
+
|
|
103
|
+
def start(
|
|
104
|
+
self,
|
|
105
|
+
source: SceneT | runtime_scene_state.SceneState[SceneT],
|
|
106
|
+
mindbuf: core_mindbuf.MindBuf,
|
|
107
|
+
*,
|
|
108
|
+
model_name: str,
|
|
109
|
+
) -> runtime_concurrency.RunHandle[SceneT]:
|
|
110
|
+
"""Schedule the pipeline and return its run handle. @sergent/docs/execution-model.md
|
|
111
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
112
|
+
run = self._new_run(model_name)
|
|
113
|
+
token = runtime_concurrency.CancelToken()
|
|
114
|
+
task = asyncio.create_task(
|
|
115
|
+
self._execute(run, source, mindbuf, token),
|
|
116
|
+
name=f"sergent-run-{run.run_id}",
|
|
117
|
+
)
|
|
118
|
+
return runtime_concurrency.RunHandle(
|
|
119
|
+
run_id=run.run_id,
|
|
120
|
+
_run=run,
|
|
121
|
+
_task=task,
|
|
122
|
+
_cancel=token,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def _new_run(self, model_name: str) -> runtime_run.Run[SceneT]:
|
|
126
|
+
"""Create one progress and Run Record builder. @sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
127
|
+
return runtime_run.Run(
|
|
128
|
+
run_id=core_identifiers.new_id("run"),
|
|
129
|
+
model_name=model_name,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
async def _execute(
|
|
133
|
+
self,
|
|
134
|
+
run: runtime_run.Run[SceneT],
|
|
135
|
+
source: SceneT | runtime_scene_state.SceneState[SceneT],
|
|
136
|
+
mindbuf: core_mindbuf.MindBuf,
|
|
137
|
+
cancel: runtime_concurrency.CancelToken | None,
|
|
138
|
+
) -> core_result.SergentResult[SceneT]:
|
|
139
|
+
"""Contain ordinary failures and isolate observers. @sergent/docs/execution-model.md
|
|
140
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
141
|
+
observer_errors: list[core_errors.RunError] = []
|
|
142
|
+
self._deliver("progress", run.snapshot(), run.stage, observer_errors)
|
|
143
|
+
try:
|
|
144
|
+
result = await self._drive(run, source, mindbuf, cancel, observer_errors)
|
|
145
|
+
except asyncio.CancelledError:
|
|
146
|
+
base_scene = run.base_scene
|
|
147
|
+
identity = run.identity
|
|
148
|
+
if cancel is None or not cancel.cancelled() or base_scene is None or identity is None:
|
|
149
|
+
raise
|
|
150
|
+
result = run.cancelled(base_scene, checkpoint="task_cancelled", cancel=cancel)
|
|
151
|
+
except Exception as exc: # containment: ordinary exceptions -> structured failure
|
|
152
|
+
result = run.failure(exc)
|
|
153
|
+
self._deliver("progress", run.snapshot(), run.stage, observer_errors)
|
|
154
|
+
result.observer_errors.extend(observer_errors)
|
|
155
|
+
self._deliver("finished", result, result.stage, result.observer_errors)
|
|
156
|
+
return result
|
|
157
|
+
|
|
158
|
+
async def _drive(
|
|
159
|
+
self,
|
|
160
|
+
run: runtime_run.Run[SceneT],
|
|
161
|
+
source: SceneT | runtime_scene_state.SceneState[SceneT],
|
|
162
|
+
mindbuf: core_mindbuf.MindBuf,
|
|
163
|
+
cancel: runtime_concurrency.CancelToken | None,
|
|
164
|
+
observer_errors: list[core_errors.RunError],
|
|
165
|
+
) -> core_result.SergentResult[SceneT]:
|
|
166
|
+
"""Sequence proposal and deterministic stages. @sergent/docs/execution-model.md
|
|
167
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
168
|
+
base_scene, identity, target = self._process_input(run, source, mindbuf, observer_errors)
|
|
169
|
+
if target is None:
|
|
170
|
+
return run.no_target(base_scene, self._recipe.no_target_error)
|
|
171
|
+
context = _RunContext(base_scene=base_scene, identity=identity, target=target)
|
|
172
|
+
if cancel is not None and cancel.cancelled():
|
|
173
|
+
return run.cancelled(base_scene, checkpoint="before_intent", cancel=cancel)
|
|
174
|
+
run.open_step("intent", None)
|
|
175
|
+
intent_proposal_type = self._proposals.intent_proposal_type
|
|
176
|
+
intent_schema = self._proposals.intent_schema
|
|
177
|
+
if intent_schema is None:
|
|
178
|
+
intent_proposal = intent_proposal_type()
|
|
179
|
+
else:
|
|
180
|
+
intent_request = self._intent_request(run, context, mindbuf, intent_schema)
|
|
181
|
+
self._advance(run, runtime_observe.Stage.INTENT_CALL, observer_errors)
|
|
182
|
+
intent_response, intent_payload = await self._client.invoke(intent_request)
|
|
183
|
+
try:
|
|
184
|
+
intent_proposal = intent_proposal_type.model_validate(intent_payload)
|
|
185
|
+
except pydantic.ValidationError as exc:
|
|
186
|
+
run.finish_model_call(intent_response, None)
|
|
187
|
+
name = intent_proposal_type.__name__
|
|
188
|
+
raise runtime_errors._ProposalSchemaError(
|
|
189
|
+
f"invalid {name} proposal: {exc}"
|
|
190
|
+
) from exc
|
|
191
|
+
run.finish_model_call(intent_response, intent_proposal)
|
|
192
|
+
self._advance(run, runtime_observe.Stage.INTENT, observer_errors)
|
|
193
|
+
intent, flow = self._validated_intent(run, context, intent_proposal)
|
|
194
|
+
if cancel is not None and cancel.cancelled():
|
|
195
|
+
return run.cancelled(base_scene, checkpoint="after_intent_validation", cancel=cancel)
|
|
196
|
+
if flow == "stop":
|
|
197
|
+
return self._intent_stop(run, context, intent)
|
|
198
|
+
run.finish_step("success")
|
|
199
|
+
run.open_step("execution_plan", None)
|
|
200
|
+
plan_request, operation_registry = self._plan_request(run, context, intent, mindbuf)
|
|
201
|
+
self._advance(run, runtime_observe.Stage.PLAN_CALL, observer_errors)
|
|
202
|
+
plan_response, plan_payload = await self._client.invoke(plan_request)
|
|
203
|
+
try:
|
|
204
|
+
plan_proposal = operation_registry.decode_plan_proposal(
|
|
205
|
+
plan_payload,
|
|
206
|
+
max_operations=self._proposals.max_operations,
|
|
207
|
+
)
|
|
208
|
+
except ValueError as exc:
|
|
209
|
+
run.finish_model_call(plan_response, None)
|
|
210
|
+
raise runtime_errors._ProposalSchemaError(str(exc)) from exc
|
|
211
|
+
run.finish_model_call(plan_response, plan_proposal)
|
|
212
|
+
self._advance(run, runtime_observe.Stage.EXECUTION_PLAN, observer_errors)
|
|
213
|
+
plan = self._validated_plan(run, context, intent, plan_proposal)
|
|
214
|
+
self._advance(run, runtime_observe.Stage.PATCH, observer_errors)
|
|
215
|
+
patch, patch_summary = self._compiled_patch(run, context, plan)
|
|
216
|
+
if cancel is not None and cancel.cancelled():
|
|
217
|
+
return run.cancelled(base_scene, checkpoint="before_dry_run", cancel=cancel)
|
|
218
|
+
self._advance(run, runtime_observe.Stage.DRY_RUN, observer_errors)
|
|
219
|
+
self._dry_run_step(run, context, patch)
|
|
220
|
+
if cancel is not None and cancel.cancelled():
|
|
221
|
+
return run.cancelled(base_scene, checkpoint="before_commit", cancel=cancel)
|
|
222
|
+
self._advance(run, runtime_observe.Stage.COMMIT, observer_errors)
|
|
223
|
+
commit_input = _CommitInput(intent, patch, patch_summary)
|
|
224
|
+
return self._commit_step(run, source, context, commit_input)
|
|
225
|
+
|
|
226
|
+
def _intent_request(
|
|
227
|
+
self,
|
|
228
|
+
run: runtime_run.Run[SceneT],
|
|
229
|
+
context: _RunContext[SceneT],
|
|
230
|
+
mindbuf: core_mindbuf.MindBuf,
|
|
231
|
+
proposal_schema: core_model_calls.ProposalSchema,
|
|
232
|
+
) -> core_model_calls.ModelRequest:
|
|
233
|
+
"""Record a model-backed Intent proposal request. @sergent/docs/framework.md
|
|
234
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
235
|
+
request = self._recipe.build_intent_request(
|
|
236
|
+
context.base_scene, mindbuf, context.target, run.model_name, proposal_schema
|
|
237
|
+
)
|
|
238
|
+
if request.proposal_schema is not proposal_schema:
|
|
239
|
+
raise AssertionError("build_intent_request must return the provided schema object")
|
|
240
|
+
run.start_model_call(request)
|
|
241
|
+
return request
|
|
242
|
+
|
|
243
|
+
def _plan_request(
|
|
244
|
+
self,
|
|
245
|
+
run: runtime_run.Run[SceneT],
|
|
246
|
+
context: _RunContext[SceneT],
|
|
247
|
+
intent: typing.Any,
|
|
248
|
+
mindbuf: core_mindbuf.MindBuf,
|
|
249
|
+
) -> tuple[core_model_calls.ModelRequest, core_operation_registry.OperationRegistry]:
|
|
250
|
+
"""Record a continuing Plan proposal request. @sergent/docs/framework.md
|
|
251
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
252
|
+
operation_registry = self._proposals.operation_registry
|
|
253
|
+
proposal_schema = self._proposals.plan_schema
|
|
254
|
+
if operation_registry is None or proposal_schema is None:
|
|
255
|
+
raise AssertionError("continue-flow recipe requires operation_registry")
|
|
256
|
+
request = self._recipe.build_plan_request(
|
|
257
|
+
context.base_scene, context.target, intent, mindbuf, run.model_name, proposal_schema
|
|
258
|
+
)
|
|
259
|
+
if request.proposal_schema is not proposal_schema:
|
|
260
|
+
raise AssertionError("build_plan_request must return the provided schema object")
|
|
261
|
+
run.start_model_call(request)
|
|
262
|
+
return request, operation_registry
|
|
263
|
+
|
|
264
|
+
def _process_input(
|
|
265
|
+
self,
|
|
266
|
+
run: runtime_run.Run[SceneT],
|
|
267
|
+
source: SceneT | runtime_scene_state.SceneState[SceneT],
|
|
268
|
+
mindbuf: core_mindbuf.MindBuf,
|
|
269
|
+
observer_errors: list[core_errors.RunError],
|
|
270
|
+
) -> tuple[SceneT, core_scene.SceneIdentity, core_target.Target | None]:
|
|
271
|
+
"""Process observation and select one bounded Target. @sergent/docs/framework.md
|
|
272
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
273
|
+
run.open_step("process_input", {"observation": mindbuf.export()})
|
|
274
|
+
if isinstance(source, runtime_scene_state.SceneState):
|
|
275
|
+
base_scene, identity = source.snapshot()
|
|
276
|
+
else:
|
|
277
|
+
base_scene, identity = self._scene.clone(source), self._scene.identity(source)
|
|
278
|
+
run.begin(base_scene, identity)
|
|
279
|
+
self._advance(run, runtime_observe.Stage.STARTED, observer_errors)
|
|
280
|
+
target = self._scene.select_target(base_scene)
|
|
281
|
+
run.update_step_output(selected_target=target)
|
|
282
|
+
if target is not None:
|
|
283
|
+
run.finish_step("success")
|
|
284
|
+
return base_scene, identity, target
|
|
285
|
+
|
|
286
|
+
def _validated_intent(
|
|
287
|
+
self,
|
|
288
|
+
run: runtime_run.Run[SceneT],
|
|
289
|
+
context: _RunContext[SceneT],
|
|
290
|
+
intent_proposal: typing.Any,
|
|
291
|
+
) -> tuple[typing.Any, str]:
|
|
292
|
+
"""Derive and validate Intent before reading its flow. @sergent/docs/framework.md
|
|
293
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
294
|
+
intent = self._recipe.derive_intent(
|
|
295
|
+
context.base_scene, context.identity, context.target, intent_proposal
|
|
296
|
+
)
|
|
297
|
+
run.update_step_output(derived_intent=intent)
|
|
298
|
+
self._recipe.validate_intent(context.base_scene, context.identity, intent)
|
|
299
|
+
flow = core_intent.intent_flow(intent)
|
|
300
|
+
run.update_step_output(flow=flow)
|
|
301
|
+
return intent, flow
|
|
302
|
+
|
|
303
|
+
def _intent_stop(
|
|
304
|
+
self,
|
|
305
|
+
run: runtime_run.Run[SceneT],
|
|
306
|
+
context: _RunContext[SceneT],
|
|
307
|
+
intent: typing.Any,
|
|
308
|
+
) -> core_result.SergentResult[SceneT]:
|
|
309
|
+
"""Return unchanged success for a stop Intent. @sergent/docs/execution-model.md
|
|
310
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
311
|
+
terminal_message = core_intent.intent_terminal_message(intent)
|
|
312
|
+
terminal_metadata = core_intent.intent_terminal_metadata(intent)
|
|
313
|
+
run.finish_step("success")
|
|
314
|
+
return run.success_without_patch(
|
|
315
|
+
context.base_scene,
|
|
316
|
+
context.identity,
|
|
317
|
+
terminal_message=terminal_message,
|
|
318
|
+
terminal_metadata=terminal_metadata,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
def _validated_plan(
|
|
322
|
+
self,
|
|
323
|
+
run: runtime_run.Run[SceneT],
|
|
324
|
+
context: _RunContext[SceneT],
|
|
325
|
+
intent: typing.Any,
|
|
326
|
+
plan_proposal: core_plan.PlanProposal,
|
|
327
|
+
) -> core_plan.ExecutionPlan:
|
|
328
|
+
"""Derive and validate ExecutionPlan and Operations once. @sergent/docs/framework.md
|
|
329
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
330
|
+
plan = self._recipe.derive_plan(
|
|
331
|
+
context.base_scene,
|
|
332
|
+
context.identity,
|
|
333
|
+
context.target,
|
|
334
|
+
intent,
|
|
335
|
+
plan_proposal,
|
|
336
|
+
)
|
|
337
|
+
run.update_step_output(derived_execution_plan=plan)
|
|
338
|
+
runtime_deterministic._validate_operations(
|
|
339
|
+
self._scene,
|
|
340
|
+
context.base_scene,
|
|
341
|
+
intent,
|
|
342
|
+
context.target,
|
|
343
|
+
plan.steps,
|
|
344
|
+
)
|
|
345
|
+
self._recipe.validate_plan(
|
|
346
|
+
context.base_scene, context.identity, context.target, intent, plan
|
|
347
|
+
)
|
|
348
|
+
run.finish_step("success")
|
|
349
|
+
return plan
|
|
350
|
+
|
|
351
|
+
def _compiled_patch(
|
|
352
|
+
self,
|
|
353
|
+
run: runtime_run.Run[SceneT],
|
|
354
|
+
context: _RunContext[SceneT],
|
|
355
|
+
plan: core_plan.ExecutionPlan,
|
|
356
|
+
) -> tuple[core_patch.Patch, dict[str, object]]:
|
|
357
|
+
"""Compile and validate Patch evidence before rehearsal. @sergent/docs/execution-model.md
|
|
358
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
359
|
+
run.open_step("patch", None)
|
|
360
|
+
patch = self._recipe.compile_patch(plan)
|
|
361
|
+
patch_summary = run.patch_summary(patch)
|
|
362
|
+
run.update_step_output(compiled_patch=patch_summary)
|
|
363
|
+
try:
|
|
364
|
+
runtime_deterministic.validate_patch(
|
|
365
|
+
self._scene, context.base_scene, context.identity, context.target, patch
|
|
366
|
+
)
|
|
367
|
+
except runtime_errors.PatchValidationError:
|
|
368
|
+
run.update_step_output(patch_validation={"status": "failure"})
|
|
369
|
+
raise
|
|
370
|
+
return patch, patch_summary
|
|
371
|
+
|
|
372
|
+
def _dry_run_step(
|
|
373
|
+
self,
|
|
374
|
+
run: runtime_run.Run[SceneT],
|
|
375
|
+
context: _RunContext[SceneT],
|
|
376
|
+
patch: core_patch.Patch,
|
|
377
|
+
) -> None:
|
|
378
|
+
"""Rehearse the Patch without mutation. @sergent/docs/execution-model.md
|
|
379
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
380
|
+
after_dry_run = runtime_deterministic.dry_run(
|
|
381
|
+
self._scene, context.base_scene, context.identity, context.target, patch
|
|
382
|
+
)
|
|
383
|
+
run.update_step_output(
|
|
384
|
+
dry_run={
|
|
385
|
+
"after_identity": self._scene.identity(after_dry_run),
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
run.finish_step("success")
|
|
389
|
+
|
|
390
|
+
def _commit_step(
|
|
391
|
+
self,
|
|
392
|
+
run: runtime_run.Run[SceneT],
|
|
393
|
+
source: SceneT | runtime_scene_state.SceneState[SceneT],
|
|
394
|
+
context: _RunContext[SceneT],
|
|
395
|
+
commit_input: _CommitInput,
|
|
396
|
+
) -> core_result.SergentResult[SceneT]:
|
|
397
|
+
"""Use the selected plain or shared Scene authority. @sergent/docs/framework.md
|
|
398
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
399
|
+
run.open_step("commit", None)
|
|
400
|
+
if isinstance(source, runtime_scene_state.SceneState):
|
|
401
|
+
return self._commit_live_step(run, source, context, commit_input)
|
|
402
|
+
return self._commit_plain_step(run, context, commit_input.patch)
|
|
403
|
+
|
|
404
|
+
def _commit_live_step(
|
|
405
|
+
self,
|
|
406
|
+
run: runtime_run.Run[SceneT],
|
|
407
|
+
source: runtime_scene_state.SceneState[SceneT],
|
|
408
|
+
context: _RunContext[SceneT],
|
|
409
|
+
commit_input: _CommitInput,
|
|
410
|
+
) -> core_result.SergentResult[SceneT]:
|
|
411
|
+
"""Commit with live authority and optional Scene-owned rebase. @sergent/docs/framework.md
|
|
412
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
413
|
+
identity = context.identity
|
|
414
|
+
try:
|
|
415
|
+
result = runtime_rebase._commit_live(
|
|
416
|
+
source,
|
|
417
|
+
self._scene,
|
|
418
|
+
runtime_rebase._PatchRebaseBase(
|
|
419
|
+
base_scene=context.base_scene,
|
|
420
|
+
base_identity=identity,
|
|
421
|
+
target=context.target,
|
|
422
|
+
patch=commit_input.patch,
|
|
423
|
+
),
|
|
424
|
+
commit_input.patch_summary,
|
|
425
|
+
commit_input.intent,
|
|
426
|
+
)
|
|
427
|
+
except runtime_errors.PatchValidationError as exc:
|
|
428
|
+
run.finish_step("failure", error=run.error_record(exc))
|
|
429
|
+
raise
|
|
430
|
+
run.update_step_output(
|
|
431
|
+
commit_kind=result.kind,
|
|
432
|
+
metadata=result.metadata,
|
|
433
|
+
)
|
|
434
|
+
run.finish_step("success")
|
|
435
|
+
return run.success(
|
|
436
|
+
result.scene,
|
|
437
|
+
result.identity,
|
|
438
|
+
terminal_metadata=result.metadata,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
def _commit_plain_step(
|
|
442
|
+
self,
|
|
443
|
+
run: runtime_run.Run[SceneT],
|
|
444
|
+
context: _RunContext[SceneT],
|
|
445
|
+
patch: core_patch.Patch,
|
|
446
|
+
) -> core_result.SergentResult[SceneT]:
|
|
447
|
+
"""Commit against the isolated plain Scene snapshot. @sergent/docs/framework.md
|
|
448
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
449
|
+
identity = context.identity
|
|
450
|
+
committed = runtime_deterministic.commit(
|
|
451
|
+
context.base_scene, context.target, patch, self._scene
|
|
452
|
+
)
|
|
453
|
+
identity_after = core_scene.SceneIdentity(
|
|
454
|
+
scene_id=identity.scene_id,
|
|
455
|
+
revision=identity.revision + 1,
|
|
456
|
+
)
|
|
457
|
+
run.update_step_output(
|
|
458
|
+
commit_kind="plain",
|
|
459
|
+
metadata={},
|
|
460
|
+
)
|
|
461
|
+
run.finish_step("success")
|
|
462
|
+
return run.success(committed, identity_after)
|
|
463
|
+
|
|
464
|
+
def _advance(
|
|
465
|
+
self,
|
|
466
|
+
run: runtime_run.Run[SceneT],
|
|
467
|
+
stage: runtime_observe.Stage,
|
|
468
|
+
observer_errors: list[core_errors.RunError],
|
|
469
|
+
) -> None:
|
|
470
|
+
"""Advance and deliver sanitized progress. @sergent/docs/execution-model.md
|
|
471
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
472
|
+
run.advance(stage)
|
|
473
|
+
self._deliver("progress", run.snapshot(), run.stage, observer_errors)
|
|
474
|
+
|
|
475
|
+
def _deliver(
|
|
476
|
+
self,
|
|
477
|
+
callback: typing.Literal["progress", "finished"],
|
|
478
|
+
value: object,
|
|
479
|
+
stage: str,
|
|
480
|
+
observer_errors: list[core_errors.RunError],
|
|
481
|
+
) -> None:
|
|
482
|
+
"""Deliver every observer slot and contain ordinary failures.
|
|
483
|
+
@sergent/docs/execution-model.md
|
|
484
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
485
|
+
for observer in self._observers:
|
|
486
|
+
try:
|
|
487
|
+
getattr(observer, callback)(value)
|
|
488
|
+
except (asyncio.CancelledError, Exception) as exc:
|
|
489
|
+
error = runtime_observe._observer_error(callback, observer, exc, stage)
|
|
490
|
+
observer_errors.append(error)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Define structured runtime execution failures. @sergent/docs/execution-model.md
|
|
2
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _ProposalSchemaError(ValueError):
|
|
8
|
+
"""Report a failed typed proposal crossing. @sergent/docs/trust-boundaries.md
|
|
9
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _OperationAdmissibilityError(ValueError):
|
|
13
|
+
"""Report an Operation rejected for its bounded context. @sergent/docs/execution-model.md
|
|
14
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, message: str, metadata: dict[str, object]) -> None:
|
|
17
|
+
super().__init__(message)
|
|
18
|
+
self.metadata = dict(metadata)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PatchValidationError(ValueError):
|
|
22
|
+
"""Reject a compiled Patch with an unsafe envelope. @sergent/docs/framework.md
|
|
23
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
message: str,
|
|
28
|
+
*,
|
|
29
|
+
metadata: dict[str, object] | None = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.metadata = dict(metadata or {})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class StalePatchError(PatchValidationError):
|
|
36
|
+
"""Reject a Patch whose base revision is stale. @sergent/docs/framework.md
|
|
37
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
message: str,
|
|
42
|
+
*,
|
|
43
|
+
base_revision: int,
|
|
44
|
+
current_revision: int,
|
|
45
|
+
) -> None:
|
|
46
|
+
super().__init__(message)
|
|
47
|
+
self.base_revision = base_revision
|
|
48
|
+
self.current_revision = current_revision
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DryRunError(ValueError):
|
|
52
|
+
"""Reject a Patch rehearsal that fails Scene verification. @sergent/docs/execution-model.md
|
|
53
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
message: str,
|
|
58
|
+
*,
|
|
59
|
+
metadata: dict[str, object],
|
|
60
|
+
) -> None:
|
|
61
|
+
super().__init__(message)
|
|
62
|
+
self.metadata = dict(metadata)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class MergeConflictError(PatchValidationError):
|
|
66
|
+
"""Report a deterministic Scene-owned rebase rejection. @sergent/docs/framework.md
|
|
67
|
+
@sergent-py-runtime/docs/KNOWLEDGE.md"""
|