oneharness-sdk 0.4.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nick DeRobertis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: oneharness-sdk
3
+ Version: 0.4.0
4
+ Summary: Typed async Python SDK for oneharness
5
+ Keywords: cli,agent,claude,codex,harness
6
+ Author: Nick DeRobertis
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Typing :: Typed
20
+ Requires-Dist: jsonschema>=4.18,<5
21
+ Requires-Dist: oneharness-cli==0.4.0
22
+ Requires-Python: >=3.9
23
+ Project-URL: Homepage, https://github.com/nickderobertis/oneharness
24
+ Project-URL: Repository, https://github.com/nickderobertis/oneharness
25
+ Description-Content-Type: text/markdown
26
+
27
+ # oneharness-sdk
28
+
29
+ Async Python access to the `oneharness` engine, generated from and validated against the Rust-owned JSON Schema contracts. The distribution is `oneharness-sdk`, the import is `oneharness_sdk`, and every release depends on the exact same `oneharness-cli` version.
30
+
31
+ ```console
32
+ pip install oneharness-sdk
33
+ ```
34
+
35
+ ```python
36
+ import asyncio
37
+
38
+ from oneharness_sdk import OneHarness
39
+
40
+
41
+ async def main() -> None:
42
+ oneharness = OneHarness()
43
+ report = await oneharness.run(
44
+ {"prompt": "Summarize this repository", "harnesses": ["codex"]}
45
+ )
46
+ print(report["results"][0]["text"])
47
+
48
+
49
+ asyncio.run(main())
50
+ ```
51
+
52
+ The complete client surface is `run`, `run_stream`, `list`, `detect`, `history`, `history_list`, and `history_watch`. `run_stream` and `history_watch` are async iterators. Every envelope is validated before it is yielded; closing or cancelling an iterator terminates its subprocess.
53
+
54
+ ```python
55
+ async for envelope in oneharness.run_stream(
56
+ {"prompt": "Inspect this repository", "harnesses": ["codex"]}
57
+ ):
58
+ if envelope["type"] == "event" and envelope["event"]["name"] == "shell":
59
+ break
60
+
61
+ async for envelope in oneharness.history_watch(
62
+ {"labels": {"graph": "release"}, "after": last_history_id}
63
+ ):
64
+ print(envelope["record"]["history_id"])
65
+ ```
66
+
67
+ Python input keys are snake case and strict: unknown fields and misspellings fail before the CLI starts. Output dictionaries preserve additive fields from newer compatible CLI versions. `history` and `history_watch` raise `HistoryNotFoundError` when a session, record, or cursor cannot be resolved.
@@ -0,0 +1,41 @@
1
+ # oneharness-sdk
2
+
3
+ Async Python access to the `oneharness` engine, generated from and validated against the Rust-owned JSON Schema contracts. The distribution is `oneharness-sdk`, the import is `oneharness_sdk`, and every release depends on the exact same `oneharness-cli` version.
4
+
5
+ ```console
6
+ pip install oneharness-sdk
7
+ ```
8
+
9
+ ```python
10
+ import asyncio
11
+
12
+ from oneharness_sdk import OneHarness
13
+
14
+
15
+ async def main() -> None:
16
+ oneharness = OneHarness()
17
+ report = await oneharness.run(
18
+ {"prompt": "Summarize this repository", "harnesses": ["codex"]}
19
+ )
20
+ print(report["results"][0]["text"])
21
+
22
+
23
+ asyncio.run(main())
24
+ ```
25
+
26
+ The complete client surface is `run`, `run_stream`, `list`, `detect`, `history`, `history_list`, and `history_watch`. `run_stream` and `history_watch` are async iterators. Every envelope is validated before it is yielded; closing or cancelling an iterator terminates its subprocess.
27
+
28
+ ```python
29
+ async for envelope in oneharness.run_stream(
30
+ {"prompt": "Inspect this repository", "harnesses": ["codex"]}
31
+ ):
32
+ if envelope["type"] == "event" and envelope["event"]["name"] == "shell":
33
+ break
34
+
35
+ async for envelope in oneharness.history_watch(
36
+ {"labels": {"graph": "release"}, "after": last_history_id}
37
+ ):
38
+ print(envelope["record"]["history_id"])
39
+ ```
40
+
41
+ Python input keys are snake case and strict: unknown fields and misspellings fail before the CLI starts. Output dictionaries preserve additive fields from newer compatible CLI versions. `history` and `history_watch` raise `HistoryNotFoundError` when a session, record, or cursor cannot be resolved.
@@ -0,0 +1,76 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.11,<0.12"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "oneharness-sdk"
7
+ version = "0.4.0"
8
+ description = "Typed async Python SDK for oneharness"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Nick DeRobertis" }]
14
+ keywords = ["cli", "agent", "claude", "codex", "harness"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = [
29
+ "jsonschema>=4.18,<5",
30
+ "oneharness-cli==0.4.0",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/nickderobertis/oneharness"
35
+ Repository = "https://github.com/nickderobertis/oneharness"
36
+
37
+ [tool.uv.build-backend]
38
+ module-name = "oneharness_sdk"
39
+ module-root = "src"
40
+
41
+ [tool.ruff]
42
+ target-version = "py39"
43
+ line-length = 100
44
+
45
+ [tool.ruff.lint]
46
+ select = ["E4", "E7", "E9", "F", "I", "UP", "B", "ASYNC", "S", "RUF"]
47
+ ignore = [
48
+ "E501", # JSON protocol fixtures are clearer as intact strings.
49
+ "UP045", # Optional keeps runtime annotations unambiguous on Python 3.9.
50
+ ]
51
+
52
+ [tool.ruff.lint.per-file-ignores]
53
+ "scripts/**" = [
54
+ "S404", # The generator deliberately invokes Cargo across the schema boundary.
55
+ "S603", # Its fixed argument vector is not built from untrusted input.
56
+ "S607", # Cargo is a required developer tool resolved from the active toolchain.
57
+ ]
58
+ "test/**" = [
59
+ "S101", # Assertions are the test contract.
60
+ "S404", # Subprocess boundaries are intentional integration coverage.
61
+ "S603", # Test commands use fixed arguments or controlled fixtures.
62
+ "S607", # Tests intentionally resolve required developer tools from PATH.
63
+ ]
64
+
65
+ [tool.mypy]
66
+ python_version = "3.9"
67
+ strict = true
68
+ warn_unreachable = true
69
+
70
+ [tool.coverage.run]
71
+ branch = true
72
+ source = ["oneharness_sdk"]
73
+
74
+ [tool.coverage.report]
75
+ fail_under = 95
76
+ show_missing = true
@@ -0,0 +1,35 @@
1
+ """Async Python SDK for the oneharness CLI."""
2
+
3
+ from ._client import OneHarness
4
+ from ._errors import ContractError, HistoryNotFoundError, OneHarnessProcessError
5
+ from ._generated_types import (
6
+ Detection,
7
+ HarnessInfo,
8
+ HistoryListOptions,
9
+ HistoryLookup,
10
+ HistoryRecord,
11
+ HistoryStreamEnvelope,
12
+ HistoryWatchOptions,
13
+ RunOptions,
14
+ RunReport,
15
+ RunStreamEnvelope,
16
+ )
17
+ from ._version import __version__
18
+
19
+ __all__ = [
20
+ "ContractError",
21
+ "Detection",
22
+ "HarnessInfo",
23
+ "HistoryListOptions",
24
+ "HistoryLookup",
25
+ "HistoryNotFoundError",
26
+ "HistoryRecord",
27
+ "HistoryStreamEnvelope",
28
+ "HistoryWatchOptions",
29
+ "OneHarness",
30
+ "OneHarnessProcessError",
31
+ "RunOptions",
32
+ "RunReport",
33
+ "RunStreamEnvelope",
34
+ "__version__",
35
+ ]
@@ -0,0 +1,332 @@
1
+ """Async subprocess client for the oneharness JSON and JSONL interfaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import builtins
7
+ import json
8
+ import os
9
+ import shutil
10
+ from collections.abc import AsyncIterator, Mapping, Sequence
11
+ from functools import cache
12
+ from pathlib import Path
13
+ from typing import Any, Optional, cast
14
+
15
+ from jsonschema import Draft202012Validator
16
+ from jsonschema.protocols import Validator
17
+
18
+ from ._errors import ContractError, HistoryNotFoundError, OneHarnessProcessError
19
+ from ._generated_types import (
20
+ Detection,
21
+ HarnessInfo,
22
+ HistoryListOptions,
23
+ HistoryLookup,
24
+ HistoryRecord,
25
+ HistoryStreamEnvelope,
26
+ HistoryWatchOptions,
27
+ RunOptions,
28
+ RunReport,
29
+ RunStreamEnvelope,
30
+ )
31
+
32
+ _STREAM_LIMIT = 16 * 1024 * 1024
33
+ _INPUT_ROOTS = {
34
+ "history_list_options",
35
+ "history_lookup",
36
+ "history_watch_options",
37
+ "run_options",
38
+ }
39
+
40
+
41
+ def _load_json(name: str) -> dict[str, Any]:
42
+ path = Path(__file__).with_name("_generated") / name
43
+ return cast("dict[str, Any]", json.loads(path.read_text(encoding="utf-8")))
44
+
45
+
46
+ _SCHEMAS = _load_json("schemas.json")
47
+ _INPUT_KEYS = cast("dict[str, dict[str, str]]", _load_json("input-keys.json"))
48
+
49
+
50
+ @cache
51
+ def _validator(root: str) -> Validator:
52
+ schema = _SCHEMAS[root]
53
+ Draft202012Validator.check_schema(schema)
54
+ return Draft202012Validator(schema)
55
+
56
+
57
+ def _validate(root: str, value: Any, label: str) -> Any:
58
+ errors = sorted(_validator(root).iter_errors(value), key=lambda error: list(error.path))
59
+ if not errors:
60
+ return value
61
+ details = []
62
+ for error in errors:
63
+ path = ".".join(str(part) for part in error.absolute_path) or "<root>"
64
+ details.append(f"{path}: {error.message}")
65
+ raise ContractError(f"{label}: {'; '.join(details)}")
66
+
67
+
68
+ def _input(root: str, value: Any, label: str) -> dict[str, Any]:
69
+ if root not in _INPUT_ROOTS: # pragma: no cover - internal programming guard
70
+ raise AssertionError(f"{root} is not an input schema")
71
+ checked = cast("Mapping[str, Any]", _validate(root, value, label))
72
+ keys = _INPUT_KEYS[root]
73
+ return {keys.get(key, key): item for key, item in checked.items()}
74
+
75
+
76
+ def _many(args: list[str], flag: str, values: Optional[Sequence[str]]) -> None:
77
+ for value in values or ():
78
+ args.extend((flag, value))
79
+
80
+
81
+ def _run_arguments(options: Mapping[str, Any], *, stream: bool) -> list[str]:
82
+ args = ["run", "--prompt", cast("str", options["prompt"]), "--compact"]
83
+ _many(args, "--harness", cast("Optional[Sequence[str]]", options.get("harnesses")))
84
+ _many(args, "--model", cast("Optional[Sequence[str]]", options.get("models")))
85
+ scalar_flags = {
86
+ "system": "--system",
87
+ "reasoning": "--reasoning",
88
+ "resume": "--resume",
89
+ "session": "--session",
90
+ "mode": "--mode",
91
+ "historyName": "--history-name",
92
+ "historyDir": "--history-dir",
93
+ }
94
+ for key, flag in scalar_flags.items():
95
+ if key in options:
96
+ args.extend((flag, cast("str", options[key])))
97
+ if options.get("fork"):
98
+ args.append("--fork")
99
+ if "timeoutSeconds" in options:
100
+ args.extend(("--timeout", str(options["timeoutSeconds"])))
101
+ if options.get("events"):
102
+ args.append("--events")
103
+ if stream:
104
+ args.append("--stream")
105
+ if options.get("history"):
106
+ args.append("--history")
107
+ for key, value in cast("Mapping[str, str]", options.get("historyLabels", {})).items():
108
+ args.extend(("--history-label", f"{key}={value}"))
109
+ for key, value in cast("Mapping[str, str]", options.get("env", {})).items():
110
+ args.extend(("--env", f"{key}={value}"))
111
+ for key, value in cast("Mapping[str, str]", options.get("bins", {})).items():
112
+ args.extend(("--bin", f"{key}={value}"))
113
+ return args
114
+
115
+
116
+ async def _terminate(process: asyncio.subprocess.Process) -> None:
117
+ if process.returncode is not None:
118
+ return
119
+ try:
120
+ process.terminate()
121
+ except ProcessLookupError: # pragma: no cover - OS race after returncode check
122
+ return
123
+ try:
124
+ await asyncio.wait_for(process.wait(), timeout=2)
125
+ except asyncio.TimeoutError: # pragma: no cover - defensive hard-kill fallback
126
+ process.kill()
127
+ await process.wait()
128
+
129
+
130
+ def _process_error(returncode: int, stderr: str, *, history: bool) -> OneHarnessProcessError:
131
+ if history and (returncode == 1 or "was not found" in stderr):
132
+ return HistoryNotFoundError(returncode, stderr)
133
+ return OneHarnessProcessError(returncode, stderr)
134
+
135
+
136
+ class OneHarness:
137
+ """Validated async access to an installed oneharness CLI."""
138
+
139
+ def __init__(
140
+ self,
141
+ *,
142
+ executable: Optional[str] = None,
143
+ executable_args: Sequence[str] = (),
144
+ env: Optional[Mapping[str, str]] = None,
145
+ ) -> None:
146
+ self._executable = executable
147
+ self._executable_args = tuple(executable_args)
148
+ self._env = dict(env or {})
149
+
150
+ def _command(self, args: Sequence[str]) -> tuple[str, ...]:
151
+ command = self._executable or os.environ.get("ONEHARNESS_BIN")
152
+ if command is None:
153
+ command = shutil.which("oneharness") or "oneharness"
154
+ return (command, *self._executable_args, *args)
155
+
156
+ async def _spawn(
157
+ self, args: Sequence[str], cwd: Optional[str] = None
158
+ ) -> asyncio.subprocess.Process:
159
+ return await asyncio.create_subprocess_exec(
160
+ *self._command(args),
161
+ cwd=cwd,
162
+ env={**os.environ, **self._env},
163
+ stdout=asyncio.subprocess.PIPE,
164
+ stderr=asyncio.subprocess.PIPE,
165
+ limit=_STREAM_LIMIT,
166
+ )
167
+
168
+ async def _invoke(
169
+ self,
170
+ args: Sequence[str],
171
+ *,
172
+ cwd: Optional[str] = None,
173
+ accept_json_on_nonzero: bool = False,
174
+ history: bool = False,
175
+ ) -> Any:
176
+ process = await self._spawn(args, cwd)
177
+ try:
178
+ stdout_bytes, stderr_bytes = await process.communicate()
179
+ except BaseException:
180
+ await _terminate(process)
181
+ raise
182
+ stderr = stderr_bytes.decode("utf-8", errors="replace")
183
+ if process.returncode != 0 and not accept_json_on_nonzero:
184
+ raise _process_error(process.returncode or 0, stderr, history=history)
185
+ try:
186
+ return json.loads(stdout_bytes)
187
+ except (UnicodeDecodeError, json.JSONDecodeError) as error:
188
+ if process.returncode != 0:
189
+ raise _process_error(process.returncode or 0, stderr, history=history) from error
190
+ raise ContractError(f"oneharness returned invalid JSON: {error}") from error
191
+
192
+ async def _stream(
193
+ self,
194
+ args: Sequence[str],
195
+ root: str,
196
+ label: str,
197
+ *,
198
+ cwd: Optional[str] = None,
199
+ history: bool = False,
200
+ ) -> AsyncIterator[dict[str, Any]]:
201
+ process = await self._spawn(args, cwd)
202
+ if process.stdout is None or process.stderr is None: # pragma: no cover - PIPE invariant
203
+ await _terminate(process)
204
+ raise RuntimeError("oneharness stream pipes were not created")
205
+ stderr_task = asyncio.create_task(process.stderr.read())
206
+ try:
207
+ while line := await process.stdout.readline():
208
+ if not line.strip():
209
+ continue
210
+ try:
211
+ value = json.loads(line)
212
+ except (UnicodeDecodeError, json.JSONDecodeError) as error:
213
+ raise ContractError(f"{label}: invalid JSON: {error}") from error
214
+ yield cast("dict[str, Any]", _validate(root, value, label))
215
+ returncode = await process.wait()
216
+ stderr = (await stderr_task).decode("utf-8", errors="replace")
217
+ if returncode != 0:
218
+ raise _process_error(returncode, stderr, history=history)
219
+ finally:
220
+ await _terminate(process)
221
+ if not stderr_task.done():
222
+ stderr_task.cancel()
223
+ await asyncio.gather(stderr_task, return_exceptions=True)
224
+
225
+ async def run(self, options: RunOptions) -> RunReport:
226
+ """Run one prompt and return a validated report."""
227
+ parsed = _input("run_options", options, "invalid oneharness run options")
228
+ value = await self._invoke(
229
+ _run_arguments(parsed, stream=False),
230
+ cwd=cast("Optional[str]", parsed.get("cwd")),
231
+ accept_json_on_nonzero=True,
232
+ )
233
+ return cast("RunReport", _validate("run_report", value, "invalid oneharness run contract"))
234
+
235
+ def run_stream(self, options: RunOptions) -> AsyncIterator[RunStreamEnvelope]:
236
+ """Yield validated action/result envelopes as the harness runs."""
237
+ parsed = _input("run_options", options, "invalid oneharness run options")
238
+ return self._stream(
239
+ _run_arguments(parsed, stream=True),
240
+ "run_stream_envelope",
241
+ "invalid oneharness run stream contract",
242
+ cwd=cast("Optional[str]", parsed.get("cwd")),
243
+ )
244
+
245
+ async def list(self) -> builtins.list[HarnessInfo]:
246
+ """Return the validated harness registry."""
247
+ value = _validate(
248
+ "list_report",
249
+ await self._invoke(("list", "--compact")),
250
+ "invalid oneharness list contract",
251
+ )
252
+ return cast("builtins.list[HarnessInfo]", value["harnesses"])
253
+
254
+ async def detect(self, harnesses: Sequence[str] = ()) -> builtins.list[Detection]:
255
+ """Probe selected harness binaries."""
256
+ if isinstance(harnesses, (str, bytes)) or not all(
257
+ isinstance(harness, str) for harness in harnesses
258
+ ):
259
+ raise ContractError("invalid oneharness detect options: harnesses must be strings")
260
+ args = ["detect", "--compact"]
261
+ _many(args, "--harness", harnesses)
262
+ value = _validate(
263
+ "detect_report",
264
+ await self._invoke(args),
265
+ "invalid oneharness detect contract",
266
+ )
267
+ return cast("builtins.list[Detection]", value["detected"])
268
+
269
+ async def history(self, lookup: HistoryLookup) -> builtins.list[HistoryRecord]:
270
+ """Resolve one history record or session."""
271
+ parsed = _input("history_lookup", lookup, "invalid oneharness history options")
272
+ args = ["history", "show", "--compact"]
273
+ if parsed.get("last") is True:
274
+ args.append("--last")
275
+ else:
276
+ args.append(cast("str", parsed["session"]))
277
+ if parsed.get("project"):
278
+ args.extend(("--project", cast("str", parsed["project"])))
279
+ if parsed.get("allProjects"):
280
+ args.append("--all-projects")
281
+ if parsed.get("historyDir"):
282
+ args.extend(("--history-dir", cast("str", parsed["historyDir"])))
283
+ value = await self._invoke(args, history=True)
284
+ return cast(
285
+ "builtins.list[HistoryRecord]",
286
+ _validate("history_records", value, "invalid oneharness history contract"),
287
+ )
288
+
289
+ async def history_list(
290
+ self, options: Optional[HistoryListOptions] = None
291
+ ) -> builtins.list[dict[str, Any]]:
292
+ """List standardized history sessions."""
293
+ parsed = _input(
294
+ "history_list_options", options or {}, "invalid oneharness history list options"
295
+ )
296
+ args = ["history", "list", "--compact"]
297
+ if parsed.get("project"):
298
+ args.extend(("--project", cast("str", parsed["project"])))
299
+ if parsed.get("allProjects"):
300
+ args.append("--all-projects")
301
+ if parsed.get("historyDir"):
302
+ args.extend(("--history-dir", cast("str", parsed["historyDir"])))
303
+ value = await self._invoke(args)
304
+ return cast(
305
+ "builtins.list[dict[str, Any]]",
306
+ _validate("history_list", value, "invalid oneharness history list contract"),
307
+ )
308
+
309
+ def history_watch(
310
+ self, options: Optional[HistoryWatchOptions] = None
311
+ ) -> AsyncIterator[HistoryStreamEnvelope]:
312
+ """Follow validated standardized history records."""
313
+ parsed = _input(
314
+ "history_watch_options", options or {}, "invalid oneharness history watch options"
315
+ )
316
+ args = ["history", "watch", "--format", "jsonl"]
317
+ if parsed.get("after"):
318
+ args.extend(("--after", cast("str", parsed["after"])))
319
+ for key, value in cast("Mapping[str, str]", parsed.get("labels", {})).items():
320
+ args.extend(("--label", f"{key}={value}"))
321
+ if parsed.get("project"):
322
+ args.extend(("--project", cast("str", parsed["project"])))
323
+ if parsed.get("allProjects"):
324
+ args.append("--all-projects")
325
+ if parsed.get("historyDir"):
326
+ args.extend(("--history-dir", cast("str", parsed["historyDir"])))
327
+ return self._stream(
328
+ args,
329
+ "history_stream_envelope",
330
+ "invalid oneharness history watch contract",
331
+ history=True,
332
+ )
@@ -0,0 +1,18 @@
1
+ """Typed public errors raised by the Python SDK."""
2
+
3
+
4
+ class ContractError(ValueError):
5
+ """A value did not match its Rust-owned SDK contract."""
6
+
7
+
8
+ class OneHarnessProcessError(RuntimeError):
9
+ """The oneharness subprocess exited unsuccessfully."""
10
+
11
+ def __init__(self, returncode: int, stderr: str) -> None:
12
+ self.returncode = returncode
13
+ self.stderr = stderr
14
+ super().__init__(f"oneharness exited {returncode}: {stderr.strip()}")
15
+
16
+
17
+ class HistoryNotFoundError(OneHarnessProcessError):
18
+ """A history session, record, or watch cursor could not be resolved."""
@@ -0,0 +1 @@
1
+ """Rust-generated Python SDK assets."""
@@ -0,0 +1,41 @@
1
+ {
2
+ "history_list_options": {
3
+ "all_projects": "allProjects",
4
+ "history_dir": "historyDir",
5
+ "project": "project"
6
+ },
7
+ "history_lookup": {
8
+ "all_projects": "allProjects",
9
+ "history_dir": "historyDir",
10
+ "last": "last",
11
+ "project": "project",
12
+ "session": "session"
13
+ },
14
+ "history_watch_options": {
15
+ "after": "after",
16
+ "all_projects": "allProjects",
17
+ "history_dir": "historyDir",
18
+ "labels": "labels",
19
+ "project": "project"
20
+ },
21
+ "run_options": {
22
+ "bins": "bins",
23
+ "cwd": "cwd",
24
+ "env": "env",
25
+ "events": "events",
26
+ "fork": "fork",
27
+ "harnesses": "harnesses",
28
+ "history": "history",
29
+ "history_dir": "historyDir",
30
+ "history_labels": "historyLabels",
31
+ "history_name": "historyName",
32
+ "mode": "mode",
33
+ "models": "models",
34
+ "prompt": "prompt",
35
+ "reasoning": "reasoning",
36
+ "resume": "resume",
37
+ "session": "session",
38
+ "system": "system",
39
+ "timeout_seconds": "timeoutSeconds"
40
+ }
41
+ }