orchvia 0.1.2__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.
Files changed (42) hide show
  1. orchvia-0.1.2/LICENSE +21 -0
  2. orchvia-0.1.2/PKG-INFO +378 -0
  3. orchvia-0.1.2/README.md +355 -0
  4. orchvia-0.1.2/pyproject.toml +36 -0
  5. orchvia-0.1.2/setup.cfg +4 -0
  6. orchvia-0.1.2/src/orchvia/__init__.py +9 -0
  7. orchvia-0.1.2/src/orchvia/_version.py +2 -0
  8. orchvia-0.1.2/src/orchvia/client.py +710 -0
  9. orchvia-0.1.2/src/orchvia/errors.py +26 -0
  10. orchvia-0.1.2/src/orchvia/identity.py +33 -0
  11. orchvia-0.1.2/src/orchvia/protocol.schema.json +3091 -0
  12. orchvia-0.1.2/src/orchvia/py.typed +0 -0
  13. orchvia-0.1.2/src/orchvia/routing.py +817 -0
  14. orchvia-0.1.2/src/orchvia/transport.py +212 -0
  15. orchvia-0.1.2/src/orchvia/types.py +205 -0
  16. orchvia-0.1.2/src/orchvia/wire.py +123 -0
  17. orchvia-0.1.2/src/orchvia/wire_types.py +690 -0
  18. orchvia-0.1.2/src/orchvia.egg-info/PKG-INFO +378 -0
  19. orchvia-0.1.2/src/orchvia.egg-info/SOURCES.txt +40 -0
  20. orchvia-0.1.2/src/orchvia.egg-info/dependency_links.txt +1 -0
  21. orchvia-0.1.2/src/orchvia.egg-info/top_level.txt +1 -0
  22. orchvia-0.1.2/tests/test_audit_corrections.py +96 -0
  23. orchvia-0.1.2/tests/test_claude_cleanup_reconcile.py +181 -0
  24. orchvia-0.1.2/tests/test_context_check.py +181 -0
  25. orchvia-0.1.2/tests/test_execution_isolation.py +102 -0
  26. orchvia-0.1.2/tests/test_fork_model.py +94 -0
  27. orchvia-0.1.2/tests/test_host_start_errors.py +159 -0
  28. orchvia-0.1.2/tests/test_host_workflow.py +117 -0
  29. orchvia-0.1.2/tests/test_lifecycle.py +144 -0
  30. orchvia-0.1.2/tests/test_naming.py +24 -0
  31. orchvia-0.1.2/tests/test_node_e2e.py +179 -0
  32. orchvia-0.1.2/tests/test_node_execution_isolation.py +234 -0
  33. orchvia-0.1.2/tests/test_node_reconcile.py +173 -0
  34. orchvia-0.1.2/tests/test_queue_waits.py +44 -0
  35. orchvia-0.1.2/tests/test_reconcile.py +101 -0
  36. orchvia-0.1.2/tests/test_routing.py +365 -0
  37. orchvia-0.1.2/tests/test_routing_corrections.py +393 -0
  38. orchvia-0.1.2/tests/test_sdk.py +216 -0
  39. orchvia-0.1.2/tests/test_session_recovery.py +63 -0
  40. orchvia-0.1.2/tests/test_storage_degraded.py +32 -0
  41. orchvia-0.1.2/tests/test_store_namespaces.py +53 -0
  42. orchvia-0.1.2/tests/test_transport_parsing.py +76 -0
