@junghanacs/entwurf 0.41.0 → 0.41.2

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 (91) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +7 -5
  3. package/CHANGELOG.md +426 -0
  4. package/DELIVERY.md +37 -20
  5. package/README.md +83 -81
  6. package/VERIFY.md +10 -10
  7. package/demo/README.md +1 -1
  8. package/docs/acp-backend-rail.md +1 -1
  9. package/docs/external-mcp-host.md +1 -1
  10. package/docs/setup-clean-host.md +95 -29
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -11
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +6 -4
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +94 -28
  25. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
  26. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +2 -2
  27. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
  28. package/mcp/entwurf-bridge/src/index.ts +14 -11
  29. package/native/win32-x64/entwurf_win.node +0 -0
  30. package/native/win32-x64/manifest.json +2 -2
  31. package/package.json +15 -14
  32. package/pi/pi-durable/carrier/tui.js +18 -3
  33. package/pi/pi-durable/overlay/upstream-pin.json +12 -12
  34. package/pi-extensions/lib/acp/backend-adapter.ts +5 -3
  35. package/pi-extensions/lib/acp/backend.ts +12 -9
  36. package/pi-extensions/lib/entwurf-self-address.ts +13 -0
  37. package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
  38. package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
  39. package/pi-extensions/lib/entwurf-v2-surface.ts +3 -0
  40. package/pi-extensions/lib/fresh-call-composition.ts +21 -10
  41. package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
  42. package/pi-extensions/lib/meta-sender-identity.ts +4 -0
  43. package/pi-extensions/lib/mux-fresh-call.ts +6 -4
  44. package/pi-extensions/lib/native-push/register.ts +17 -2
  45. package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
  46. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
  47. package/pi-extensions/lib/remote-pair-receive.ts +27 -19
  48. package/pi-extensions/lib/remote-pair-send.ts +117 -30
  49. package/pi-extensions/meta-bridge-omp.ts +69 -7
  50. package/pi-extensions/meta-bridge-pi-durable.ts +3 -3
  51. package/run.sh +218 -49
  52. package/scripts/check-agy-sender-identity.ts +6 -4
  53. package/scripts/check-entwurf-self-address.ts +34 -3
  54. package/scripts/check-entwurf-v2-contract.ts +24 -2
  55. package/scripts/check-entwurf-v2-production.ts +45 -31
  56. package/scripts/check-gate-qualification.ts +11 -9
  57. package/scripts/check-native-push-adapter.ts +4 -0
  58. package/scripts/check-native-push-register.ts +32 -82
  59. package/scripts/check-remote-pair-installed.ts +2 -2
  60. package/scripts/dev-bin.sh +112 -1
  61. package/scripts/lib/chain-terminus-publication.ts +58 -0
  62. package/scripts/lib/codex-fresh-live-protocol.ts +33 -2
  63. package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
  64. package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
  65. package/scripts/mutants/chain-terminus-publication.json +17 -0
  66. package/scripts/mutants/codex-native.json +98 -0
  67. package/scripts/mutants/control-send-receipt.json +3 -3
  68. package/scripts/mutants/mux-fresh-call.json +16 -1
  69. package/scripts/mutants/native-push-paused.json +17 -0
  70. package/scripts/mutants/omp-fresh.json +13 -2
  71. package/scripts/mutants/pack-install.json +50 -4
  72. package/scripts/mutants/pi-durable.json +60 -0
  73. package/scripts/mutants/pi-floor.json +5 -5
  74. package/scripts/mutants/remote-pair-display.json +3 -3
  75. package/scripts/mutants/remote-pair-receive.json +41 -11
  76. package/scripts/mutants/remote-pair-roundtrip.json +67 -7
  77. package/scripts/mutants/remote-pair-send.json +156 -17
  78. package/scripts/mutants/remote-pair-transport.json +4 -4
  79. package/scripts/mutants/source-install.json +66 -0
  80. package/scripts/remote-pair-receive.ts +12 -12
  81. package/scripts/smoke-agy-install-state.sh +121 -0
  82. package/scripts/smoke-agy-native-push-live.ts +13 -0
  83. package/scripts/smoke-codex-fresh-live.ts +52 -28
  84. package/scripts/smoke-entwurf-chain-live.ts +7 -9
  85. package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
  86. package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
  87. package/scripts/smoke-herdr-raw-install-live.sh +10 -3
  88. package/scripts/smoke-mux-lifecycle-live.ts +140 -8
  89. package/scripts/smoke-omp-fresh-live.ts +5 -4
  90. package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
  91. package/scripts/smoke-setup-verdict.sh +27 -2
@@ -23,18 +23,22 @@ evidence split, and its three states must not collapse into "macOS is supported"
23
23
 
24
24
  ## Requirements
25
25
 
26
+ This table is the published `@junghanacs/entwurf@0.41.1`'s, which ships the Pi 1.4.0 SDK set.
27
+ The previous package, `0.41.0`, ships the Pi 1.3.0 SDK set and requires Pi 1.3.x; the setup guide
28
+ inside that tarball is its contract.
29
+
26
30
  | Component | Requirement | Needed for |
27
31
  |---|---|---|
28
32
  | Node | **`>=24.0.0`** | package and bridge runtime |
29
33
  | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
30
34
  | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
31
35
  | entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
32
- | pi | optional-by-presence, `>=1.3.0 <1.4` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
33
- | Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus the exact 1.3.0 pi SDK set as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
36
+ | pi | optional-by-presence, `>=1.4.0 <1.5` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
37
+ | Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus the exact 1.4.0 pi SDK set as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
34
38
  | Claude Code | optional, **`>=2.1.306`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
35
39
  | GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
36
40
  | OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
