{"_id":"@danmestas/orch-executor-cf-durable-object","name":"@danmestas/orch-executor-cf-durable-object","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@danmestas/orch-executor-cf-durable-object","version":"0.1.0","description":"orch executor backend: spawns persistent Cloudflare Durable Object agents per orch proposal 0003 (executor-protocol contract).","license":"Apache-2.0","type":"module","repository":{"type":"git","url":"git+https://github.com/danmestas/orch-executor-cf-durable-object.git"},"bugs":"https://github.com/danmestas/orch-executor-cf-durable-object/issues","homepage":"https://github.com/danmestas/orch-executor-cf-durable-object#readme","bin":{"orch-executor-cf-durable-object":"bin/orch-executor-cf-durable-object"},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit && tsc -p tsconfig.build.json --noEmit","test":"node --test test/cli.test.mjs","test:wrangler":"node --test test/wrangler-dev.test.mjs","deploy":"wrangler deploy","deploy-dry-run":"wrangler deploy --dry-run","dev":"wrangler dev","version-probe":"bin/orch-executor-cf-durable-object --version","prepublishOnly":"npm run build"},"dependencies":{"yaml":"2.6.1"},"devDependencies":{"@cloudflare/workers-types":"4.20250408.0","@types/node":"22.10.7","typescript":"5.6.3","wrangler":"3.114.17"},"engines":{"node":">=18"},"_id":"@danmestas/orch-executor-cf-durable-object@0.1.0","_integrity":"sha512-0o/x7FkarJqW5RCc/ilP3Qyc/DOsicKVC5x1mAaNf/78k+rUcaeXSmyLdt/Qxi1Qzr+1Ta2Y7TRAJ5jfVnzdcg==","_nodeVersion":"24.3.0","_npmVersion":"10.8.3","shasum":"40f79a17bd891d7c607dd92f2a77e6af91b38707","dist":{"integrity":"sha512-0o/x7FkarJqW5RCc/ilP3Qyc/DOsicKVC5x1mAaNf/78k+rUcaeXSmyLdt/Qxi1Qzr+1Ta2Y7TRAJ5jfVnzdcg==","shasum":"40f79a17bd891d7c607dd92f2a77e6af91b38707","tarball":"https://registry.npmjs.org/@danmestas/orch-executor-cf-durable-object/-/orch-executor-cf-durable-object-0.1.0.tgz","fileCount":10,"unpackedSize":73863,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIH2/dWnFkQkkDUlM/WbsapnhyQT1hMaJBkL8OEh8HPqgAiEAwodSbwwCXQ2O+HKV/IBSjmDEIi4MrU0kcgC4WeGP9gg="}]},"_npmUser":{"name":"danmestas","email":"daniel.mestas@craftdesign.group"},"directories":{},"maintainers":[{"name":"danmestas","email":"daniel.mestas@craftdesign.group"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/orch-executor-cf-durable-object_0.1.0_1779641170495_0.13008287729851942"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-24T16:46:10.233Z","0.1.0":"2026-05-24T16:46:10.636Z","modified":"2026-05-24T16:46:10.910Z"},"maintainers":[{"name":"danmestas","email":"daniel.mestas@craftdesign.group"}],"description":"orch executor backend: spawns persistent Cloudflare Durable Object agents per orch proposal 0003 (executor-protocol contract).","homepage":"https://github.com/danmestas/orch-executor-cf-durable-object#readme","repository":{"type":"git","url":"git+https://github.com/danmestas/orch-executor-cf-durable-object.git"},"bugs":"https://github.com/danmestas/orch-executor-cf-durable-object/issues","license":"Apache-2.0","readme":"# orch-executor-cf-durable-object\n\nCloudflare Durable Object executor backend for [orch](https://github.com/danmestas/orch).\nSpawns a **persistent** open-agent bridge that survives across fetch requests\n— the bridge keeps its NATS-over-WebSocket connection warm so the agent stays\ndiscoverable on `$SRV.INFO.agents` for the full warm window of its DO.\n\nFor an **ephemeral** per-request bridge, see the sister repo\n[orch-executor-cf-worker](https://github.com/danmestas/orch-executor-cf-worker).\n\n## Status\n\n**Phase B — real implementation.** Per orch proposals\n[0002 (typed executor contract)][0002] and\n[0003 (extract executor backends)][0003], this repo now ships:\n\n- A Cloudflare Durable Object worker (`src/index.ts`, `src/local-sandbox.ts`)\n  that hosts the persistent open-agent bridge.\n- A Node binary (`bin/orch-executor-cf-durable-object`, source in\n  `src/cli.ts`) implementing the\n  [orch executor-protocol][protocol] — SpawnSpec on stdin, WorkerHandle on\n  stdout, exit 0/non-zero on success/failure.\n\n[0002]: https://github.com/danmestas/orch/issues/141\n[0003]: https://github.com/danmestas/orch/issues/142\n[protocol]: https://github.com/danmestas/orch/blob/main/docs/proposals/0002-typed-executor-contract.md\n\nSee [MIGRATION.md](MIGRATION.md) for the per-file move manifest from orch's\nin-tree `executors/wasm/cf-durable-object/`.\n\n## Why a separate repo\n\nPer orch proposal 0003 (Ousterhout-review-adjusted): backends with **heavyweight\ndependency footprints** (CF Worker / Durable Object) extract to sister repos\nso orch's main repo stops shipping TypeScript + wrangler + miniflare + DO\nbindings. The lightweight `tmux` backend stays in-tree — too small (~50 LoC\nbash) to justify extraction overhead.\n\n## How orch finds this backend\n\norch-spawn applies the orch#142 hybrid discovery order:\n\n1. **Env override** — `$ORCH_EXECUTOR_CF_DURABLE_OBJECT_CMD` (absolute path or\n   PATH-resolvable command). Useful when running multiple builds side-by-side\n   or testing a local checkout against a stable orch.\n2. **PATH lookup** — first `orch-executor-cf-durable-object` on `$PATH`. This\n   is the production install shape (`npm install -g …` puts it there).\n3. **In-tree fallback** — orch's vendored copy at\n   `orch/executors/wasm/cf-durable-object/` (only present before Phase C; will\n   be removed once installs cut over to this repo).\n\n## Cold-spawn vs re-attach: the differentiator\n\nThe headline difference between this backend and\n[orch-executor-cf-worker](https://github.com/danmestas/orch-executor-cf-worker):\n\n| Aspect                 | cf-worker (ephemeral)                  | cf-durable-object (persistent)              |\n| ---------------------- | -------------------------------------- | ------------------------------------------- |\n| Bridge lifetime        | per-request (~ms to seconds)           | until eviction or explicit teardown (~10m+) |\n| NATS connection        | re-opened per request                  | re-used across requests                     |\n| `$SRV.INFO.agents`     | flickers in/out                        | continuously registered                     |\n| Dominant flow          | cold spawn every time                  | **re-attach** every time after the first    |\n| Multi-turn convo state | none (caller carries it)               | preserved in-memory across turns            |\n\nRe-attach is the differentiator. Calling `orch-executor-cf-durable-object`\ntwice with the **same `SpawnSpec.name`** is idempotent — `idFromName(slug)`\nalways resolves to the same DO instance. The CLI surfaces the distinction:\n\n```\n# First call (cold spawn):\n$ cat spec.yaml | orch-executor-cf-durable-object\nspec_version: v1\nname: alpha-do\nexecutor: cf-durable-object\nstatus: ready\ncreated_at: 2026-05-24T12:00:00.000Z\nmessage: provisioned new DO instance\n...\n\n# Second call (re-attach — same WorkerHandle, no DO churn):\n$ cat spec.yaml | orch-executor-cf-durable-object\nspec_version: v1\nname: alpha-do\nexecutor: cf-durable-object\nstatus: ready\ncreated_at: 2026-05-24T12:00:00.000Z      # ← same timestamp\nmessage: re-attached to existing DO instance (provisioned at 2026-05-24T12:00:00.000Z)\n...\n```\n\nThe dispatcher MAY treat both as \"ready\" and immediately publish prompts on the\ndeclared NATS bus subjects.\n\n## Spawn contract\n\nPer [orch executor-protocol][protocol]:\n\n```\n$ orch-executor-cf-durable-object\n  stdin:  SpawnSpec YAML (v1 — see orch dist/schema/spawn-spec.v1.json)\n  stdout: WorkerHandle YAML on success (v1 — see orch dist/schema/worker-handle.v1.json)\n  stderr: human-readable diagnostics\n  exit:   0 success; non-zero failure\n            64 EX_USAGE       — stdin missing / arg shape wrong\n            65 EX_DATAERR     — SpawnSpec parse / validation failed\n            69 EX_UNAVAILABLE — DO endpoint unreachable\n```\n\nSupplementary commands:\n\n| Command                                       | Purpose                                |\n| --------------------------------------------- | -------------------------------------- |\n| `orch-executor-cf-durable-object --version`   | Backend version for orch-version probe |\n| `orch-executor-cf-durable-object --validate`  | Pre-flight check (no spawn)            |\n| `orch-executor-cf-durable-object --help`      | Usage to stderr                        |\n\n### Example: SpawnSpec input\n\n```yaml\nspec_version: v1\nname: alpha-do                      # DNS-label slug (also the DO routing key)\nagent: claude-code\nsession: alpha-do\nowner: dmestas\ncf-durable-object:\n  do_namespace: AGENT_DO            # wrangler binding name\n  do_id: alpha-do                   # idFromName(<this>) routes the DO\nenv:\n  ORCH_DO_ENDPOINT: https://orch-agent-do.example.workers.dev\n```\n\n### Example: WorkerHandle output\n\n```yaml\nspec_version: v1\nname: alpha-do\nagent: claude-code\nsession: alpha-do\ncreated_at: 2026-05-24T12:00:00.000Z\nexecutor: cf-durable-object\nid: AGENT_DO/alpha-do\nbus:\n  prompt: agents.prompt.open-agent.dmestas.alpha-do.prompt\n  status: agents.prompt.open-agent.dmestas.alpha-do.status\n  hb:     agents.prompt.open-agent.dmestas.alpha-do.hb\n  signal: agents.prompt.open-agent.dmestas.alpha-do.signal\nabort:\n  kind: do-call\n  target: https://orch-agent-do.example.workers.dev/agent/alpha-do/stop\nstatus: ready\nmessage: provisioned new DO instance\n```\n\n### Endpoint resolution\n\nThe CLI talks to whatever wrangler-deployed worker hosts the DO. Resolution\norder (first match wins):\n\n1. `SpawnSpec.env.ORCH_DO_ENDPOINT`  (per-spawn override)\n2. `$ORCH_DO_ENDPOINT`               (operator-wide default)\n3. `$ORCH_CF_DO_BASE_URL`            (legacy alias)\n4. `http://127.0.0.1:8787`           (the wrangler-dev default)\n\n## Install\n\n```bash\nnpm install -g @danmestas/orch-executor-cf-durable-object\norch-executor-cf-durable-object --version\n```\n\nFor a source checkout:\n\n```bash\ngit clone https://github.com/danmestas/orch-executor-cf-durable-object.git\ncd orch-executor-cf-durable-object\nnpm install\nnpm run build              # emits dist/cli.js\nnpm link                   # exposes the bin on PATH\n```\n\n## What this executor wraps\n\nA Cloudflare Durable Object that hosts a persistent Synadia open-agent bridge.\nPer-session routing is via `idFromName(<session>)`; every fetch for the same\nsession lands on the same DO instance, which holds the live NATS-over-WebSocket\nconnection and the in-flight `runBridge()` return value. The agent name on the\nNATS bus matches cf-worker:\n\n```\nagents.prompt.open-agent.<OPEN_AGENT_OWNER>.<session>\n```\n\nSynadia metadata advertised on the bus:\n\n| Field      | Value         |\n| ---------- | ------------- |\n| `executor` | `wasm`        |\n| `location` | `edge`        |\n| `lifetime` | `persistent`  |\n\n**Keepalive**: uses CF's storage alarm primitive (`state.storage.setAlarm`),\n**not** `setInterval`, so the bridge survives DO eviction. A 5-minute cron\ntrigger tickles each session in `OPEN_AGENT_WARM_SESSIONS` so fresh deploys\nre-warm without a manual client fetch. Cadence aligns with Synadia agent\nheartbeats.\n\n## Local development\n\n```bash\nnpm install\nnpm run dev                # wrangler dev on http://127.0.0.1:8787\n# In a second shell:\ncurl http://127.0.0.1:8787/health\ncurl -X POST http://127.0.0.1:8787/agent/demo/provision\n```\n\nThe DO worker accepts `ORCH_DO_TEST_MODE=1` to skip the real open-agent bridge.\nThat mode is what the wrangler-dev tests use — it exercises the DO state\nmachine (session persistence, alarm, teardown) without requiring the upstream\n`@synadia-ai/open-agent` and `@nats-io/transport-websockets` packages.\n\n## Tests\n\n```bash\nnpm test                   # CLI unit tests (validation + binary shim)\nnpm run test:wrangler      # wrangler-dev integration tests (cold + re-attach)\nnpm run typecheck          # tsc --noEmit for DO worker + CLI\nnpx wrangler deploy --dry-run   # validates wrangler.toml shape\n```\n\nThe wrangler-dev test exercises the headline acceptance criterion: two\nback-to-back invocations against the same DO instance must yield handles with\nthe **same `created_at` timestamp** and the second one must report\n`re-attached to existing DO instance` in its `message`.\n\n## Deployment\n\n```bash\nnpm install\nwrangler secret put NATS_WS_URL          # ws://your-hub:8080\nwrangler secret put OPENROUTER_API_KEY   # sk-or-...\nnpx wrangler deploy --dry-run            # validates config\nwrangler deploy\n```\n\nAdjust `OPEN_AGENT_OWNER` in `wrangler.toml` to your namespace (e.g. your\nGitHub username) — it scopes every NATS subject this DO publishes on.\n\nTo keep specific sessions warm via cron, set\n`OPEN_AGENT_WARM_SESSIONS = \"alpha,beta,demo\"` in `wrangler.toml` `[vars]`.\n\n## Releasing\n\nReleases are tag-driven. Pushing a tag `vX.Y.Z` triggers\n[`.github/workflows/release.yml`](.github/workflows/release.yml):\n\n1. **build** — `npm ci`, `npm run typecheck`, `npm run build`,\n   `npx wrangler deploy --dry-run`, `npm test`, and a `--version` probe\n   on the compiled bin.\n2. **publish-npm** — syncs `package.json` version from the tag (strips\n   leading `v`) and runs `npm publish --access public` with\n   `NODE_AUTH_TOKEN` sourced from the `NPM_TOKEN` secret.\n\nTo cut a release:\n\n```sh\ngit tag vX.Y.Z\ngit push --tags\n```\n\n### Manual dry-run\n\n`workflow_dispatch` lets you rehearse a publish without cutting a tag.\nThe `dry_run` input defaults to `true`, so a one-click run from the\nActions tab exercises the full pipeline (`npm publish --dry-run`) without\nuploading anything to npm. Flip `dry_run` to `false` to publish manually\nfrom a branch or arbitrary ref — useful for emergency republishes.\n\n### One-time operator setup\n\nSet the `NPM_TOKEN` secret in the repo's GitHub settings\n(Settings → Secrets and variables → Actions) with an npm automation\ntoken that has publish access to `@danmestas/orch-executor-cf-durable-object`.\nPRs already run `npm publish --dry-run` in CI\n(`npm-publish-dry-run` job in [`ci.yml`](.github/workflows/ci.yml))\nto catch packaging breakage — missing files in `files:`, broken `bin/`\nreferences — before a tag is pushed.\n\n## License\n\nApache 2.0 (matches orch).\n","readmeFilename":"README.md","_rev":"1-7cef7e968eafe62227cfabaf23de6523"}