Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions PUBLIC-EXPORT-MANIFEST.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"fileCount": 815,
"fileCount": 817,
"files": [
{
"mode": "644",
Expand Down Expand Up @@ -129,7 +129,7 @@
{
"mode": "644",
"path": "README.md",
"sha256": "a518244e7ce7578446a671042d86d145e7900754ec34baf34de244a1926a1422"
"sha256": "f73e0c1859dc69be7a796253ca87d58f45ce77911ab02598cbe3fe6b0a1b9607"
},
{
"mode": "644",
Expand Down Expand Up @@ -659,7 +659,7 @@
{
"mode": "644",
"path": "runtimes/python/README.md",
"sha256": "b7a6df3d498faeeccc792faf57680c235579eb180990b236fa4e2a835d22b979"
"sha256": "38e7e9dbffba5632fdad5aab673050140d118f82baa8fc1d994721213d14ff3c"
},
{
"mode": "644",
Expand Down Expand Up @@ -796,6 +796,11 @@
"path": "runtimes/python/src/vyral_runtime/_local_experience.py",
"sha256": "c00ff042ebb73e6518b9f2e87a50c61a4b1daf6512dd6fab472403f3d794689d"
},
{
"mode": "644",
"path": "runtimes/python/src/vyral_runtime/_starter.py",
"sha256": "cd79d15b65f4cad7ab795c7616c2fba85772133ae47e11d0c3b7e839f692be16"
},
{
"mode": "644",
"path": "runtimes/python/src/vyral_runtime/_version.py",
Expand Down Expand Up @@ -974,7 +979,7 @@
{
"mode": "644",
"path": "runtimes/python/src/vyral_runtime/host/cli.py",
"sha256": "2e46667804b58fb10c9c0c6b1dfac7a2b5089be9abc6e2f4f868b6abe6cdb75f"
"sha256": "335bc7cdad92d1e33ae732f062a6a43391989a7bc6fcb2af9784bda59b078290"
},
{
"mode": "644",
Expand Down Expand Up @@ -1189,7 +1194,7 @@
{
"mode": "644",
"path": "runtimes/python/tests/test_host_cli.py",
"sha256": "f561334d308b62eac6e965ef97b7486be6cfcdc77299c2eedf12393c1fcbf3fd"
"sha256": "642ca4f2f0cda08f3a80f45238f3cde6a44e5cff1e32ea56672477d70fdb41b5"
},
{
"mode": "644",
Expand Down Expand Up @@ -1261,6 +1266,11 @@
"path": "runtimes/python/tests/test_snapshots.py",
"sha256": "013e28f4da7c5db1da6d46fee383b0320b1086363b80aac8e3dbce6d2f86fff1"
},
{
"mode": "644",
"path": "runtimes/python/tests/test_starter.py",
"sha256": "e6166b2fffad3940da2809c6fca1aee133bede29bf26445c441c4c7a825d8f1c"
},
{
"mode": "644",
"path": "runtimes/python/tests/test_trace_store.py",
Expand Down Expand Up @@ -1519,7 +1529,7 @@
{
"mode": "644",
"path": "scripts/test-python-runtime-platform-matrix.py",
"sha256": "3f8d53916d72ad2bfe25d7ba45d64baafa272d9b1f13661fc797021e2116a26f"
"sha256": "aac7fe630798498481afc5ba0ee7732ab578832a84399ebd27ffb84bd6804b7d"
},
{
"mode": "755",
Expand Down Expand Up @@ -1699,7 +1709,7 @@
{
"mode": "755",
"path": "scripts/verify-python-runtime-install.py",
"sha256": "2a105eee7f5c5b648c3160b2e5f4d3e88c710053a19b672b617454397d0ee132"
"sha256": "e58289de89a7b26a1ec86cb30413351532f6f011d736814bf43e30150f8235fe"
},
{
"mode": "755",
Expand All @@ -1709,7 +1719,7 @@
{
"mode": "644",
"path": "scripts/verify-python-runtime-platform-matrix.py",
"sha256": "479ebe8be702cdec6f2f6a2ca1cfaa8d2f67c1affacd7c0c6c93ba1fececdd4d"
"sha256": "94395166aa3f64b2015b4531aef3196409cb3c101f52fe3501b220ce9becf88a"
},
{
"mode": "644",
Expand All @@ -1724,7 +1734,7 @@
{
"mode": "755",
"path": "scripts/verify-python-runtime.sh",
"sha256": "6a0c28f9829a74e5d4adfaac6a0c6832d532a2e14826f992cd17c7cf8728cf8f"
"sha256": "cb0c27829555b34ab3f7cdd6b21bcbdf7b99b164644d56e6a82c7b33945342b2"
},
{
"mode": "755",
Expand All @@ -1749,7 +1759,7 @@
{
"mode": "644",
"path": "scripts/write-python-runtime-platform-receipt.py",
"sha256": "4610d63939fb59ece2a9747bbd70bf501139b5223aa5683a2a5c00ed039dd167"
"sha256": "591b7834a89f73aeaab8793841b062aa28634fd5d188c50444945a1ebd0b6a74"
},
{
"mode": "644",
Expand Down Expand Up @@ -4079,5 +4089,5 @@
],
"schemaVersion": 1,
"sourceDirty": false,
"treeSha256": "671ee8905302737f33c2abf43f100cf13f58a64ec49afe3f2bfa60986d29e020"
"treeSha256": "ab0c8cce92a13d4aad77ce0b80d86ac70d6e2c5ac7b341c50b36a7b4e39d93c7"
}
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,23 @@ python3 -m venv .venv
python -m pip install --editable runtimes/python
```

Run the connected local quickstart against an explicit durable directory:
Create and run the smallest user-owned application:

```bash
vyral-runtime init --path ./vyral_app.py
python ./vyral_app.py
```

The generated file uses the concise `@vyral(...)` authoring surface, prints a
durable receipt before dispatch, closes and reopens its local runtime, and then
completes the preserved run. Rerunning the file replays the same idempotent run
instead of dispatching duplicate work. The generator creates one readable
Python file, never overwrites an existing path, and keeps its state visibly
beside the file under `.vyral/starter`. After editing the intended work, the
developer increments the visible `RUN_VERSION` to admit a new idempotent run.

Run the connected retrieval-and-execution quickstart against a separate,
explicit durable directory:

```bash
vyral-runtime quickstart --root ./.vyral/quickstart
Expand All @@ -101,10 +117,12 @@ without rerunning the quickstart:
vyral-runtime inspect --root ./.vyral/quickstart
```

Add `--json` to capture machine-readable first-citation, durable-receipt,
restart-recovery, and completion timings. Clean wheel and source-distribution
qualification runs the same command in an isolated environment and enforces a
five-minute first-citation budget rather than relying on a synthetic smoke.
Add `--json` to the quickstart to capture machine-readable first-citation,
durable-receipt, restart-recovery, and completion timings. Clean wheel and
source-distribution qualification generates and runs the editable application
as well as the connected quickstart in isolated environments. It enforces a
five-minute package-install-to-useful-result budget rather than relying on a
synthetic import smoke.

The quickstart owns only the directory bearing its marker. Its reset command
refuses foreign, symbolic-link, home, filesystem-root, and working-directory
Expand Down
47 changes: 34 additions & 13 deletions runtimes/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,39 @@ pass locally; automated evidence does not substitute for independent review.
Unsupported optional providers are disclosed explicitly; the runtime does not
silently delegate embedded behavior to .NET or to the existing HTTP client.

## Local single-player quickstart
## Local single-player experience

From the repository root, install the package into an isolated environment and
run one command against an explicit durable directory:
From the repository root, install the package into an isolated environment:

```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --editable runtimes/python
```

Generate one editable application and run it:

```bash
vyral-runtime init --path ./vyral_app.py
python ./vyral_app.py
```

The generated file uses `@vyral(...)`, admits work with a stable idempotency
key, prints the durable receipt, closes the first runtime before dispatch,
reopens `.vyral/starter`, and completes the preserved run. Running the same
file again reports `replayed=true` and dispatches no duplicate work. The
generator refuses to overwrite an existing path; the result is ordinary
Python source intended to be edited or absorbed into an application. A visible
`RUN_VERSION` makes intent explicit: leave it unchanged to prove replay, then
increment it after changing the work to admit a new run.

For a connected retrieval-and-execution proof, run:

```bash
vyral-runtime quickstart --root ./.vyral/quickstart
```

The command creates and ingests a three-document corpus, returns cited hybrid
The quickstart creates and ingests a three-document corpus, returns cited hybrid
retrieval context, admits a decorated handler with a stable idempotency key,
closes the runtime before dispatch, reopens the same SQLite/filesystem state,
and completes the preserved run identity. It reports the queued receipt before
Expand All @@ -88,18 +108,19 @@ directory bearing that marker:
vyral-runtime quickstart --root ./.vyral/quickstart --reset
```

Use `--json` with either command for machine-readable output. Package
publication remains withheld, so the editable install is the supported public
pre-release path. Once a qualified wheel is published, the install command can
be replaced with `python -m pip install vyral-runtime` without changing the
quickstart or inspection commands.
Use `--json` with `init`, `quickstart`, or `inspect` for machine-readable
output. Package publication remains withheld, so the editable install is the
supported public pre-release path. Once a qualified wheel is published, the
install command can be replaced with `python -m pip install vyral-runtime`
without changing the local commands.

The quickstart JSON includes measured `firstCitationMs`, `durableReceiptMs`,
`restartRecoveryMs`, and `completedMs` milestones. Artifact qualification runs
the installed wheel and sdist through the first command, a second-process
idempotent replay, independent inspection, and marker-bounded reset. Each
supported platform cell rejects a first citation or complete command taking
more than five minutes.
the installed wheel and sdist through the generated editable application, its
idempotent rerun, the connected quickstart, a second-process quickstart replay,
independent inspection, and marker-bounded reset. Each supported platform cell
rejects either installed path if package installation through its first useful
result takes more than five minutes.

```python
from vyral_runtime import VyralRuntime
Expand Down
147 changes: 147 additions & 0 deletions runtimes/python/src/vyral_runtime/_starter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""Generate a self-contained user-owned local Vyral application."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path


_STARTER_SOURCE = '''\
from __future__ import annotations

import asyncio
from collections.abc import Mapping
import json
from pathlib import Path

from vyral_runtime import (
ExecutionRunContext,
ExecutionRunRequest,
ExecutionRunResult,
VyralRuntime,
execution_plugin,
vyral,
)


STATE_ROOT = Path(__file__).resolve().parent / ".vyral" / "starter"
PLUGIN_ID = "starter"
HANDLER_ID = "starter.hello"
# Rerun unchanged to prove idempotency. Increment only to admit new work.
RUN_VERSION = 1


@vyral(HANDLER_ID, plugin=PLUGIN_ID, max_attempts=2)
async def hello(context: ExecutionRunContext) -> ExecutionRunResult:
"""Edit this handler, increment RUN_VERSION, and rerun it durably."""

name = "world"
if isinstance(context.run.payload, Mapping):
candidate = context.run.payload.get("name")
if isinstance(candidate, str) and candidate.strip():
name = candidate.strip()
await context.record_event(
"starter.hello",
message="The user-owned Vyral handler is running.",
)
return ExecutionRunResult.succeeded_result(
{"message": f"Hello, {name}!"}
)


PLUGIN = execution_plugin(
PLUGIN_ID,
name="Starter",
version="1.0.0",
handlers=(hello,),
)


async def main() -> None:
request = ExecutionRunRequest(
HANDLER_ID,
plugin_id=PLUGIN_ID,
payload={"name": "Vyral", "runVersion": RUN_VERSION},
idempotency_key=f"starter.hello.v{RUN_VERSION}",
)

with VyralRuntime.open_local(
STATE_ROOT,
execution_plugins=(PLUGIN,),
) as runtime:
admitted = await runtime.execution.start_run(request)
print(
"Accepted receipt: "
f"run={admitted.id} status={admitted.status} "
f"replayed={str(admitted.admission_replayed).lower()}"
)

print("Closed the first runtime instance before dispatch.")
with VyralRuntime.open_local(
STATE_ROOT,
execution_plugins=(PLUGIN,),
) as runtime:
persisted = await runtime.execution.get_run(admitted.id)
if persisted is None:
raise RuntimeError("The admitted run was not recovered.")
print(f"Recovered: run={persisted.id} status={persisted.status}")
dispatched = await runtime.execution.dispatch_ready_runs(
recover_interrupted_runs=True
)
completed = await runtime.execution.get_run(admitted.id)
if completed is None or completed.status != "succeeded":
status = completed.status if completed is not None else "missing"
raise RuntimeError(f"The starter run did not succeed: {status}")
print(
"Completed: "
f"run={completed.id} status={completed.status} "
f"dispatched={dispatched} "
f"result={json.dumps(completed.result, sort_keys=True)}"
)


if __name__ == "__main__":
asyncio.run(main())
'''


@dataclass(frozen=True)
class LocalStarterResult:
created_path: Path
state_root_path: Path

def to_dict(self) -> dict[str, object]:
return {
"createdPath": str(self.created_path),
"stateRootPath": str(self.state_root_path),
"runArguments": ["python", str(self.created_path)],
}


def create_local_starter(output_path: str | Path) -> LocalStarterResult:
"""Create one editable starter without overwriting an existing path."""

requested = Path(output_path).expanduser()
if requested.suffix.casefold() != ".py":
raise ValueError("The local starter path must end with .py.")
if requested.exists() or requested.is_symlink():
raise ValueError(
f"Refusing to overwrite an existing starter path: {requested}"
)
requested.parent.mkdir(parents=True, exist_ok=True)
target = requested.absolute()
try:
with target.open("x", encoding="utf-8", newline="\n") as stream:
stream.write(_STARTER_SOURCE)
except FileExistsError as error:
raise ValueError(
f"Refusing to overwrite an existing starter path: {target}"
) from error
created = target.resolve()
return LocalStarterResult(
created_path=created,
state_root_path=created.parent / ".vyral" / "starter",
)


__all__ = ["LocalStarterResult", "create_local_starter"]
Loading
Loading