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.
- orchvia-0.1.2/LICENSE +21 -0
- orchvia-0.1.2/PKG-INFO +378 -0
- orchvia-0.1.2/README.md +355 -0
- orchvia-0.1.2/pyproject.toml +36 -0
- orchvia-0.1.2/setup.cfg +4 -0
- orchvia-0.1.2/src/orchvia/__init__.py +9 -0
- orchvia-0.1.2/src/orchvia/_version.py +2 -0
- orchvia-0.1.2/src/orchvia/client.py +710 -0
- orchvia-0.1.2/src/orchvia/errors.py +26 -0
- orchvia-0.1.2/src/orchvia/identity.py +33 -0
- orchvia-0.1.2/src/orchvia/protocol.schema.json +3091 -0
- orchvia-0.1.2/src/orchvia/py.typed +0 -0
- orchvia-0.1.2/src/orchvia/routing.py +817 -0
- orchvia-0.1.2/src/orchvia/transport.py +212 -0
- orchvia-0.1.2/src/orchvia/types.py +205 -0
- orchvia-0.1.2/src/orchvia/wire.py +123 -0
- orchvia-0.1.2/src/orchvia/wire_types.py +690 -0
- orchvia-0.1.2/src/orchvia.egg-info/PKG-INFO +378 -0
- orchvia-0.1.2/src/orchvia.egg-info/SOURCES.txt +40 -0
- orchvia-0.1.2/src/orchvia.egg-info/dependency_links.txt +1 -0
- orchvia-0.1.2/src/orchvia.egg-info/top_level.txt +1 -0
- orchvia-0.1.2/tests/test_audit_corrections.py +96 -0
- orchvia-0.1.2/tests/test_claude_cleanup_reconcile.py +181 -0
- orchvia-0.1.2/tests/test_context_check.py +181 -0
- orchvia-0.1.2/tests/test_execution_isolation.py +102 -0
- orchvia-0.1.2/tests/test_fork_model.py +94 -0
- orchvia-0.1.2/tests/test_host_start_errors.py +159 -0
- orchvia-0.1.2/tests/test_host_workflow.py +117 -0
- orchvia-0.1.2/tests/test_lifecycle.py +144 -0
- orchvia-0.1.2/tests/test_naming.py +24 -0
- orchvia-0.1.2/tests/test_node_e2e.py +179 -0
- orchvia-0.1.2/tests/test_node_execution_isolation.py +234 -0
- orchvia-0.1.2/tests/test_node_reconcile.py +173 -0
- orchvia-0.1.2/tests/test_queue_waits.py +44 -0
- orchvia-0.1.2/tests/test_reconcile.py +101 -0
- orchvia-0.1.2/tests/test_routing.py +365 -0
- orchvia-0.1.2/tests/test_routing_corrections.py +393 -0
- orchvia-0.1.2/tests/test_sdk.py +216 -0
- orchvia-0.1.2/tests/test_session_recovery.py +63 -0
- orchvia-0.1.2/tests/test_storage_degraded.py +32 -0
- orchvia-0.1.2/tests/test_store_namespaces.py +53 -0
- 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).
|