37
- | Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
41
+ | Antigravity `agy` | optional, operator-installed and authenticated | legacy citizen; native push paused (#152) |
38
42
  | OpenAI Codex CLI | optional-by-presence, operator-installed and authenticated; detected composes all four operator-owned units (birth/MCP/status-line/terminal-title) and stays non-green until the operator trusts the birth declaration once in a visible Codex; visible fresh also requires the operator-owned app-server, started with `entwurf codex-app-server` in a tmux session of the operator's choosing (#95 D1 retired the fixed `codex` home) | native-push and visible fresh, supported in 0.21.0 |
39
43
  | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
40
44
 
@@ -79,11 +83,14 @@ node --version
79
83
  npm --version
80
84
  ```
81
85
 
86
+ The two npm forms below install the **published** package, `0.41.1` (Pi 1.4.x, the Pi 1.4.0 SDK
87
+ set).
88
+
82
89
  Global install is simplest when native harnesses should find stable bins from every
83
90
  working directory:
84
91
 
85
92
  ```bash
86
- npm install -g @junghanacs/entwurf
93
+ npm install -g @junghanacs/entwurf@0.41.1
87
94
  entwurf --help
88
95
  entwurf setup /path/to/your-project
89
96
  entwurf check-bridge
@@ -94,7 +101,7 @@ A project-local installation is also supported:
94
101
  ```bash
95
102
  mkdir -p ~/entwurf-smoke && cd ~/entwurf-smoke
96
103
  npm init -y
97
- npm install --save-dev @junghanacs/entwurf
104
+ npm install --save-dev @junghanacs/entwurf@0.41.1
98
105
  npx entwurf setup .
99
106
  npx entwurf check-bridge
100
107
  ```
@@ -104,7 +111,7 @@ project-local install, or `./run.sh <command>` from a source checkout. Keep that
104
111
  same scope when upgrading and checking; native process restarts are separate (§7).
105
112
 
106
113
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
107
- lists the nine garden tools (0.41.0); it does not prove a backend model turn, native hook or retirement permission.
114
+ lists the nine garden tools (0.41.1); it does not prove a backend model turn, native hook or retirement permission.
108
115
 
109
116
  Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
110
117
  Cortex, and their authentication remain operator-owned optional prerequisites for the
@@ -119,11 +126,19 @@ with a nonzero setup result):
119
126
  ```bash
120
127
  git clone https://github.com/junghan0611/entwurf ~/repos/gh/entwurf
121
128
  cd ~/repos/gh/entwurf
129
+ git checkout "$CANDIDATE_REF" # set it first to the commit you mean to run; a bare clone gives the default branch
122
130
  ./run.sh setup /path/to/consumer-project
123
131
  ```
124
132
 
133
+ Set `CANDIDATE_REF` explicitly to a published branch or tag that carries the commit you mean;
134
+ a bare clone selects the default branch, not a promised candidate. A validation branch is not a
135
+ release acceptance. A checkout that already holds the intended commit skips the clone and the
136
+ `git checkout`.
137
+
125
138
  This owns `~/.local/bin/entwurf` as a symlink to that checkout's `run.sh` and fails if the
126
- link is foreign, outside PATH, or shadowed by another command. It detects and wires
139
+ link is foreign, outside PATH, or shadowed by another command, or if another root still answers to
140
+ a package bin name in PATH or in the global bin dir `pnpm bin -g` / `npm prefix -g` report (it
141
+ removes nothing). It detects and wires
127
142
  pi/Claude/agy/Copilot/OMP/Codex by presence and prints a computed per-component
128
143
  PASS/SKIP/FAIL summary — a detected harness that cannot be completed makes setup exit nonzero.
129
144
  A detected `copilot` composes all four native units, and a detected `omp` composes its own four.
@@ -207,10 +222,12 @@ physical-Darwin receipt. `entwurf install` may write `.pi/settings.json`
207
222
  on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
208
223
  `setup` names the pi row FAIL with the §1 Darwin wording.
209
224
 
210
- Install the exact release floor, then wire the project:
225
+ Install the exact release floor, then wire the project. The previous 0.41.0 instead takes the
226
+ Pi 1.3.x its own setup guide names. A source checkout passes its checkout path to `-e` in place of
227
+ the global package root:
211
228
 
212
229
  ```bash
213
- npm install -g @earendil-works/pi-coding-agent@1.3.0
230
+ npm install -g @earendil-works/pi-coding-agent@1.4.0
214
231
  pi --version
215
232
 
216
233
  cd ~/entwurf-smoke
@@ -218,7 +235,7 @@ entwurf install .
218
235
  pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
219
236
  ```
220
237
 
221
- The supported range is `>=1.3.0 <1.4`. It is a hard minimum: installing this
238
+ The supported range is `>=1.4.0 <1.5`. It is a hard minimum: installing this
222
239
  release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
223
240
  minor. A host using only the external MCP bridge can skip pi until it needs a
224
241
  control socket; no delivery rail launches a pi process.
@@ -240,14 +257,14 @@ A plain `pi` can load the package without becoming a citizen.
240
257
  In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
241
258
  live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
242
259
  message. These are harness tools, not shell subcommands; pi exposes them through
243
- Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.41.0 bridge has nine tools;
260
+ Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.41.1 bridge has nine tools;
244
261
  pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving seven active.
245
262
  The added `entwurf_retire_call` is only for one target/action explicitly requested by the
246
263
  human operator, never autonomous cleanup. Where a host provides or is configured with an approval
247
264
  step, that step applies as well; Pi and pi-durable 1.3.0 have no built-in approval prompt by