orchvia-0.1.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 masonlee39
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.
orchvia-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,378 @@
1
+ Metadata-Version: 2.4
2
+ Name: orchvia
3
+ Version: 0.1.2
4
+ Summary: Python SDK for Orchvia: run Claude Code and Codex agents as a team
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/masonlee39/orchvia
7
+ Project-URL: Documentation, https://github.com/masonlee39/orchvia/tree/main/docs
8
+ Project-URL: Repository, https://github.com/masonlee39/orchvia
9
+ Project-URL: Issues, https://github.com/masonlee39/orchvia/issues
10
+ Keywords: orchvia,multi-agent,claude-code,codex,orchestration,agents
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # orchvia: Python SDK
25
+
26
+ The Python SDK for [Orchvia](https://github.com/masonlee39/orchvia), which runs Claude Code and Codex agents as a team from your own application: warm sessions that keep their history, a durable mailbox, human approval of results, and per-task token records.
27
+
28
+ ```sh
29
+ pip install orchvia
30
+ npm install @orchvia/cli @orchvia/engine @orchvia/adapter-claude
31
+ ```
32
+
33
+ The SDK uses only the Python standard library and supports Python 3.11+. It talks to one Node host (Node.js 22.18+ from `@orchvia/cli`), which owns the scheduler, the SQLite state and the agent runtimes; Python starts that host as a child process or connects to a running one. Python does not implement another scheduler, open the database, or call model APIs.
34
+
35
+ With the npm packages installed in the current directory, the host program is `node_modules/@orchvia/cli/dist/main.js`; pass it as `cli_source` below. Orchvia is alpha software: see its [status](https://github.com/masonlee39/orchvia/blob/main/docs/status.md) for what is and is not verified. The tests below use the explicit `fake` provider; they are not acceptance with real models.
36
+
37
+ ## Run from this checkout
38
+
39
+ From the repository root:
40
+
41
+ ```sh
42
+ PYTHONPATH=python/src python3 examples/python/fake_roundtrip.py
43
+ PYTHONPATH=python/src python3 -m unittest discover -s python/tests -v
44
+ ```
45
+
46
+ The example creates temporary, separate workspace/state directories, starts the real
47
+ Node stdio host with only the fake provider, approves exact known fixture evidence,
48
+ checks the completed task, shuts down its host, and removes its own temporary files.
49
+ It makes no model calls and does not read login credentials.
50
+
51
+ Tests include a separate Python protocol fixture and the real Node host. Unix socket
52
+ tests require permission to create local sockets. The Node integration class is skipped
53
+ only if Node or the local CLI source is unavailable; a test skip is not integration proof.
54
+
55
+ For an editable development install, use a virtual environment:
56
+
57
+ ```sh
58
+ python3 -m venv .venv
59
+ .venv/bin/python -m pip install -e ./python
60
+ ```
61
+
62
+ Setuptools is a build dependency, not a runtime dependency. This command may need access
63
+ to your configured package index for build tooling.
64
+
65
+ ## Own a local host
66
+
67
+ Use the real absolute workspace/state paths and an explicit provider in the host config.
68
+ The current local entry point accepts an argument array containing the CLI configuration:
69
+
70
+ ```python
71
+ from orchvia import Orchestrator
72
+
73
+ async with Orchestrator.local(
74
+ engine_command=[node_executable, cli_source, "host", "--stdio", "--config", config_file],
75
+ close_timeout=30.0,
76
+ ) as orch:
77
+ print(orch.info.instance_id, orch.info.store_id)
78
+ capabilities = await orch.capabilities(provider="fake")
79
+ ```
80
+
81
+ All arguments are passed without a shell. `await Orchestrator.local(...)` also starts and
82
+ returns an owner when an application wants to manage `close()` explicitly.
83
+ The host's stdout is reserved for JSON-RPC. The SDK continuously reads stdout and stderr,
84
+ retaining only the last 16 KiB of stderr bytes (`orch.stderr_tail`) without printing logs.
85
+
86
+ Host deadlines belong in the caller's JSON configuration passed by `--config`; `local()`
87
+ does not accept a `timeouts` option or rewrite `engine_command`. The optional `timeouts`
88
+ object uses `acceptanceMs=30000`, `turnMs=1800000`, `drainMs=300000`,
89
+ `interruptMs=30000`, and `reconcileMs=60000` by default. Each override must be an integer
90
+ from 1 through 86400000 milliseconds. `LifecycleTimeouts` exposes snake_case fields;
91
+ `orchvia.types.to_wire(LifecycleTimeouts(...))` produces that JSON object. These
92
+ deadlines are independent of local SDK wait timeouts and do not reset on retry/restart.
93
+ `LifecycleTimeouts().turn_ms` is therefore 1800000. This total budget starts at dispatch
94
+ and includes adapter startup, initialization and acceptance waiting. Acceptance and
95
+ ordinary output do not restart it. The effective budget is the smaller of the host
96
+ limit and any explicit provider cap; acceptance waiting also fits within the total.
97
+ The engine and adapters use the same monotonic remaining budget. Persisted UTC times
98
+ support diagnosis and recovery; cleanup has its own bounded wait.
99
+
100
+ The owner JSON configuration also accepts `limits.maxQuarantinedDispatches`, default 32,
101
+ an integer from 1 through 1024 and at least the effective `limits.maxActiveSessions`
102
+ (default 2, range 1 through 2). Clients cannot increase these limits through the SDK.
103
+ Changing a limit requires a host restart; existing records and deadlines are retained.
104
+
105
+ An owner closes via `host.shutdown`. `ShutdownIncomplete` leaves the connection alive:
106
+
107
+ ```python
108
+ from orchvia import ShutdownIncomplete
109
+
110
+ try:
111
+ await orch.close(timeout=30)
112
+ except ShutdownIncomplete as pending:
113
+ # Select drain or interrupt according to the caller's authorized shutdown policy.
114
+ await pending.client.close(
115
+ operation_id=pending.operation_id, mode="drain", timeout=30,
116
+ )
117
+ ```
118
+
119
+ An additional timeout still requires handling. Do not exit the event loop and report the
120
+ host as closed while shutdown remains incomplete. `async with` uses bounded drain and
121
+ preserves a body exception as the cause if cleanup also fails. Applications should retain
122
+ their business result/error separately when they continue cleanup outside the context.
123
+ The executable example demonstrates that pattern. The SDK installs no global signal handlers.
124
+
125
+ ## Connect to an existing local host
126
+
127
+ ```python
128
+ async with Orchestrator.connect(socket_path="/absolute/private/host.sock") as orch:
129
+ state = await orch.tasks.get(task_id)
130
+ print(state.status)
131
+ ```
132
+
133
+ Connection-mode `close()` only disconnects. It does not send host shutdown or cancel tasks.
134
+ Explicit owner shutdown options on a connected client raise `UNAUTHORIZED`.
135
+ The same connection cannot submit owner reconciliation: `sessions.reconcile` returns
136
+ `UNAUTHORIZED` for ordinary socket clients even when the host advertises the capability.
137
+ The scheduler's `get` and `get_conflict` methods are read-only and available on these
138
+ connections; `resolve_conflict` is owner-only and also returns `UNAUTHORIZED`.
139
+
140
+ ## Tasks, approvals, handles and events
141
+
142
+ ```python
143
+ from orchvia import AcceptanceSpec, RuntimeSpec, TaskSpec
144
+
145
+ task = await orch.tasks.create(
146
+ TaskSpec(
147
+ goal="Inspect the approved workspace",
148
+ runtime=RuntimeSpec(provider="fake", model="fake-model"),
149
+ acceptance=AcceptanceSpec(mode="human", criteria=["Evidence reviewed"]),
150
+ ),
151
+ idempotency_key="my-persisted-business-key",
152
+ )
153
+ ```
154
+
155
+ The returned `TaskHandle` is an attribute-access creation snapshot with an ID and
156
+ `await task.wait(timeout=...)`. Creating a task only confirms persistence. An approval
157
+ consumer must read `approval.requested`, query `approvals.get`, display current evidence,
158
+ and call `approvals.decide(approval_id, {"choice": ..., "expected_revision": ...})` using
159
+ an authorized decision. The task does not become completed just because a model returns.
160
+ `tasks.get` returns a fresh snapshot; `task.wait` returns a completed/failed/cancelled snapshot.
161
+ Paused or blocked tasks require explicit caller handling and are not successful results.
162
+
163
+ Task/session mutations and approval decisions return operation handles where specified:
164
+ `await operation.wait(timeout=...)` returns completed/noop/rejected/failed/outcome_unknown.
165
+ Use `operations.get` or `operations.lookup(method=..., scope=..., idempotency_key=...)`
166
+ to resolve a lost receipt. Mutation receipts and errors retain all three lookup fields.
167
+ `tasks.create` uses scope `local`; task changes use the task ID, session controls the session
168
+ ID, messages the recipient session ID, and approval decisions the approval ID. Explicit
169
+ business keys are required for cross-process recovery. Generated keys are also retained on
170
+ locally cancelled mutation calls.
171
+ `scheduler.resolveConflict` uses the conflict ID as its operation scope.
172
+
173
+ `events` uses bounded read-only `events.read` pages (128 by default, at most 256) and sleeps
174
+ 50 ms on empty pages. It does not generate model requests or accumulate an unbounded queue:
175
+
176
+ ```python
177
+ async for event in orch.events(task_id=task_id):
178
+ print(event.type, event.cursor)
179
+ ```
180
+
181
+ Persist both `event.cursor` and `event.store_id` when implementing restartable consumers.
182
+ For continuation, pass `after_cursor=...` and `store_id=...`; a nonzero cursor without its
183
+ store identity is rejected. Known protocol fields use snake_case. Raw usage and unknown
184
+ user dictionaries, including `operation.result`, preserve their original keys.
185
+ `Snapshot.as_dict()` returns plain data.
186
+
187
+ Cancelling a wait/iterator or receiving `TIMEOUT` only stops local waiting. Use
188
+ `tasks.cancel(task_id)` for remote cancellation. Concurrent requests are bounded to 64 and
189
+ UTF-8 JSON frames to 1 MiB. Exceeding either produces a stable error rather than a silent retry.
190
+
191
+ ## Inspect execution capacity and resource conflicts
192
+
193
+ All three scheduler methods require the complete `initialize.capabilities.executionIsolation`
194
+ contract: integer `version=1`, `resourceRelease=true`, `schedulerStatus=true`,
195
+ `ownerConflictResolution=true`, and integer `budgetVersion=2`. Python exposes this object
196
+ as `orch.info.capabilities.execution_isolation` with snake_case fields. Missing or
197
+ incompatible values raise `UNSUPPORTED_CAPABILITY` before a scheduler request is sent;
198
+ the SDK does not fall back to an older host's behavior. Lifecycle version 1 remains separate.
199
+
200
+ ```python
201
+ scheduler = await orch.scheduler.get()
202
+ print(scheduler.execution_occupied, scheduler.quarantined, scheduler.quarantine_reserved)
203
+ print(scheduler.can_dispatch, scheduler.reasons)
204
+ if scheduler.conflicts:
205
+ conflict = await orch.scheduler.get_conflict(scheduler.conflicts[0].conflict_id)
206
+ print(conflict.id, conflict.revision, conflict.dispatch_id, conflict.status)
207
+ ```
208
+
209
+ The counts are per dispatch: A (`execution_occupied`) counts held execution leases;
210
+ Q (`quarantined`) counts business outcomes still isolated; R (`quarantine_reserved`)
211
+ counts held leases not yet quarantined, including initialization and pending cleanup.
212
+ A new dispatch needs both A < `max_active_sessions` and Q + R <
213
+ `max_quarantined_dispatches`. A and Q can overlap. Confirmed execution stop and cleanup
214
+ can release A while Q remains; only final business reconciliation removes Q.
215
+
216
+ At the Q + R limit, new work is rejected with `QUARANTINE_CAPACITY_EXCEEDED` and queued
217
+ work stops dispatching. Original idempotent requests still return their receipts.
218
+ Queries, reconciliation, cancellation, approval and shutdown remain available; resuming
219
+ a saved result only to request acceptance is also allowed. Other scheduler reasons are
220
+ `EXECUTION_CAPACITY_EXHAUSTED`, `HOST_STOPPING`, `RESOURCE_CLEANUP_PENDING` and
221
+ `EXECUTION_EVIDENCE_CONFLICT`. Tolerate additional reason strings in future versions.
222
+ A/Q/R and conflict data are read in one database transaction; `can_dispatch` and
223
+ `reasons` also reflect this host's shutdown flag and in-memory cleanup records.
224
+ `RESOURCE_CLEANUP_PENDING` blocks new dispatches until the owner explicitly continues
225
+ an incomplete reconciliation cleanup, as described below. These reads do not run cleanup.
226
+ `occupants` and `conflicts` contain at most 16 examples each; check `truncated` and
227
+ `conflicts_truncated` alongside `open_conflicts`.
228
+
229
+ `sessions.get(session_id)` includes optional `execution` while an active dispatch is
230
+ associated with the session. Its `lease.status` is `held` or `released`;
231
+ `quarantined` is the independent business flag. Optional `execution.budget` reports
232
+ `policy_version=2`, `entered_at`, `acceptance_deadline_at`, `deadline_at`,
233
+ `effective_acceptance_ms`, `effective_turn_ms`, `acceptance_source` and `turn_source`.
234
+ Remaining-time callbacks are internal Node capabilities and never enter the Python wire.
235
+
236
+ To resolve a specific resource conflict, the owner supplies reviewed stop evidence and
237
+ the latest conflict revision. This example accepts evidence already checked by the caller:
238
+
239
+ ```python
240
+ async def resolve_reviewed_conflict(owner, conflict_id, evidence, business_key):
241
+ conflict = await owner.scheduler.get_conflict(conflict_id)
242
+ operation = await owner.scheduler.resolve_conflict(
243
+ conflict.id, evidence, expected_revision=conflict.revision,
244
+ idempotency_key=business_key,
245
+ )
246
+ return await operation.wait(timeout=10)
247
+ ```
248
+
249
+ Use `ReconcileEvidence(source="owner_attestation", ...)` with both `local_resources`
250
+ and `remote_execution` set to `stopped` and a review summary. Its business `side_effects`
251
+ and `outcome` may remain `unknown`. Active observation/cleanup handles, an old revision,
252
+ or insufficient stop evidence prevent resolution. The conflict ID remains usable after
253
+ the session clears its active dispatch. Conflicts survive restart and all open conflicts
254
+ must be resolved before dispatch resumes; resolution does not rewrite business outcomes
255
+ or acceptance history. Keep the same key when recovering a lost receipt.
256
+
257
+ ## Reconcile an unknown outcome as the owner
258
+
259
+ `await orch.sessions.reconcile(target, evidence, idempotency_key=...)` returns an
260
+ `OperationHandle`. It is available to the owner created by `Orchestrator.local`, after
261
+ negotiating lifecycle version 1 with `reconcile="owner-attestation"` and
262
+ `durableDeadlines=true`. Read this as `orch.info.capabilities.lifecycle.durable_deadlines`.
263
+ Missing or incompatible capability produces `UNSUPPORTED_CAPABILITY` before sending.
264
+
265
+ Use a fresh session's ID, generation, revision, active dispatch and state as the exact
266
+ target. `ReconcileEvidence` records an explicit human owner's review of local resources,
267
+ remote execution, side effects and outcome. It does not inspect upstream history for you.
268
+ Unknown business evidence keeps the dispatch isolated. In A2, both resource fields being
269
+ `stopped`, with no active observation/cleanup handle or open execution-evidence conflict,
270
+ allow a partial reconciliation to release the execution lease while `side_effects` or
271
+ `outcome` remains `unknown`. The task stays blocked, the session stays `outcome_unknown`,
272
+ the active dispatch identity and Q are retained, and no acceptance or rerun is created.
273
+ Only one stopped resource field is insufficient. A live execution or observed child
274
+ process rejects a stop claim. R04 permits a narrow exception for a sealed adapter record
275
+ whose observation ended without ever observing a process, with an exact matching target;
276
+ it does not turn an owner declaration into observed exit evidence. A complete business
277
+ attestation must also agree with recorded terminal evidence.
278
+
279
+ The completed reconciliation operation reports resource and business decisions separately.
280
+ Its result is raw JSON, so read the camelCase key exactly:
281
+
282
+ ```python
283
+ receipt = await operation.wait(timeout=10) # operation returned by sessions.reconcile
284
+ print(receipt.result["executionReleased"], receipt.result["resolved"])
285
+ ```
286
+
287
+ A resource-only reconciliation returns `executionReleased=True` and `resolved=False`.
288
+ The operation's `completed` status confirms that the declaration and any required
289
+ adapter-record cleanup were acknowledged; it does not mean the task completed.
290
+ `execution_released` is not a key in `receipt.result`.
291
+
292
+ R04 also reports `receipt.result["unobservedResourcesReconciled"]`: true only after the
293
+ unobserved adapter records have been retired and completion acknowledged. It is false
294
+ when no such cleanup was needed or while cleanup remains pending. Optional
295
+ `receipt.result["resourceCleanup"]` contains `status` (`pending` or `completed`) and
296
+ `ownerInstanceId`; the object is absent when no such cleanup was required. All of these
297
+ keys remain camelCase inside raw `result`.
298
+
299
+ If `sessions.reconcile` raises `OrchestrationError` with code
300
+ `RESOURCE_CLEANUP_INCOMPLETE`, `error.operation_id` identifies the saved operation and
301
+ `error.data["auditCommitted"]` is true. The declaration and its business/resource
302
+ decisions are already committed; this error does not roll them back. Keep the original
303
+ target, evidence and idempotency key, saved before the first request.
304
+
305
+ ```python
306
+ # operation_id was saved from error.operation_id in the exception handler.
307
+ receipt = await owner.operations.get(operation_id)
308
+ print(receipt.status, receipt.result.get("resourceCleanup"))
309
+
310
+ # Later, explicitly continue the original attempt on the same live owner.
311
+ # original_target/evidence/key are the saved original values, not a freshly read target.
312
+ operation = await owner.sessions.reconcile(
313
+ original_target, original_evidence, idempotency_key=original_key,
314
+ )
315
+ receipt = await operation.wait(timeout=10)
316
+ ```
317
+
318
+ `operations.get` and `operations.lookup` return snapshots; `OperationHandle.wait()`
319
+ only polls. They do not execute a finalizer, so waiting on a `persisted` cleanup receipt
320
+ alone will reach the local timeout. An explicit same-key reconcile retries the original
321
+ cleanup, or only acknowledges it if cleanup already ran. If it fails again, retain the
322
+ same recovery information; do not retry in a blind loop or change the key. After owner
323
+ restart, the original in-memory finalizer is unavailable: the saved operation becomes
324
+ `outcome_unknown`, and retry still reports `RESOURCE_CLEANUP_INCOMPLETE`. Neither the
325
+ absence of a scheduler blocker nor `wait()` returning that unknown status means success.
326
+
327
+ For `completed`, include the reviewed full result string (an empty string is valid; maximum
328
+ length 524288): reconciliation saves it and leaves the
329
+ task paused, and an explicit `tasks.resume` only requests acceptance again. `not_executed`
330
+ allows explicit requeueing; `failed`/`interrupted` make the original task failed.
331
+ The earlier unknown operation retains its status and gains a `resolution` reference.
332
+ Keep a stable business key and use `operations.lookup` after a lost receipt instead of
333
+ submitting a new key. The complete
334
+ [TS/Python examples](https://github.com/masonlee39/orchvia/blob/main/docs/guide.md#114-implemented-owner-attestation)
335
+ show the target and evidence mapping. [Python TDD evidence](https://github.com/masonlee39/orchvia/blob/main/docs/tdd/0003-a-python.md)
336
+ and [increment evidence](https://github.com/masonlee39/orchvia/blob/main/docs/tdd/0003-a-evidence.md) distinguish fixture verification
337
+ from unperformed real-model acceptance.
338
+
339
+ ## Host upgrade and adapter compatibility
340
+
341
+ Wire protocol is `2.0`; database schema is 3 (`orch.info.schema_version`). The host verifies a legacy recovery database and a full bundle of retained artifacts/managed native history before migrating schema 1/2. Migration failure prevents startup. Old wire 1.0 clients are rejected. Existing deadlines and unknown work are not refreshed or replayed.
342
+
343
+ Mutation receipts and transport errors expose `retry_identity` with immutable store/method/scope/key/digest. Save it before reconnecting. `await orch.refresh()` intentionally observes a new active namespace; `await orch.retry(identity, original_params)` retains the original namespace and rejects a changed payload. Read old receipts with `archives.lookup` after rollover, not a new-key resubmission.
344
+
345
+ Node adapters must advertise `executionBudget={version:2, acceptanceCapMs:..., turnCapMs:...}`.
346
+ Each cap is either `null` for no explicit provider cap or an integer from 1 through
347
+ 86400000; an omitted version or any version other than 2 makes new `tasks.create` calls
348
+ fail with `UNSUPPORTED_CAPABILITY` before task persistence or adapter execution. The
349
+ host also validates this contract before dispatch. Built-in fake, Claude and Codex
350
+ adapters implement it; explicit provider timeouts remain effective when shorter.
351
+ Adapters must consume the supplied monotonic budget and report matching execution and
352
+ cleanup evidence. Missing or unverified terminal coverage cannot release a lease merely
353
+ because an iterator ended. These adapter hooks are internal Node contracts, not new
354
+ Python `local()` arguments. Offline fixtures do not prove real-provider stop guarantees.
355
+
356
+ ## Implemented boundary
357
+
358
+ SPEC-0007 adds `await orch.usage.get_record(usage_record_id)` and durable `usage.recorded`
359
+ events. For example, inside `async for event in orch.events(store_id=saved_store,
360
+ after_cursor=saved_cursor)`, read `event.data.usage_record_id` and
361
+ `event.data.dispatch_id`, then retrieve the exact record. Persist it to the host's
362
+ outbox/ledger before advancing the checkpoint; deduplicate by `(event.store_id, record.id)`.
363
+ Raw usage retains its provider keys. Missing record IDs return `NOT_FOUND`; malformed
364
+ IDs return `VALIDATION_ERROR`. Historical usage rows are not backfilled into events.
365
+ See the [offline durable forwarding example](https://github.com/masonlee39/orchvia/blob/main/examples/typescript/usage-forwarding.ts).
366
+
367
+ Python can consume usage from an embedded TypeScript host over the existing socket.
368
+ It cannot serialize Claude native callbacks or `observeExecutionStop` into JSON configuration.
369
+ Stock CLI providers remain read-only; serializable Codex `networkAccess`/`webSearch`
370
+ settings are supported. Native tool approval remains separate from engine task acceptance.
371
+
372
+ Implemented namespaces include tasks, session open/fork/compact/rotate/stop/inspect/control/reconcile, scheduler, messages, approvals, operations, usage, costs, context estimates, storage policy/GC/pins/backups, leased state snapshots, stores rollover/import, read-only archives, capabilities/events and owner/connection lifecycle. Owner-only administration is rejected over ordinary Unix connections.
373
+
374
+ `TaskSpec` accepts dependencies, `context_plan`, `write_scope`, budgets and context estimates; `CheckAcceptanceSpec` selects owner-registered verification rules. Task acceptance and runtime permission approval have different `purpose` values. Consumers must inspect the purpose and exact target before deciding. Provider options, native callbacks and permissions remain host configuration.
375
+
376
+ Generated `orchvia.wire_types` uses camelCase wire field names. Public dataclasses/methods use snake_case. `validate_wire(definition, payload)` validates raw wire JSON against the shipped audited schema subset. Operation results, cost reports and raw native observations intentionally preserve their wire JSON keys.
377
+
378
+ Build a wheel/sdist with the root README commands and install a local wheel using `python -m pip install --no-index --no-deps /absolute/path/orchvia-0.1.0-py3-none-any.whl`. A local owner additionally needs the Node host and selected adapter; the Python package never downloads or implements an engine. See the [current wiring guide](https://github.com/masonlee39/orchvia/blob/main/docs/guide.md), [completion matrix](https://github.com/masonlee39/orchvia/blob/main/docs/specs/0009-complete-design.md#completion-matrix), and [native acceptance boundary](https://github.com/masonlee39/orchvia/blob/main/docs/acceptance/README.md).