pdl-taskmaster 2.4.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.
- pdl_taskmaster/__init__.py +6 -0
- pdl_taskmaster/contracts/AUTHORITY_MAP.json +19 -0
- pdl_taskmaster/contracts/CALIBRATION.json +7 -0
- pdl_taskmaster/contracts/CONTRACT_MANIFEST.json +231 -0
- pdl_taskmaster/contracts/EXECUTION_CONTRACT.json +615 -0
- pdl_taskmaster/contracts/IMPLEMENTATION_BOUNDARY.json +32 -0
- pdl_taskmaster/contracts/VERIFICATION_CONTRACT.json +379 -0
- pdl_taskmaster/contracts/standards/ARTIFACT_STANDARD.md +7 -0
- pdl_taskmaster/contracts/standards/AUTHORITY_STANDARD.md +17 -0
- pdl_taskmaster/contracts/standards/CONFORMANCE_STANDARD.md +15 -0
- pdl_taskmaster/contracts/standards/CONTEXT_STANDARD.md +15 -0
- pdl_taskmaster/contracts/standards/EXECUTION_STANDARD.md +14 -0
- pdl_taskmaster/contracts/standards/HANDOFF_STANDARD.md +21 -0
- pdl_taskmaster/contracts/standards/INSTALLATION_STANDARD.md +17 -0
- pdl_taskmaster/contracts/standards/PDL_STANDARD.md +19 -0
- pdl_taskmaster/contracts/standards/PROMPT_STANDARD.md +13 -0
- pdl_taskmaster/contracts/standards/PROTOCOL_STANDARD.md +17 -0
- pdl_taskmaster/contracts/standards/RESPONSE_PLAN_STANDARD.md +24 -0
- pdl_taskmaster/contracts/standards/RESULT_STANDARD.md +35 -0
- pdl_taskmaster/contracts/standards/REVIEW_STANDARD.md +31 -0
- pdl_taskmaster/contracts/standards/SEMANTIC_INPUT_STANDARD.md +15 -0
- pdl_taskmaster/contracts/standards/TASK_SEMANTICS_STANDARD.md +9 -0
- pdl_taskmaster/controller/__init__.py +0 -0
- pdl_taskmaster/controller/mechanical_controller.py +542 -0
- pdl_taskmaster/controller/schemas/activation_decision.schema.json +34 -0
- pdl_taskmaster/controller/schemas/artifact_body.schema.json +14 -0
- pdl_taskmaster/controller/schemas/artifact_review_facts.schema.json +91 -0
- pdl_taskmaster/controller/schemas/bootstrap_analysis.schema.json +60 -0
- pdl_taskmaster/controller/schemas/execution_draft.schema.json +47 -0
- pdl_taskmaster/controller/schemas/execution_input_decision.schema.json +43 -0
- pdl_taskmaster/controller/schemas/execution_outcome.schema.json +201 -0
- pdl_taskmaster/controller/schemas/plan_artifact.schema.json +14 -0
- pdl_taskmaster/controller/schemas/prompt_artifact.schema.json +63 -0
- pdl_taskmaster/controller/schemas/protocol_discussion.schema.json +8 -0
- pdl_taskmaster/controller/schemas/result_ir_repair.schema.json +140 -0
- pdl_taskmaster/eval/__init__.py +1 -0
- pdl_taskmaster/eval/build_adversarial_battery.py +351 -0
- pdl_taskmaster/eval/compare_eval_runs.py +251 -0
- pdl_taskmaster/eval/control_api_call.py +428 -0
- pdl_taskmaster/eval/export_rlcd_dataset.py +799 -0
- pdl_taskmaster/eval/fidelity_scan.py +268 -0
- pdl_taskmaster/eval/gen_fidelity_battery.py +457 -0
- pdl_taskmaster/eval/leak_scan.py +260 -0
- pdl_taskmaster/eval/rerecord_fixtures.py +139 -0
- pdl_taskmaster/eval/run_canary_shape_probe.py +229 -0
- pdl_taskmaster/eval/run_dual_gate_probe.py +404 -0
- pdl_taskmaster/eval/run_qualified_batch.py +841 -0
- pdl_taskmaster/eval/summarize_run.py +194 -0
- pdl_taskmaster/host/__init__.py +1 -0
- pdl_taskmaster/host/app.py +164 -0
- pdl_taskmaster/host/cli.py +100 -0
- pdl_taskmaster/host/repl.py +866 -0
- pdl_taskmaster/observation/__init__.py +1 -0
- pdl_taskmaster/observation/cli.py +47 -0
- pdl_taskmaster/observation/observed_session.py +219 -0
- pdl_taskmaster/observation/parsers.py +66 -0
- pdl_taskmaster/observation/records.py +58 -0
- pdl_taskmaster/observation/session.py +3 -0
- pdl_taskmaster/observation/sink.py +3 -0
- pdl_taskmaster/observation/sinks.py +20 -0
- pdl_taskmaster/providers/README.md +66 -0
- pdl_taskmaster/providers/__init__.py +1 -0
- pdl_taskmaster/providers/api_worker.py +411 -0
- pdl_taskmaster/providers/base.py +26 -0
- pdl_taskmaster/providers/codex_worker.py +328 -0
- pdl_taskmaster/providers/fixtures.py +157 -0
- pdl_taskmaster/providers/live_stub.py +66 -0
- pdl_taskmaster/providers/recorded.py +120 -0
- pdl_taskmaster/py.typed +1 -0
- pdl_taskmaster/runtime/__init__.py +0 -0
- pdl_taskmaster/runtime/context_compiler.py +151 -0
- pdl_taskmaster/runtime/model_classification.py +290 -0
- pdl_taskmaster/runtime/normative_store.py +157 -0
- pdl_taskmaster/runtime/operation_bridge.py +336 -0
- pdl_taskmaster/runtime/presentation.py +18 -0
- pdl_taskmaster/runtime/quarantine.py +91 -0
- pdl_taskmaster/runtime/result_ir.py +401 -0
- pdl_taskmaster/runtime/semantic_observer.py +40 -0
- pdl_taskmaster/runtime/session_engine.py +1168 -0
- pdl_taskmaster/runtime/standard_registry.py +55 -0
- pdl_taskmaster/runtime/wire_payloads.py +462 -0
- pdl_taskmaster/runtime/worker-bootstrap.txt +4 -0
- pdl_taskmaster/runtime/workspace.py +602 -0
- pdl_taskmaster/tracking/__init__.py +1 -0
- pdl_taskmaster/tracking/log_live_session.py +98 -0
- pdl_taskmaster/tracking/mlflow_sink.py +58 -0
- pdl_taskmaster/verify/verify_repl_baseline.py +299 -0
- pdl_taskmaster-2.4.0.dist-info/METADATA +155 -0
- pdl_taskmaster-2.4.0.dist-info/RECORD +92 -0
- pdl_taskmaster-2.4.0.dist-info/WHEEL +4 -0
- pdl_taskmaster-2.4.0.dist-info/entry_points.txt +2 -0
- pdl_taskmaster-2.4.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Observation suite for PDL-R2S."""
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _show(path: Path) -> None:
|
|
9
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
10
|
+
if not line.strip():
|
|
11
|
+
continue
|
|
12
|
+
record = json.loads(line)
|
|
13
|
+
before = record.get("controller_before") or {}
|
|
14
|
+
after = record.get("controller_after") or {}
|
|
15
|
+
before_stage = (before.get("controller_state") or {}).get("stage")
|
|
16
|
+
after_stage = (after.get("controller_state") or {}).get("stage")
|
|
17
|
+
print(f"turn={record.get('turn_index')} {before_stage} -> {after_stage}")
|
|
18
|
+
for call in record.get("calls", []):
|
|
19
|
+
parsed = call.get("parsed")
|
|
20
|
+
if call.get("parse_error"):
|
|
21
|
+
parsed_summary = f"parse_error={call['parse_error']}"
|
|
22
|
+
else:
|
|
23
|
+
parsed_summary = json.dumps(parsed, sort_keys=True, default=str)[:300]
|
|
24
|
+
print(
|
|
25
|
+
f" {call.get('operation')} prompt={call.get('prompt_sha256')[:12]} "
|
|
26
|
+
f"response={call.get('response_sha256')[:12]} parsed={parsed_summary}"
|
|
27
|
+
)
|
|
28
|
+
event_kinds = [event.get("kind") for event in record.get("events", {}).get("new", [])]
|
|
29
|
+
if event_kinds:
|
|
30
|
+
print(f" events={event_kinds}")
|
|
31
|
+
if record.get("exception"):
|
|
32
|
+
print(f" exception={record['exception']}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main() -> int:
|
|
36
|
+
parser = argparse.ArgumentParser(description="PDL-R2S observation viewer")
|
|
37
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
38
|
+
show = sub.add_parser("show", help="show recorded turns")
|
|
39
|
+
show.add_argument("--session", required=True, type=Path, help="path to session JSONL")
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
if args.command == "show":
|
|
42
|
+
_show(args.session)
|
|
43
|
+
return 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
import time
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
from pdl_taskmaster.observation.parsers import parse_operation_output
|
|
9
|
+
from pdl_taskmaster.observation.records import (
|
|
10
|
+
controller_snapshot,
|
|
11
|
+
event_delta,
|
|
12
|
+
read_events,
|
|
13
|
+
sha256_json,
|
|
14
|
+
sha256_text,
|
|
15
|
+
utcnow,
|
|
16
|
+
)
|
|
17
|
+
from pdl_taskmaster.observation.sink import JsonlSink
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ObservedSession:
|
|
21
|
+
"""Wrap SessionEngine without becoming a second semantic participant.
|
|
22
|
+
|
|
23
|
+
Parsed facts are derived after the engine returns, using a disposable
|
|
24
|
+
OperationBridge. Observer failures are recorded and never change the
|
|
25
|
+
protocol result.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
engine: Any,
|
|
31
|
+
sink: JsonlSink | None = None,
|
|
32
|
+
*,
|
|
33
|
+
run_id: str = "unset",
|
|
34
|
+
case_id: str | None = None,
|
|
35
|
+
include_bodies: bool = False,
|
|
36
|
+
worker: Any | None = None,
|
|
37
|
+
):
|
|
38
|
+
self.engine = engine
|
|
39
|
+
self.sink = sink
|
|
40
|
+
self.run_id = run_id
|
|
41
|
+
self.case_id = case_id
|
|
42
|
+
self.include_bodies = include_bodies
|
|
43
|
+
self.turn_index = 0
|
|
44
|
+
self.calls: list[dict[str, Any]] = []
|
|
45
|
+
self._worker = worker
|
|
46
|
+
self._original_model_call = engine.model_call
|
|
47
|
+
engine.model_call = self._observed_model_call
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def session_id(self) -> str:
|
|
51
|
+
if self.engine.workspace is not None:
|
|
52
|
+
return str(self.engine.workspace.metadata.get("workspace_id", "workspace"))
|
|
53
|
+
return "session"
|
|
54
|
+
|
|
55
|
+
def _observed_model_call(self, request: Any) -> str:
|
|
56
|
+
started = time.perf_counter()
|
|
57
|
+
metadata: dict[str, Any] = {}
|
|
58
|
+
if self._worker is not None:
|
|
59
|
+
result = self._worker.call(request)
|
|
60
|
+
raw_text = result.text
|
|
61
|
+
metadata = dict(result.metadata or {})
|
|
62
|
+
else:
|
|
63
|
+
raw_text = self._original_model_call(request)
|
|
64
|
+
latency_ms = (time.perf_counter() - started) * 1000.0
|
|
65
|
+
call = {
|
|
66
|
+
"operation": request.operation,
|
|
67
|
+
"workspace_stage": request.workspace_invocation.stage,
|
|
68
|
+
"workspace_invocation_id": request.workspace_invocation.invocation_id,
|
|
69
|
+
"projection_manifest_sha256": sha256_json(request.manifest),
|
|
70
|
+
"prompt_sha256": sha256_text(request.prompt),
|
|
71
|
+
"response_sha256": sha256_text(raw_text),
|
|
72
|
+
"raw_text": raw_text,
|
|
73
|
+
"response_id": metadata.get("response_id"),
|
|
74
|
+
"observed_model": metadata.get("observed_model"),
|
|
75
|
+
"usage": metadata.get("usage"),
|
|
76
|
+
"latency_ms": metadata.get("latency_ms", latency_ms),
|
|
77
|
+
"raw_meta": metadata.get("raw_meta"),
|
|
78
|
+
}
|
|
79
|
+
self.calls.append(call)
|
|
80
|
+
return raw_text
|
|
81
|
+
|
|
82
|
+
def handle_user_message(self, user_message: str) -> Any:
|
|
83
|
+
before = controller_snapshot(self.engine)
|
|
84
|
+
events_before = read_events(self.engine.workspace)
|
|
85
|
+
call_start = len(self.calls)
|
|
86
|
+
started = time.perf_counter()
|
|
87
|
+
exception: dict[str, Any] | None = None
|
|
88
|
+
response = None
|
|
89
|
+
try:
|
|
90
|
+
response = self.engine.handle_user_message(user_message)
|
|
91
|
+
except Exception as exc:
|
|
92
|
+
exception = {"type": type(exc).__name__, "message": str(exc)}
|
|
93
|
+
wall_ms = (time.perf_counter() - started) * 1000.0
|
|
94
|
+
after = controller_snapshot(self.engine)
|
|
95
|
+
events_after = read_events(self.engine.workspace)
|
|
96
|
+
new_calls = self.calls[call_start:]
|
|
97
|
+
parsed_calls: list[dict[str, Any]] = []
|
|
98
|
+
observer_analysis_errors: list[str] = []
|
|
99
|
+
for call in new_calls:
|
|
100
|
+
parsed, parse_error = parse_operation_output(
|
|
101
|
+
self.engine.repo_root,
|
|
102
|
+
call["operation"],
|
|
103
|
+
call["raw_text"],
|
|
104
|
+
)
|
|
105
|
+
if parse_error is not None:
|
|
106
|
+
observer_analysis_errors.append(
|
|
107
|
+
f"{call['operation']}:{parse_error}"
|
|
108
|
+
)
|
|
109
|
+
public_call = {
|
|
110
|
+
"operation": call["operation"],
|
|
111
|
+
"workspace_stage": call["workspace_stage"],
|
|
112
|
+
"workspace_invocation_id": call["workspace_invocation_id"],
|
|
113
|
+
"projection_manifest_sha256": call["projection_manifest_sha256"],
|
|
114
|
+
"prompt_sha256": call["prompt_sha256"],
|
|
115
|
+
"response_sha256": call["response_sha256"],
|
|
116
|
+
"response_id": call["response_id"],
|
|
117
|
+
"observed_model": call["observed_model"],
|
|
118
|
+
"usage": call["usage"],
|
|
119
|
+
"latency_ms": call["latency_ms"],
|
|
120
|
+
"raw_meta": call["raw_meta"],
|
|
121
|
+
"parsed": parsed,
|
|
122
|
+
"parse_error": parse_error,
|
|
123
|
+
}
|
|
124
|
+
if self.include_bodies:
|
|
125
|
+
public_call["raw_text"] = call["raw_text"]
|
|
126
|
+
parsed_calls.append(public_call)
|
|
127
|
+
|
|
128
|
+
response_record: dict[str, Any] = {}
|
|
129
|
+
if response is not None:
|
|
130
|
+
response_record = {
|
|
131
|
+
"bypass": bool(getattr(response, "bypass", False)),
|
|
132
|
+
"closed": bool(getattr(response, "closed", False)),
|
|
133
|
+
"text_sha256": sha256_text(response.text or "") if response.text is not None else None,
|
|
134
|
+
"text_length": len(response.text or "") if response.text is not None else 0,
|
|
135
|
+
}
|
|
136
|
+
if self.include_bodies and response.text is not None:
|
|
137
|
+
response_record["text"] = response.text
|
|
138
|
+
|
|
139
|
+
record = {
|
|
140
|
+
"schema": 1,
|
|
141
|
+
"at_utc": utcnow(),
|
|
142
|
+
"run_id": self.run_id,
|
|
143
|
+
"case_id": self.case_id,
|
|
144
|
+
"session_id": self.session_id,
|
|
145
|
+
"workspace_id": after["workspace_id"] if after else None,
|
|
146
|
+
"workspace_path": after["workspace_path"] if after else None,
|
|
147
|
+
"turn_index": self.turn_index + 1,
|
|
148
|
+
"user_message_sha256": sha256_text(user_message),
|
|
149
|
+
"controller_before": before,
|
|
150
|
+
"controller_after": after,
|
|
151
|
+
"response": response_record,
|
|
152
|
+
"calls": parsed_calls,
|
|
153
|
+
"events": event_delta(events_before, events_after),
|
|
154
|
+
"exception": exception,
|
|
155
|
+
"observer_analysis_error": observer_analysis_errors or None,
|
|
156
|
+
"wall_ms": wall_ms,
|
|
157
|
+
}
|
|
158
|
+
if self.sink is not None:
|
|
159
|
+
self.sink.record(record)
|
|
160
|
+
self.turn_index += 1
|
|
161
|
+
if exception is not None:
|
|
162
|
+
raise RuntimeError(f"{exception['type']}: {exception['message']}")
|
|
163
|
+
return response
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def restore(
|
|
167
|
+
cls,
|
|
168
|
+
candidate_repo: str | Path,
|
|
169
|
+
workspace_path: str | Path,
|
|
170
|
+
sink: JsonlSink | None = None,
|
|
171
|
+
*,
|
|
172
|
+
worker: Any | None = None,
|
|
173
|
+
run_id: str = "unset",
|
|
174
|
+
case_id: str | None = None,
|
|
175
|
+
include_bodies: bool = False,
|
|
176
|
+
**engine_kwargs: Any,
|
|
177
|
+
) -> "ObservedSession":
|
|
178
|
+
import sys
|
|
179
|
+
|
|
180
|
+
repo = str(Path(candidate_repo).resolve())
|
|
181
|
+
if repo not in sys.path:
|
|
182
|
+
sys.path.insert(0, repo)
|
|
183
|
+
from pdl_taskmaster.runtime.session_engine import SessionEngine
|
|
184
|
+
|
|
185
|
+
engine = SessionEngine.restore(repo, lambda request: "", workspace_path, **engine_kwargs)
|
|
186
|
+
return cls(engine, sink, run_id=run_id, case_id=case_id, include_bodies=include_bodies, worker=worker)
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def create(
|
|
190
|
+
cls,
|
|
191
|
+
candidate_repo: str | Path,
|
|
192
|
+
worker: Any,
|
|
193
|
+
*,
|
|
194
|
+
workspace_root: str | Path | None = None,
|
|
195
|
+
sink: JsonlSink | None = None,
|
|
196
|
+
run_id: str = "live",
|
|
197
|
+
case_id: str | None = None,
|
|
198
|
+
include_bodies: bool = False,
|
|
199
|
+
higher_priority_constraints: Any = "Obey applicable provider/platform safety, privacy, permission, and tool constraints.",
|
|
200
|
+
available_execution_tools: Any = None,
|
|
201
|
+
) -> "ObservedSession":
|
|
202
|
+
"""Create an observed session on a fresh workspace with arbitrary input."""
|
|
203
|
+
import sys
|
|
204
|
+
|
|
205
|
+
repo = str(Path(candidate_repo).resolve())
|
|
206
|
+
if repo not in sys.path:
|
|
207
|
+
sys.path.insert(0, repo)
|
|
208
|
+
from pdl_taskmaster.runtime.session_engine import SessionEngine
|
|
209
|
+
|
|
210
|
+
if workspace_root is None:
|
|
211
|
+
workspace_root = tempfile.mkdtemp(prefix="pdl-r2s-live-")
|
|
212
|
+
engine = SessionEngine(
|
|
213
|
+
repo,
|
|
214
|
+
lambda request: "",
|
|
215
|
+
higher_priority_constraints=higher_priority_constraints,
|
|
216
|
+
available_execution_tools=available_execution_tools or [],
|
|
217
|
+
workspace_root=workspace_root,
|
|
218
|
+
)
|
|
219
|
+
return cls(engine, sink, run_id=run_id, case_id=case_id, include_bodies=include_bodies, worker=worker)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, is_dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _candidate_path(candidate_repo: str | Path) -> str:
|
|
11
|
+
value = str(Path(candidate_repo).resolve())
|
|
12
|
+
if value not in sys.path:
|
|
13
|
+
sys.path.insert(0, value)
|
|
14
|
+
return value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def new_operation_bridge(candidate_repo: str | Path) -> Any:
|
|
18
|
+
_candidate_path(candidate_repo)
|
|
19
|
+
from pdl_taskmaster.runtime.operation_bridge import OperationBridge
|
|
20
|
+
|
|
21
|
+
return OperationBridge(candidate_repo)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _jsonable(value: Any) -> Any:
|
|
25
|
+
if is_dataclass(value):
|
|
26
|
+
return _jsonable(asdict(value))
|
|
27
|
+
if isinstance(value, Enum):
|
|
28
|
+
return value.value
|
|
29
|
+
if isinstance(value, dict):
|
|
30
|
+
return {str(k): _jsonable(v) for k, v in value.items()}
|
|
31
|
+
if isinstance(value, (list, tuple)):
|
|
32
|
+
return [_jsonable(v) for v in value]
|
|
33
|
+
if isinstance(value, Path):
|
|
34
|
+
return str(value)
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def operation_parsers() -> dict[str, str]:
|
|
39
|
+
return {
|
|
40
|
+
"INTERPRET_ACTIVATION": "parse_activation",
|
|
41
|
+
"DRAFT_PROMPT": "parse_prompt_draft",
|
|
42
|
+
"DRAFT_PLAN": "parse_plan_body",
|
|
43
|
+
"REVISE_PROMPT": "parse_prompt_body",
|
|
44
|
+
"REVISE_PLAN": "parse_plan_body",
|
|
45
|
+
"INTERPRET_PROMPT_REVIEW": "parse_prompt_review",
|
|
46
|
+
"INTERPRET_PLAN_REVIEW": "parse_plan_review",
|
|
47
|
+
"INTERPRET_EXECUTION_INPUT": "parse_execution_input",
|
|
48
|
+
"ANSWER_PROTOCOL_DISCUSSION": "parse_protocol_discussion",
|
|
49
|
+
"EXECUTE": "parse_execution",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def parse_operation_output(
|
|
54
|
+
candidate_repo: str | Path,
|
|
55
|
+
operation: str,
|
|
56
|
+
raw_text: str,
|
|
57
|
+
) -> tuple[Any, str | None]:
|
|
58
|
+
method = operation_parsers().get(operation)
|
|
59
|
+
if method is None:
|
|
60
|
+
return None, f"no_parser_for_operation:{operation}"
|
|
61
|
+
try:
|
|
62
|
+
bridge = new_operation_bridge(candidate_repo)
|
|
63
|
+
parser = getattr(bridge, method)
|
|
64
|
+
return _jsonable(parser(raw_text)), None
|
|
65
|
+
except Exception as exc: # observation must never alter the protocol result
|
|
66
|
+
return None, f"{type(exc).__name__}: {exc}"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def sha256_text(value: str) -> str:
|
|
11
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def sha256_json(value: Any) -> str:
|
|
15
|
+
payload = json.dumps(value, sort_keys=True, separators=(",", ":"), default=str)
|
|
16
|
+
return sha256_text(payload)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def utcnow() -> str:
|
|
20
|
+
return datetime.now(timezone.utc).isoformat()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def read_events(workspace: Any) -> list[dict[str, Any]]:
|
|
24
|
+
if workspace is None:
|
|
25
|
+
return []
|
|
26
|
+
path = Path(workspace.path) / "events" / "events.jsonl"
|
|
27
|
+
if not path.is_file():
|
|
28
|
+
return []
|
|
29
|
+
rows: list[dict[str, Any]] = []
|
|
30
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
31
|
+
if line.strip():
|
|
32
|
+
rows.append(json.loads(line))
|
|
33
|
+
return rows
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def controller_snapshot(engine: Any) -> dict[str, Any] | None:
|
|
37
|
+
if engine is None or engine.controller is None:
|
|
38
|
+
return None
|
|
39
|
+
return {
|
|
40
|
+
"controller_state": engine.controller.state.to_dict(),
|
|
41
|
+
"workspace_id": engine.workspace.metadata.get("workspace_id") if engine.workspace is not None else None,
|
|
42
|
+
"workspace_path": str(engine.workspace.path) if engine.workspace is not None else None,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def event_delta(before: list[dict[str, Any]], after: list[dict[str, Any]]) -> dict[str, Any]:
|
|
47
|
+
if len(after) >= len(before):
|
|
48
|
+
new_events = after[len(before):]
|
|
49
|
+
else:
|
|
50
|
+
new_events = after
|
|
51
|
+
return {
|
|
52
|
+
"before_count": len(before),
|
|
53
|
+
"after_count": len(after),
|
|
54
|
+
"new": [
|
|
55
|
+
{"at_utc": event.get("at_utc"), "kind": event.get("kind"), "payload": event.get("payload")}
|
|
56
|
+
for event in new_events
|
|
57
|
+
],
|
|
58
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JsonlSink:
|
|
9
|
+
def __init__(self, output_dir: str | Path, session_id: str):
|
|
10
|
+
self.output_dir = Path(output_dir)
|
|
11
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
12
|
+
self.path = self.output_dir / f"{session_id}.jsonl"
|
|
13
|
+
self._handle = self.path.open("a", encoding="utf-8", newline="\n")
|
|
14
|
+
|
|
15
|
+
def record(self, record: dict[str, Any]) -> None:
|
|
16
|
+
self._handle.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")
|
|
17
|
+
self._handle.flush()
|
|
18
|
+
|
|
19
|
+
def close(self) -> None:
|
|
20
|
+
self._handle.close()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Worker adapters
|
|
2
|
+
|
|
3
|
+
## Generic contract
|
|
4
|
+
|
|
5
|
+
`base.py` defines the generic `WorkerAdapter` contract:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
ModelRequest
|
|
9
|
+
->
|
|
10
|
+
raw model text
|
|
11
|
+
+ optional transport metadata
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The adapter knows nothing about PDL wire validity. `OperationBridge` remains
|
|
15
|
+
the candidate-side authority for wire parsing.
|
|
16
|
+
|
|
17
|
+
## Live demonstration worker
|
|
18
|
+
|
|
19
|
+
`live_stub.py` is a deterministic non-recorded worker used for live-capability
|
|
20
|
+
tests. It is labeled:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
DEVELOPMENT / LIVE DEMONSTRATION WORKER
|
|
24
|
+
NOT A QUALIFIED R2S MEASUREMENT CONDITION
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`codex_worker.py` is the live Codex-backed demonstration worker. It is also
|
|
28
|
+
labeled as a **LIVE SEMANTIC WORKER** and a development/live demonstration
|
|
29
|
+
worker; it is not a qualified R2S measurement condition and is not a coding
|
|
30
|
+
domain execution backend.
|
|
31
|
+
|
|
32
|
+
### Codex safety defaults
|
|
33
|
+
|
|
34
|
+
Normal local operation uses:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
approval mode = never
|
|
38
|
+
sandbox mode = read-only
|
|
39
|
+
bypass = OFF
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`workspace-write` is opt-in (`/sandbox workspace-write` or
|
|
43
|
+
`--worker-sandbox workspace-write`) and remains constrained to an explicit
|
|
44
|
+
session/disposable workspace root. The dangerous bypass condition is a
|
|
45
|
+
separate startup-only mode intended only for externally hardened or
|
|
46
|
+
disposable execution environments; it is never an automatic fallback and is
|
|
47
|
+
not part of the Phase 0–5 seal.
|
|
48
|
+
|
|
49
|
+
### Codex telemetry
|
|
50
|
+
|
|
51
|
+
Machine-readable `codex exec --json` events are preferred. Structured usage is
|
|
52
|
+
recorded only when the JSON explicitly provides the field, prefixed as
|
|
53
|
+
`codex_cli_*`. If only plaintext `tokens used` is available, it is recorded as
|
|
54
|
+
`codex_cli_reported_total` with `usage_source=codex_cli_plaintext` and
|
|
55
|
+
`usage_exact=false`. Parsed totals are never mapped into provider-authoritative
|
|
56
|
+
`total_tokens`, `input_tokens`, `cached_tokens`, or `reasoning_tokens`.
|
|
57
|
+
Telemetry records the Codex CLI version, requested/observed model, sandbox
|
|
58
|
+
mode, approval mode, workdir, JSON mode, response/session identifier,
|
|
59
|
+
latency, usage source, and usage exactness. Credentials are never stored.
|
|
60
|
+
|
|
61
|
+
## Replay/qualification only
|
|
62
|
+
|
|
63
|
+
`recorded.py` and `fixtures.py` are replay/qualification utilities. They are
|
|
64
|
+
not the general-purpose worker contract and may reference historical evidence,
|
|
65
|
+
case IDs, and recorded turn sequences only because they exist to make
|
|
66
|
+
deterministic replay reproducible.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Worker adapter package."""
|