248
265
  default. The configured Claude ACP child is also [set to run without per-call permission prompts](./acp-backend-rail.md#configured-claude-acp-permissions); ACP retirement remains unmeasured.
249
266
  Tool availability, annotations and caller identity are not a grant.
250
- These candidate counts do not claim that an older installed package or running host has rolled
267
+ These counts do not claim that an older installed package or running host has rolled
251
268
  forward; see [applying new source to a running host](#applying-new-source-to-a-running-host).
252
269
  Tool-call ingress is MCP; delivery to a pi citizen is still its record-addressed
253
270
  control address (a Unix socket; a named pipe on the experimental native-Windows Pi
@@ -322,7 +339,7 @@ by `entwurf pi` or by registering ordinary Pi's adapter.
322
339
  What the Entwurf package supplies:
323
340
 
324
341
  1. **The pi SDK set** — `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
325
- pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry}` at exactly `1.3.0`, plus
342
+ pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry}` at exactly `1.4.0`, plus
326
343
  `proper-lockfile`, as Entwurf's own production dependencies (declared once in
327
344
  `pi/pi-durable/overlay/upstream-pin.json` `sdk`). Model data rides the published
328
345
  `pi-ai`; there is no catalog copy.
@@ -335,7 +352,7 @@ What the Entwurf package supplies:
335
352
  (`pi/pi-durable/carrier-resolver.mjs`), which maps them to the published package's
336
353
  own `dist/` as installed — never through a source checkout.
337
354
 
338
- The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.3.0/packages/durable/README.md)
355
+ The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.4.0/packages/durable/README.md)
339
356
  and the [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
340
357
  (last published at v1.1.0) describe what the carrier is. Running the bare upstream app does not attach Entwurf's
341
358
  garden contact. Entwurf does not rebuild the controller/model/auth/storage system or
@@ -420,7 +437,7 @@ tree (for example an override that binds pi-durable's `pi-ai` to an older publis
420
437
  manifest field; it is a known constraint of supplying both from one package, accepted for 0.32,
421
438
  not a claim that every load path is harmless.
422
439
 
423
- The ordinary Pi CLI on PATH is a separate subject (§2, range `>=1.3.0 <1.4`): a host Pi is
440
+ The ordinary Pi CLI on PATH is a separate subject (§2, range `>=1.4.0 <1.5`): a host Pi is
424
441
  not needed to run pi-durable, and a host inside the range need not be the same copy as the
425
442
  carrier's. Native Pi owns credentials, model settings and durable SQLite sessions; keep them
426
443
  in place rather than copying credentials or migrating ordinary Pi transcripts. Native
@@ -431,6 +448,29 @@ to the app's own model resolution; the checkout contact/send/receive gates run t
431
448
  with scripted S plus native H; Pi ↔ pi-durable LIVE on this supply surface is a separate release
432
449
  proof ([#130](https://github.com/junghan0611/entwurf/issues/130)).
433
450
 
451
+ ### Moving durable sessions from the Pi 1.3 SDK to 1.4
452
+
453
+ 0.41.1 is published; moving a live host onto it remains a separate operator decision for each
454
+ host. When you do:
455
+
456
+ 1. **Confirm the old owner is gone.** Close the 0.41 (SDK 1.3) durable session normally and check
457
+ that its process has exited. A stale session lock alone is not proof.
458
+ 2. **Back up first.** Before the first 1.4 open, copy the target session directory whole
459
+ (`session.sqlite` with any `-wal`/`-shm`) as one consistent set.
460
+ 3. **Choose the session by name.** Reopen with the `--resume` picker and pick the entry you mean;
461
+ do not use `--continue` on a host with more than one session.
462
+ 4. **Package downgrade is not data rollback.** The first 1.4 open migrates SQLite storage to
463
+ schema 2, which SDK 1.3 (Entwurf 0.41) refuses to open. Reinstalling the old package does not
464
+ undo that. Restoring the backup does, and work done after the 1.4 open is not in the backup.
465
+ 5. **Inspect parked work before retrying.** On its first open, 1.4 counts a task that SDK 1.3 left
466
+ running as one restart, and after repeated cut-offs it parks the task (`restart_limit`). Read
467
+ what the task already did before you resend anything: recovery of a parked model turn is not
468
+ measured, and its side effects or outcome may be unknown. The existing conversation abort is
469
+ cancellation, not retry; it need not affect background tasks.
470
+
471
+ The measurements behind these points, and what they do not cover, are in the
472
+ [#148 checkpoint](https://github.com/junghan0611/entwurf/issues/148#issuecomment-6339048361).
473
+
434
474
  ### Maintainer: re-emitting the carrier
435
475
 
436
476
  Only a maintainer changing the pi-durable app runs `scripts/emit-pi-durable-carrier.ts`. Its
@@ -458,31 +498,45 @@ re-emitted with `--write`, and its generated digest is reviewed like any other c
458
498
 
459
499
  ## 2c. Optional remote pair between two hosts (#107)
460
500
 
461
- This lets one declared pair of existing citizens on two hosts reach each other over SSH that you already run. [DELIVERY](../DELIVERY.md#remote-pair-107-one-declared-pair-across-two-hosts) owns the contract. The machine boundary is set by a person, and nothing here is automatic.
501
+ This lets declared pairs of existing citizens on two or more hosts reach each other over SSH that you already run. Each pair is one route; one host's file may carry several (#149). [DELIVERY](../DELIVERY.md#remote-pairs-107-149-declared-pairs-across-hosts) owns the contract. The machine boundary is set by a person, and nothing here is automatic.
462
502
 
463
- 1. **Connect the hosts yourself.** Use a private network (Tailscale or similar is recommended) and SSH keys and authorization you already trust. From each host, `ssh -o BatchMode=yes <alias> true` must succeed non-interactively, and the other host's non-interactive `command -v entwurf` must resolve to the installation you intend. Entwurf edits no SSH config, key, known-hosts file or network setting. Whatever SSH itself writes under your own policy, such as host-key updates, stays yours.
503
+ 1. **Connect the hosts yourself.** Use a private network (Tailscale or similar is recommended) and SSH keys and authorization you already trust. From each host, `ssh -o BatchMode=yes <alias> true` must succeed non-interactively, and the other host's non-interactive `command -v entwurf` must resolve to the installation you intend. Entwurf edits no SSH config, key, known-hosts file or network setting. Whatever SSH itself writes under your own policy, such as host-key updates, stays yours. Everything that can log in to an account is inside that account's trust: if two other hosts' keys reach the same account, either can claim any garden id that account's file declares.
464
504
  2. **Run the accepted bytes on both hosts, in the processes that matter.** See [applying new source to a running host](#applying-new-source-to-a-running-host). A pulled checkout or a green setup line does not, by itself, show what a running bridge loaded.
465
505
  3. **Write one file on each host,** at `remotePairOptInPath()`. That is the pi agent dir unless `ENTWURF_META_SESSIONS_DIR` moves the meta-record store. Check that the sending bridge's environment and the SSH receive environment resolve the same path. Use mode `0600` and never overwrite a file you did not inspect:
466
506
 
467
507
  ```json
468
508
  {
469
- "remoteGardenId": "<the other host's garden id>",
470
- "ssh": "<your existing alias for the other host>",
471
- "localGardenId": "<this host's garden id that talks to it>",
472
- "remotePairFile": "<absolute path of the other host's own file>"
509
+ "version": 2,
510
+ "routes": [
511
+ {
512
+ "localGardenId": "<this host's garden id that talks to it>",
513
+ "remoteGardenId": "<the other host's garden id>",
514
+ "ssh": "<your existing alias for the other host>",
515
+ "remotePairFile": "<absolute path of the other host's own file>"
516
+ }
517
+ ]
473
518
  }
474
519
  ```
475
520
 
476
- The two files mirror each other: one host's `remoteGardenId` is the other's `localGardenId`. The other host's `remotePairFile` must spell this path as it is normalized, not through a symlinked alias.
521
+ Add one route per pair. One local citizen may appear in several routes, and so may one remote citizen or one alias. The file is refused if it repeats a pair, uses one garden id as both local and remote, reaches one remote garden id through two aliases or files, or gives one alias two different files. For each route, the other host's file needs the mirror route: there, this route's `remoteGardenId` is the `localGardenId`. That host's `remotePairFile` must spell this path as it is normalized, not through a symlinked alias.
477
522
 
478
523
  To check a file, read it with the repository's internal parser from a source checkout. Use the same agent/store-root settings the bridge runs with. This is a read-only dev-clone check: there is no installed operator validation verb, and it performs no send and no model input.
479
524
 
480
525
  ```bash
481
- node --experimental-strip-types -e 'import("./pi-extensions/lib/remote-pair-send.ts").then(m=>console.log(m.readRemotePairOptIn(m.remotePairOptInPath())))'
526
+ node --experimental-strip-types -e 'import("./pi-extensions/lib/remote-pair-send.ts").then(m=>console.log(JSON.stringify(m.readRemotePairOptIn(m.remotePairOptInPath()),null,2)))'
482
527
  ```
483
528
 
484
529
  Do not validate the configuration by sending a test message.
485
- 4. **Use it only between siblings you have open.** The measured self-fetch pair must be active and armed, and the target rail's own admission still applies. Delivery never starts, resumes or wakes an OFF citizen. To an active, armed one it rings the normal doorbell, which may start a turn at once in the receiving TUI. Remove the file to switch the pair off; there is nothing to stop or reconnect.
530
+ 4. **Use it only between siblings you have open.** The measured self-fetch pair must be active and armed, and the target rail's own admission still applies. Delivery never starts, resumes or wakes an OFF citizen. To an active, armed one it rings the normal doorbell, which may start a turn at once in the receiving TUI. Remove the file to switch every route off; there is nothing to stop or reconnect.
531
+
532
+ **Converting a file from 0.41 or earlier.** The earlier versionless file (the four keys alone) is refused by name. It is not read as one route, and it is not read as off. Convert each host by hand, one host at a time:
533
+
534
+ 1. Inspect the existing file.
535
+ 2. Install the new bytes on that host.
536
+ 3. Restart the processes whose bridge you use. A bridge that is already running keeps the code it loaded.
537
+ 4. Rewrite the file as `{"version":2,"routes":[<the old object>]}`, mode `0600`.
538
+
539
+ While the bytes and the file disagree, **every** `entwurf_v2` call of that host's bridges is refused, local targets included, because the file is read before the target is classified. That applies either way: new code with the old file, or old code with the new file. Keep that window short, and do not hide it with a fallback. The other host can stay on its own release and file until it converts. The claim, SSH argv, exit codes and receipt did not change, so the two hosts should still interoperate, but that mixed state has not been measured, and neither has any exchange over the new file.
486
540
 
487
541
  ## 3. Optional Claude Code native citizen
488
542
 
@@ -647,7 +701,7 @@ as a note rather than a fault.
647
701
 
648
702
  **entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
649
703
  presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
650
- gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.3.0 <1.4`) and Claude Code
704
+ gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.4.0 <1.5`) and Claude Code
651
705
  (`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
652
706
  decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
653
707
  earned one because an older binary validates the exec manifest, drops `args` at runtime, and
@@ -899,9 +953,14 @@ mechanism, because both the anchor and the env fallback would answer the same ro
899
953
 
900
954
  ## 5. Optional Antigravity native citizen
901
955
 
902
- This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending
903
- physical host. The installers may write on Darwin; a green install is
904
- not a native-push receipt.
956
+ Native-push send on this rail is paused (#152, below). Its Linux certification is
957
+ history: the last live bidirectional round trip was on agy 1.1.0 (2026-07-14), and it
958
+ is not a current certification. What remains is the legacy identity surface — install,
959
+ birth, statusline and sender identity. While the pause holds, a release accepts agy
960
+ through the deterministic pause cell and the install/sender gates only; checking these
961
+ surfaces in a fresh conversation is an optional diagnostic, not a release certification of
962
+ the installed host. On macOS it is NOT CERTIFIED — pending physical host. The installers may write on Darwin; a green install is not a native-push
963
+ receipt.
905
964
 
906
965
  Install the three independently owned surfaces:
907
966
 
@@ -920,9 +979,16 @@ owns its subtree; the hook owns one `PreInvocation` entry. Unrelated settings ar
920
979
  preserved. A fresh conversation initially may show `🪛 ? agy`; the first invocation
921
980
  births the record by native `conversationId`, after which the garden id appears.
922
981
 
923
- Real native-push acceptance needs an already-running conversation:
982
+ Native push to agy is paused (#152): on the external path entwurf measured, the agy
983
+ language server refused `agentapi` calls for a missing CSRF token (a first-party token
984
+ path is unmeasured), so `entwurf_v2` refuses an agy target as
985
+ `native-push-paused`, `entwurf_self` reports `replyable: false`, and
986
+ `entwurf_register_native` refuses. The citizen keeps its garden id and statusline. While
987
+ the pause holds, the LIVE smoke exits 2 with `[entwurf:paused]` before it reads the
988
+ conversation id, which is not a PASS, and no conversation needs to be opened for it:
924
989
 
925
990
  ```bash
991
+ # exits 2 [entwurf:paused] while #152 holds — not a PASS, not a SKIP
926
992
  LIVE=1 AGY_CONVERSATION_ID=<id> entwurf smoke-agy-native-push-live
927
993
  ```
928
994
 
@@ -77,7 +77,7 @@ import { resolveMailboxReceiverFacts } from "../../../pi-extensions/lib/entwurf-
77
77
  import { listEntwurfFacts } from "../../../pi-extensions/lib/entwurf-fact-provider.js";
78
78
  import { ENTWURF_PEERS_RENDER_LIMIT, renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-render.js";
79
79
  import { computeSelfAddressability } from "../../../pi-extensions/lib/entwurf-self-address.js";
80
- import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
80
+ import { nativePushPaused, nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
81
81
  import { resolveMailboxWakeModeCapability } from "../../../pi-extensions/lib/entwurf-v2-decider.js";
82
82
  import { retireCitizen } from "../../../pi-extensions/lib/entwurf-v2-retire.js";
83
83
  import { makeProductionRetireDeps } from "../../../pi-extensions/lib/entwurf-v2-retire-production.js";
@@ -235,6 +235,7 @@ async function buildMetaSenderEnvelope(identity, cwd, trustedMarker) {
235
235
  metaDeliveryDomain,
236
236
  recordBacked: true,
237
237
  probeAlive: await probeNativeSenderAlive(identity),
238
+ nativePushPaused: nativePushPaused(identity.backend),
238
239
  }
239
240
  : metaDeliveryDomain === "self-fetch"
240
241
  ? (() => {
@@ -402,7 +403,7 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
402
403
  "mailbox only while deliverable, native-push direct injection only while its adapter probe is alive, " +
403
404
  "or a THIRD RESULT: rejection when neither holds. An inactive self-fetch citizen is mailbox-undeliverable; " +
404
405
  "native-push has NO mailbox: dead → native-push-target-dead; indeterminate → " +
405
- "native-push-probe-indeterminate. " +
406
+ "native-push-probe-indeterminate; agy (paused, #152) → native-push-paused, no probe. " +
406
407
  "DORMANT IS UNREACHABLE: a socket-domain citizen that is not running gets dormant-fire-forget-unsupported " +
407
408
  "— same receiver rule as the mailbox, no active drainer means no delivery. " +
408
409
  "The intent that used to answer there, owned-outcome, resumed it by launching a hidden background child " +
@@ -417,7 +418,8 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
417
418
  .describe("fire-and-forget = send/reply/hand-off to a LIVE socket target or to any citizen with no " +
418
419
  "socket liveness — the decider picks its rail, and a rail can also REJECT (self-fetch → " +
419
420
  "mailbox when deliverable, else mailbox-undeliverable; native-push → alive: direct injection, " +
420
- "dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate); set " +
421
+ "dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate; a paused " +
422
+ "backend — agy, #152 — rejects as native-push-paused before any probe); set " +
421
423
  "wants_reply for an answer. This is the ONLY intent: owned-outcome, which resumed a dormant " +
422
424
  "citizen by launching a hidden background child, was withdrawn under the visible-first rule. " +
423
425
  "It is not selectable, not deprecated-but-tolerated; a dormant citizen is currently " +
@@ -438,11 +440,11 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
438
440
  // Resolved ONCE so the dispatch-moment timestamp is fixed and the control RPC sender
439
441
  // + the mailbox body sender share one envelope. No replyability gate (see above).
440
442
  const sender = await buildSendSenderEnvelope({ requestMeta: extra._meta });
441
- // #107: the operator's remote-pair opt-in is read per send. Absent = off and the local
442
- // surface below runs exactly as before; a declared target goes over that one route.
443
+ // #107/#149: the operator's remote-pair opt-in is read per send. Absent = off and the local
444
+ // surface below runs exactly as before; a declared target goes over its one exact route.
443
445
  const rendered = await runEntwurfV2WithRemoteRoute({ target, intent, message, mode, wants_reply }, {
444
446
  senderProvider: () => sender,
445
- route: readRemotePairOptIn(remotePairOptInPath()),
447
+ optIn: readRemotePairOptIn(remotePairOptInPath()),
446
448
  // The decider's canonical socket dir AND this bridge's ENTWURF_DIR: a trace in either is local.
447
449
  localTrace: makeLocalTraceFact({
448
450
  sessionsDir: defaultMetaSessionsDir(),
@@ -546,7 +548,7 @@ server.tool("entwurf_peers", "List the entwurf fact surface: garden citizens (fr
546
548
  "or a REJECT when neither holds: mailbox-undeliverable for a self-fetch citizen whose inbox " +
547
549
  "nobody drains, and on the native-push probe dead → native-push-target-dead vs " +
548
550
  "indeterminate → native-push-probe-indeterminate, kept apart because an unestablished probe " +
549
- "is not a departed host. So " +
551
+ "is not a departed host; an agy target rejects as native-push-paused (#152) without being probed. So " +
550
552
  "`unsupported` does not promise reachability either: a record whose backend has no adapter on " +
551
553
  "this lane resolves to that reject. " +
552
554
  "Note: this is the *active* world and it is facts-only — it creates nothing. Pass an " +
@@ -616,8 +618,9 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
616
618
  "must be LIVE: it is probed first, and a dead/indeterminate probe is refused (no garden id is " +
617
619
  "minted for a pointer that does not resolve to a real host). Re-registering the same " +
618
620
  "conversation attaches to the SAME garden id and refreshes the cwd. Only 'antigravity' is " +
619
- "registerable on this lane. No mailbox receiver marker is written — native-push has no " +
620
- "idle-wake watch; the returned garden id is the reply handle.", {
621
+ "registerable on this lane, and antigravity native-push is PAUSED (#152): this verb refuses it by " +
622
+ "name before any probe and mints no garden id. No mailbox receiver marker is written — native-push " +
623
+ "has no idle-wake watch; the returned garden id is the reply handle.", {
621
624
  backend: z
622
625
  .enum(["antigravity"])
623
626
  .describe("The native backend hosting the conversation. Only 'antigravity' — codex is a separate lane."),
@@ -659,8 +662,8 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and
659
662
  "herdr workspace, pi and claude-code " +
660
663
  "only; everywhere else it opens in the operator's tmux with all six backends (pi, " +
661
664
  "claude-code, copilot, omp, codex, pi-durable). No fallback: an incomplete herdr context is refused by name, " +
662
- "never by a tmux window you cannot see from herdr. The sibling's FIRST action " +
663
- "is a callback to you carrying a nonce, whose sender envelope is its garden id — that is how you learn the " +
665
+ "never by a tmux window you cannot see from herdr. If proceeding, the sibling calls back before its task with a nonce; " +
666
+ "the sender envelope names its garden id — that is how you learn the " +
664
667
  "address of a thing that did not exist a moment ago. This returns a LAUNCH receipt (the owner's " +
665
668
  "coordinates plus that nonce): it does NOT mean the runtime started, the first turn ran, or " +
666
669
  "the task was delivered. Those coordinates are a VIEW, never an address — a herdr tab/pane id can change under " +
@@ -75,6 +75,16 @@ export function computeSelfAddressability(facts) {
75
75
  // default fail-closed row).
76
76
  switch (facts.metaDeliveryDomain) {
77
77
  case "native-push": {
78
+ // #152 policy, decided BEFORE the pure record+probe predicate and kept out of it:
79
+ // only a record-backed citizen can be "paused" (a record-less one keeps its own
80
+ // reason), and no probe was run for this answer, so none is reported.
81
+ if (facts.recordBacked === true && facts.nativePushPaused === true) {
82
+ return {
83
+ replyable: false,
84
+ socketState: "none",
85
+ reason: "native-push unreachable — native-push for this backend is paused (#152); no probe ran for this answer",
86
+ };
87
+ }
78
88
  const push = nativePushDeliverable({ recordBacked: facts.recordBacked, probeAlive: facts.probeAlive });
79
89
  return {
80
90
  replyable: push.deliverable,
@@ -119,6 +119,24 @@ export const NATIVE_PUSH_BACKENDS = ["antigravity", "codex"];
119
119
  export function nativePushSupported(backend) {
120
120
  return NATIVE_PUSH_BACKENDS.includes(backend);
121
121
  }
122
+ // ── Paused native-push backends (#152) ─────────────────────────────────────
123
+ // A native-push backend whose SEND is paused by product policy. On the external send path
124
+ // entwurf measured, Antigravity's language server refused the `agentapi` call with
125
+ // `Unauthenticated: missing CSRF token` (agy 1.2.16 in #128; agy 1.4.6 again for #152).
126
+ // Whether a first-party own-tool shell holds a token entwurf could legitimately present is
127
+ // UNMEASURED, not ruled out. So instead of probing a port that refused on that path and
128
+ // reporting it as an inconclusive liveness, the three public entries that would reach the agy LS —
129
+ // v2 dispatch, a sender's own `replyable`, and `entwurf_register_native` — refuse by name
130
+ // BEFORE any probe. This is a fixed constant, not a flag: no env, config or runtime toggle
131
+ // can lift it. The backend stays in NATIVE_PUSH_BACKENDS, so existing agy records, the V3
132
+ // reader and the peers listing are unchanged; only the native-push traffic stops.
133
+ // The one probe that is NOT behind this guard is the fresh-cut quiescence check
134
+ // (scripts/meta-bridge-fresh-cut.ts), which protects an operator-named archive and must keep
135
+ // failing closed rather than read the pause as a departed host.
136
+ export const NATIVE_PUSH_PAUSED_BACKENDS = ["antigravity"];
137
+ export function nativePushPaused(backend) {
138
+ return NATIVE_PUSH_PAUSED_BACKENDS.includes(backend);
139
+ }
122
140
  /**
123
141
  * Compose the 4-value FACT liveness from a backend and its socket probe.
124
142
  * Out-of-domain backend → `unsupported` (NOT dead/indeterminate, R1). An
@@ -148,18 +166,20 @@ export const ENTWURF_V2_REJECT_REASONS = [
148
166
  "mailbox-undeliverable", // F-mailbox: fire-and-forget to an unsupported citizen whose mailbox is not deliverable (fail-closed; future pi-backend non-drainable mailbox)
149
167
  "native-push-target-dead", // 봉인 1: fire-and-forget to a native-push (agy) target whose adapter probe found NO live conversation. Post-probe; observedLiveness = dead.
150
168
  "native-push-probe-indeterminate", // 봉인 1: fire-and-forget to a native-push target whose adapter probe was inconclusive (agy alive but no port served the conv, or a probe error). Post-probe; observedLiveness = indeterminate. Never coerced to dead.
169
+ "native-push-paused", // #152: fire-and-forget to a native-push backend whose send is paused by product policy (NATIVE_PUSH_PAUSED_BACKENDS — antigravity). Pre-probe, observedLiveness = null: nothing was probed, so no liveness is claimed — never coerced to dead or indeterminate.
151
170
  "bad-target", // R2: absent/typo garden-id (no existing citizen); fresh creation out of v2 scope
152
171
  "record-less-socket", // #50 C4: a gid-shaped non-symlink control socket exists but NO meta-record claims it. The record is the sole address authority (목표 ②), so a bare socket is not an addressable citizen — it is a diagnostic state, refused for EVERY intent before any lock/probe (pre-probe, observedLiveness=null). NOT `bad-target`: something real answers to this gid, and mislabeling it absent would hide the very state the reject exists to surface.
153
172
  "target-locked", // R5 pre-claim for bucket B F2 per-gid lockfile conflict
154
173
  "target-address-conflict", // F3: a quarantined citizen (garden-id-socket-conflict / symlinked socket) — the gid resolves to two different receivers (record vs socket), so dispatch refuses to pick. The ONLY in-band honest channel for a dispatch-level identity-split (the listing diagnostic channel is not visible to a v2 caller, who only gets a receipt). Pre-resolver, like bad-target/target-locked — NOT a RESOLVER_REJECT_REASONS member.
155
174
  ];
156
175
  // ── Pre-probe reject reasons (?6 — observedLiveness = null) ────────────────
157
- // These four rejects are decided BEFORE any liveness probe runs, so there is no
176
+ // These five rejects are decided BEFORE any liveness probe runs, so there is no
158
177
  // honest 4-value FactLiveness to stamp: `bad-target` (no citizen/backend),
159
178
  // `target-locked` (5a lock conflict, before lstat/connect), `target-address-conflict`
160
179
  // (address-subject conflict → probing is forbidden), `record-less-socket` (#50 C4:
161
180
  // no record ⇒ not a citizen ⇒ no in-domain probe ever runs — the presence hint is
162
- // a single lstat, not a liveness measurement). `indeterminate` means an
181
+ // a single lstat, not a liveness measurement), `native-push-paused` (#152: the backend's
182
+ // native-push send is paused by policy, so its adapter is never probed). `indeterminate` means an
163
183
  // in-domain probe was inconclusive (≠ "not looked yet"); `unsupported` means the
164
184
  // backend has no predicate (≠ "pre-probe"). So a pre-probe reject's
165
185
  // observedLiveness is `null`, NOT one of the four values. Every OTHER reject —
@@ -176,6 +196,7 @@ export const PRE_PROBE_REJECT_REASONS = [
176
196
  "target-locked",
177
197
  "target-address-conflict",
178
198
  "record-less-socket",
199
+ "native-push-paused",
179
200
  ];
180
201
  export function isPreProbeReject(reason) {
181
202
  return PRE_PROBE_REJECT_REASONS.includes(reason);
@@ -44,7 +44,7 @@
44
44
  * be bypassed.
45
45
  */
46
46
  import { isOutOfSocketDomainGardenIdConflict } from "./entwurf-facts.js";
47
- import { isLivenessSupported, makeRejectReceipt, nativePushSupported, resolveDispatch, resolveNativePushDispatch, } from "./entwurf-v2-contract.js";
47
+ import { isLivenessSupported, makeRejectReceipt, nativePushPaused, nativePushSupported, resolveDispatch, resolveNativePushDispatch, } from "./entwurf-v2-contract.js";
48
48
  import { defaultMetaMailboxDir, defaultMetaSessionsDir, metaCapabilityFor, } from "./meta-session.js";
49
49
  import { isValidSessionId } from "./session-id.js";
50
50
  import { controlSocketPath, mapInspectionToLiveness } from "./socket-discovery.js";
@@ -121,6 +121,12 @@ export async function decideDispatch(input, deps) {
121
121
  // branch is LOCK-FREE (봉인 4): the pi in-domain lock closes a socket TOCTOU, but a
122
122
  // volatile probe route has no lock meaning (a duplicate-send idempotency is a D8 future).
123
123
  if (nativePushSupported(identity.backend)) {
124
+ // #152: a paused native-push backend is refused by name BEFORE its adapter is probed —
125
+ // on the measured external path that language server refused the call (missing CSRF token),
126
+ // and a refusal must not be reported as a measured liveness. Pre-probe, observedLiveness null.
127
+ if (nativePushPaused(identity.backend)) {
128
+ return reject(makeRejectReceipt("native-push-paused", null));
129
+ }
124
130
  const probe = await deps.nativePushProbe(identity);
125
131
  const receipt = resolveNativePushDispatch(input.intent, probe.status);
126
132
  if (!receipt.ok)
@@ -74,6 +74,9 @@ export function actionableRejectHint(reason) {
74
74
  // Native host up, but nothing served this conversation/thread — inconclusive, not a
75
75
  // hard dead. Backend-neutral for the same reason as the dead cell above.
76
76
  return "the native host (harness process or app-server) is up, but nothing served this conversation/thread, so the probe is INCONCLUSIVE — not a measured death. Retry once it is loaded, or verify the conversation/thread id.";
77
+ case "native-push-paused":
78
+ // #152: a policy refusal, not a liveness verdict — this dispatch probed and sent nothing.
79
+ return "native-push for this backend is paused (#152): its language server refused entwurf's measured external calls for a missing CSRF token, so this dispatch probed and sent nothing. The citizen and its record are kept; reach it from inside its own harness instead.";
77
80
  case "record-less-socket":
78
81
  // #50 C4: name the true cause AND the fix — a bare socket is a diagnostic
79
82
  // state, not an addressable citizen (the record is the address).
@@ -63,7 +63,7 @@ export const FRESH_CALL_CALLBACK_TOOL = {
63
63
  copilot: "entwurf-bridge-entwurf_callback",
64
64
  omp: "mcp__entwurf_bridge_entwurf_callback",
65
65
  // Codex's tool process is the operator app-server, not the pane: the no-arg
66
- // verb refuses there by name. First action stays the delivery verb with args.
66
+ // verb refuses there by name. Its callback stays the delivery verb with args.
67
67
  codex: "mcp__entwurf_bridge__entwurf_v2",
68
68
  // The durable contact registers the bridge verbs as its OWN tools under their bare names, and the
69
69
  // app offers a tool to the model by its registered name (`durable/harness/registry.ts:99`
@@ -348,8 +348,8 @@ export function composeBackendArgs(backend, composition, model, resolveCodexSock
348
348
  * PLACEMENT SENTENCE. The first line names where the sibling was opened, and only the RAIL knows
349
349
  * that — "the operator's tmux session" is true of a `new-window` launch and false of a herdr
350
350
  * pane. It is therefore supplied by the caller rather than composed here. Everything BELOW that
351
- * line is rail-independent and stays the contract: the callback is the first action, the task
352
- * follows it, and the three prohibitions are measured rather than polite.
351
+ * line is rail-independent and stays the contract: the callback comes before the task, an optional
352
+ * read-only check of the caller may come before the callback, and the steering is stated as fact.
353
353
  */
354
354
  /**
355
355
  * The first-turn framing, WITHOUT the task.
@@ -361,8 +361,13 @@ export function composeBackendArgs(backend, composition, model, resolveCodexSock
361
361
  * the halves separate is what lets the framing stay plain prose on both rails while the operator's
362
362
  * task text keeps every byte it was written with.
363
363
  *
364
- * Order is the contract: the callback is the FIRST action and the task follows it, so a sibling
365
- * that gets stuck in the task has already told the caller who it is.
364
+ * Order is the contract: the callback comes BEFORE THE TASK, so a sibling that gets stuck in the
365
+ * task has already told the caller who it is. It is not the literal first tool call. `[GLG 직접,
366
+ * 2026-10-24]` checking the caller and then calling back is the right order, and the framing used
367
+ * to say both "FIRST ACTION … call back" and "you can corroborate the caller first" — two
368
+ * sentences that cannot both be obeyed. What may come before the callback is the one read-only
369
+ * check offered below (and, where a dialect needs it, loading the tools); reading files, running
370
+ * commands or starting the task may not.
366
371
  *
367
372
  * WHAT THIS SAYS AND WHY IT STOPPED SAYING THE REST (#116). Until 2026-09-17 this framing carried
368
373
  * three prohibitions, including "Do not inspect environment variables, do not call entwurf_self".
@@ -390,7 +395,7 @@ export function composeFreshCallFraming(params) {
390
395
  }
391
396
  const firstAction = params.backend === "codex"
392
397
  ? [
393
- `FIRST ACTION, before reading files or anything else: call ${birthTool} with ` +
398
+ `BEFORE THE TASK, and before reading files or running commands: call ${birthTool} with ` +
394
399
  `target=${params.callerGardenId}, intent=fire-and-forget, wants_reply=false, and ` +
395
400
  `message set to exactly ${params.nonce} — that string alone, nothing added.`,
396
401
  `${params.nonce} is a correlation tag this caller minted for this one launch: it names no`,
@@ -400,7 +405,7 @@ export function composeFreshCallFraming(params) {
400
405
  "entwurf_callback is refused on this rail: the tool process is the operator app-server, not this pane.",
401
406
  ]
402
407
  : [
403
- `FIRST ACTION, before reading files or anything else: call ${birthTool} with no arguments.`,
408
+ `BEFORE THE TASK, and before reading files or running commands: call ${birthTool} with no arguments.`,
404
409
  "That call is how the caller learns the garden id you were born with. There are no",
405
410
  "parameters: a target or message you type would be a second address axis.",
406
411
  "Your own record does not carry the caller's address, so reporting your identity here does not reach it — that tool call does.",
@@ -410,9 +415,15 @@ export function composeFreshCallFraming(params) {
410
415
  "",
411
416
  ...firstAction,
412
417
  "",
413
- `You can corroborate the caller first if you want to: ${peersTool} is a read-only listing of`,
414
- `this garden's citizens and ${params.callerGardenId} is one of them. That listing is capped, so`,
415
- "a caller you do not happen to see in it is not a reason to skip the callback.",
418
+ // THE CHECK IS THE SIBLING'S, AND SO IS ITS VERDICT. `[GLG 직접, 2026-10-24]` a capped listing that
419
+ // does not show the caller proves nothing about the caller, but a sibling whose check does not hold
420
+ // up may say so and hold back. Entwurf decides nothing here — no trust store, no fallback, no
421
+ // watcher — and the cost of holding back is stated, not hidden: no callback, no address.
422
+ `You may corroborate the caller before that call: ${peersTool} is a read-only listing of`,
423
+ `this garden's citizens and ${params.callerGardenId} is one of them. That listing is capped, so not`,
424
+ "seeing the caller in it does not show the caller is absent. If what you can check is not enough,",
425
+ "or it conflicts with this message, you may say so here and hold the callback and the task; the",
426
+ "caller then has no address for you.",
416
427
  ...hintBlock,
417
428
  "",
418
429
  // WHERE THE RESULT GOES, said once, as topology. `[GLG 직접, 2026-09-18]` a Sonnet sibling
@@ -899,10 +899,12 @@ export function renderHerdrFreshCall(result) {
899
899
  `mean the sibling is running, that its first turn ran, or that the task was delivered. The tab and pane ` +
900
900
  `coordinates are a view and can change under the sibling — they are not an address and nothing may be ` +
901
901
  `dispatched to them.\n` +
902
- `The sibling's garden id arrives separately — its first action is the zero-argument callback verb, and ` +
902
+ `The sibling's garden id arrives separately — before its task it calls the zero-argument callback verb ` +
903
+ `(a read-only look at the caller may come first), and ` +
903
904
  `the sender envelope of THAT message is the address. The nonce above is the correlation tag it carries; ` +
904
905
  `the sibling reads it from its own launch env, never from this receipt. Nothing is polling for it; if it ` +
905
- `never comes, the pane is visible and can be read directly.`,
906
+ `never comes — not started, stuck, or held back after its check; this receipt cannot tell which — the ` +
907
+ `pane is visible and can be read directly.`,
906
908
  isError: false,
907
909
  };
908
910
  }
@@ -46,6 +46,7 @@
46
46
  * rails never merge — `codex` is deliberately absent from {@link META_SENDER_BACKENDS}, since
47
47
  * a backend listed there is one whose hook writes a pid marker, and codex birth writes none.
48
48
  */
49
+ import { nativePushPaused } from "./entwurf-v2-contract.js";
49
50
  import { defaultMetaSessionsDir, describe, isPlausibleOwnerPid, listAllMetaIdentities, makeStoreRecordReader, metaRecordExistsByGardenId, parentPid, readActiveStoreEntries, readMetaIdentityByGardenId, readMetaSenderMarker, } from "./meta-session.js";
50
51
  import { resolveNativePushAdapter } from "./native-push/adapter.js";
51
52
  /** Every native backend that mints a garden-id from its own hook and writes a sender marker.
@@ -193,6 +194,10 @@ export function resolveTrustedMetaSenderIdentity(opts = {}) {
193
194
  * exactly the Crash-Don't-Warn shape this lane exists to remove. Let them propagate.
194
195
  */
195
196
  export async function probeNativeSenderAlive(identity, deps = {}) {
197
+ // #152: a paused native-push backend cannot receive a reply, so its `replyable` is false by
198
+ // POLICY — no adapter is resolved or probed. This is not a liveness verdict about the citizen.
199
+ if (nativePushPaused(identity.backend))
200
+ return false;
196
201
  const resolveAdapter = deps.resolveAdapter ?? resolveNativePushAdapter;
197
202
  const probe = await resolveAdapter(identity.backend).probe(identity.nativeSessionId);
198
203
  return probe.status === "alive";
@@ -514,11 +514,13 @@ export function renderFreshCall(result) {
514
514
  `\n` +
515
515
  `This is a LAUNCH receipt: tmux created that window and was asked to start the runtime with the model above. It does ` +
516
516
  `NOT mean the sibling is running, that its first turn ran, or that the task was delivered.\n` +
517
- `The sibling's garden id arrives separately — its first action is the zero-argument callback verb ` +
518
- `(codex is the one arm that still sends the nonce as arguments), and the sender envelope of THAT ` +
517
+ `The sibling's garden id arrives separately — before its task it calls the zero-argument callback verb ` +
518
+ `(codex is the one arm that still sends the nonce as arguments; a read-only look at the caller may come ` +
519
+ `first), and the sender envelope of THAT ` +
519
520
  `message is the address. The nonce above is the correlation tag it carries; the sibling reads it ` +
520
- `from its own launch env, never from this receipt. Nothing is polling for it; if it never comes, ` +
521
- `the window is visible and can be read directly.`,
521
+ `from its own launch env, never from this receipt. Nothing is polling for it; if it never comes — ` +
522
+ `not started, stuck, or held back after its check; this receipt cannot tell which — the window is visible ` +
523
+ `and can be read directly.`,
522
524
  isError: false,
523
525
  };
524
526
  }