@junghanacs/entwurf 0.21.0 → 0.23.0
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.
- package/AGENTS.md +3 -2
- package/BASELINE.md +3 -1
- package/CHANGELOG.md +658 -0
- package/DELIVERY.md +167 -29
- package/README.md +104 -14
- package/VERIFY.md +73 -11
- package/docs/external-mcp-host.md +16 -6
- package/docs/setup-clean-host.md +73 -22
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +54 -31
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +289 -57
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +171 -263
- package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
- package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
- package/mcp/entwurf-bridge/src/index.ts +65 -31
- package/mcp/entwurf-bridge/tsconfig.build.json +10 -0
- package/package.json +4 -4
- package/pi-extensions/entwurf-control.ts +91 -52
- package/pi-extensions/lib/codex-caller-seat.ts +204 -0
- package/pi-extensions/lib/codex-declaration.js +612 -0
- package/pi-extensions/lib/codex-fresh-preflight.ts +325 -52
- package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
- package/pi-extensions/lib/entwurf-facts.ts +14 -1
- package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
- package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
- package/pi-extensions/lib/entwurf-self-address.ts +33 -0
- package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
- package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
- package/pi-extensions/lib/fresh-call-composition.ts +493 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
- package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
- package/pi-extensions/lib/herdr-placement.ts +276 -0
- package/pi-extensions/lib/mux-fresh-call.ts +257 -304
- package/run.sh +522 -10
- package/scripts/check-codex-app-server-launch.ts +445 -0
- package/scripts/check-codex-birth-hook.ts +187 -0
- package/scripts/check-entwurf-control-rpc.ts +138 -0
- package/scripts/check-entwurf-fact-provider.ts +4 -2
- package/scripts/check-entwurf-facts.ts +12 -6
- package/scripts/check-entwurf-peers-surface.ts +3 -1
- package/scripts/check-entwurf-self-address.ts +68 -0
- package/scripts/check-entwurf-v2-production.ts +42 -1
- package/scripts/check-entwurf-v2-send.ts +26 -7
- package/scripts/check-fresh-call-dispatch.ts +301 -0
- package/scripts/check-gate-qualification.ts +19 -5
- package/scripts/check-herdr-activation.ts +1293 -0
- package/scripts/check-herdr-fresh-call.ts +1250 -0
- package/scripts/check-herdr-placement.ts +383 -0
- package/scripts/check-herdr-plugin-build.ts +842 -0
- package/scripts/check-herdr-plugin-profile.ts +371 -0
- package/scripts/check-herdr-plugin.ts +523 -0
- package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
- package/scripts/check-herdr-sandbox.ts +456 -0
- package/scripts/check-herdr-supply.ts +184 -0
- package/scripts/check-mux-launch-tmux.ts +331 -35
- package/scripts/check-mux-launcher-fence.ts +138 -0
- package/scripts/check-peer-facts.ts +331 -0
- package/scripts/check-release-gate-outcomes.ts +12 -0
- package/scripts/check-typing-call-fence.ts +296 -0
- package/scripts/codex-app-server-launch.sh +275 -0
- package/scripts/codex-birth-doctor.sh +159 -16
- package/scripts/codex-birth-install.sh +178 -72
- package/scripts/codex-birth-uninstall.sh +162 -7
- package/scripts/codex-socket-path.ts +33 -0
- package/scripts/codex-terminal-title-config.py +500 -0
- package/scripts/codex_toml_io.py +121 -0
- package/scripts/fixtures/herdr-supply.json +29 -0
- package/scripts/herdr-activation.mjs +536 -0
- package/scripts/herdr-plugin-activate.mjs +270 -0
- package/scripts/herdr-plugin-deactivate.mjs +193 -0
- package/scripts/herdr-runtime.mjs +1203 -0
- package/scripts/install-herdr-ci.sh +96 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +13 -5
- package/scripts/lib/codex-fresh-source-receipts.ts +29 -2
- package/scripts/lib/tree-digest.ts +97 -0
- package/scripts/meta-bridge-install.sh +19 -2
- package/scripts/meta-bridge-state.py +167 -11
- package/scripts/mutants/codex-app-server-launch.json +157 -0
- package/scripts/mutants/codex-caller-seat.json +334 -0
- package/scripts/mutants/codex-native.json +195 -29
- package/scripts/mutants/control-socket-disconnect.json +57 -0
- package/scripts/mutants/fresh-call-dispatch.json +154 -0
- package/scripts/mutants/herdr-activation.json +305 -0
- package/scripts/mutants/herdr-fresh-call.json +448 -0
- package/scripts/mutants/herdr-placement.json +161 -0
- package/scripts/mutants/herdr-plugin-build.json +160 -0
- package/scripts/mutants/herdr-plugin-profile.json +202 -0
- package/scripts/mutants/herdr-plugin.json +133 -0
- package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
- package/scripts/mutants/herdr-supply.json +133 -0
- package/scripts/mutants/meta-hook-session-switch.json +2 -2
- package/scripts/mutants/mux-fresh-call.json +184 -25
- package/scripts/mutants/mux-launcher-fence.json +13 -0
- package/scripts/mutants/omp-fresh.json +4 -4
- package/scripts/mutants/peer-facts.json +98 -0
- package/scripts/mutants/self-address.json +33 -0
- package/scripts/mutants/typing-call-fence.json +17 -0
- package/scripts/mutants/v2-surface.json +22 -0
- package/scripts/peer-facts.ts +120 -0
- package/scripts/register-pi-package.py +8 -0
- package/scripts/register-pi-provider.py +88 -12
- package/scripts/smoke-codex-birth.sh +281 -12
- package/scripts/smoke-codex-config-state.sh +192 -3
- package/scripts/smoke-codex-fresh-live.ts +277 -37
- package/scripts/smoke-entwurf-chain-live.ts +50 -0
- package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
- package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
- package/scripts/smoke-setup-verdict.sh +13 -11
package/DELIVERY.md
CHANGED
|
@@ -88,7 +88,7 @@ physical host. Do not read a D-level cell as a Darwin receipt.
|
|
|
88
88
|
| **pi native Entwurf** | shipped | D7; D8 partial | Record-addressed Unix control socket. A record-less socket is diagnostic only and never dispatched. |
|
|
89
89
|
| **Claude Code interactive `>=2.1.217`** | shipped; Linux certified | D6; D7/D8 partial | Per-session mailbox + exec-form `FileChanged`/`asyncRewake`. B2 proved idle wake and same-session continuity on one NixOS host. |
|
|
90
90
|
| **Antigravity / agy** | shipped | D6; D7 partial | Record-backed native-push through LS gRPC `agentapi send-message`; no mailbox or receiver marker. Admitted before the #82 step 9 contract and not re-evaluated under it, so it is legacy citizen evidence, not a step-9 supported harness: `entwurf_fresh_call` cannot open an agy sibling, and nothing here should be read as claiming visible lifecycle parity. |
|
|
91
|
-
| **Codex CLI app-server citizen** | supported in 0.21.0;
|
|
91
|
+
| **Codex CLI app-server citizen** | supported in 0.21.0; caller-seat topology ACCEPTED on Linux at 0.153.4, 2026-09-16 (56 assertions, exit 0), and the caller-DIRECTORY axis ACCEPTED the same day on the same host (#95 lane C, 65 assertions, exit 0; the 2026-09-12 explicit-home acceptance is history — #95 D1 retired that room) | D6 accepted; D7 partial; D8 unproven | Vendor-trusted user-scope `SessionStart` birth plus strict per-request metadata join; no shared-pid sender marker. `thread/loaded/list` probes the operator-owned app-server UDS and one-shot `codex queue` delivers with no retry. The operator owns the app-server and seats it wherever they like; Entwurf creates, moves and supervises none of it. Since #95 lane B a Codex CALLER with no explicit placement opens its sibling beside its OWN pane, located by the `thread-id` its `[tui].terminal_title` writes into the pane title; 0 or 2+ matching panes refuse with no fallback, and the title is a placement input only — never an address, liveness or delivery fact. #95 D1 (2026-09-16) retired the fixed `codex` home that omitted-placement Codex TARGETS used to select, so an omitted seat is the caller's own session for every backend. Exact 0.153.4 source still bounds placing a sibling beside a TUI whose thread nobody named as unsupported; the anchor resolves only the pane showing the caller's own thread. |
|
|
92
92
|
| **Codex embedded TUI** | deferred | D0 partial | At the 2026-09-08 Codex 0.153.4 measurement, standalone mode had no `watchPaths`/`FileChanged`/`asyncRewake` analogue or supported idle receive route. This is dated vendor evidence, not a claim that the current candidate lacks records or fresh. |
|
|
93
93
|
| **Copilot CLI first-party extension** | raw transport probe; superseded by the owned product unit | D7 path observed; D3 control receipt incomplete; D8 unproven | CLI-spawned extension over stdio JSON-RPC; `joinSession()` + documented `fs.watch` → `session.send({mode:"enqueue"})`. Idle wake, exact-marker reply, and completion passed on 2026-08-23 (CLI 1.0.80, L4, one Linux host). Two-process isolation was observed but its decisive B log was not preserved. Kept as the transport receipt the owned receive unit was built on; the shipped unit differs deliberately — it announces the inbox instead of injecting the body. |
|
|
94
94
|
| **Copilot CLI garden citizen** | shipped in 0.15.0; send + receive + visible fresh accepted on one host | D6; D7 partial; D3 pending; D8 unproven | Birth, garden id, MCP hand and record-backed sender identity are accepted; the RECEIVER is an installed first-party extension that binds to the V3 record, writes a receiver marker owned by the WATCHER pid, and rings a doorbell the model drains with `entwurf_inbox_read`. `wakeMode` is `self-fetch`, so dispatch reaches the mailbox rail: armed → delivered, unarmed/stale → the honest `mailbox-undeliverable` refusal. **D6 is the owned-invocation LIVE acceptance of 2026-08-23** — garden `20260823T181316-d9f6ba`, native `20fe30c8-b2bc-4600-91a0-8a409131be51`, CLI 1.0.80: receive log `joined`→`armed`→`doorbell fresh=1`→`rang`, mailbox `lastEnqueuedAt 09:23:41.235Z` / `lastReadAt 09:23:56.480Z`, and a model reply on the same record/native/gid chain. **Visible fresh (step 9 clause 7) is a separate LIVE, 2026-08-25** — launch window `@89`/`%89` nonce `mux-fresh-call-690529ae99f99faa2252aefb`; exact-callback garden `20260825T085721-f68be0`; one `entwurf_v2` → `meta-mailbox → enqueued`; same garden `lastReadAt 2026-08-24T23:57:47.784Z` plus same-gid reply; GLG saw footer garden id and a healthy multi-turn window. Those rows stay unmerged. D7 is PARTIAL: reply and read receipt were observed, the completion taxonomy and long-haul operation were not. D3 (second-session isolation of an owned invocation) is PENDING — observed once, decisive log lost to scratch cleanup. Evidence level L4: one host. Launch through the owned invocation `entwurf copilot`, which sets `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` for that one process; `doctor-copilot-receive` reads live CLI environments because a session launched without it is silently inert. Visible fresh is operator-metered and is not a release-gate MUST. |
|
|
@@ -152,26 +152,66 @@ operator starts app-server in existing exact tmux session `codex`
|
|
|
152
152
|
The standalone embedded TUI remains outside this rail: the 2026-09-08 measurement
|
|
153
153
|
found no idle receive route equivalent to the app-server. `turn/steer` is active-turn
|
|
154
154
|
steering, not idle wake. Entwurf does not start, stop, supervise, or health-loop the
|
|
155
|
-
app-server. The operator starts it
|
|
155
|
+
app-server. The operator starts it in a tmux session **of their own choosing**:
|
|
156
156
|
|
|
157
157
|
```bash
|
|
158
|
-
# Run from a pane in the operator-owned tmux session
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"
|
|
158
|
+
# Run from a pane in the operator-owned tmux session that will hold the app-server.
|
|
159
|
+
# For the LIVE acceptance that session must NOT be the one the Pi/Codex pair runs in.
|
|
160
|
+
entwurf codex-app-server
|
|
162
161
|
```
|
|
163
162
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
163
|
+
That verb owns the SPELLING of one vendor command and nothing else — it `exec`s
|
|
164
|
+
`codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"`
|
|
165
|
+
in the terminal it was typed in, after creating the control directory that address lives in.
|
|
166
|
+
It resolves the socket through the same leaf every other Codex surface reads, so the address a
|
|
167
|
+
consumer gets cannot drift from the one delivery looks for. There is no supervisor, no restart,
|
|
168
|
+
no daemon and no pid file: Ctrl-C is the operator's. A live socket, an unidentifiable one, or a
|
|
169
|
+
second `--listen` are named refusals; a dead socket file is reported and launched over. Passing
|
|
170
|
+
the raw vendor command by hand remains equivalent and is not deprecated.
|
|
171
|
+
|
|
172
|
+
#95 D1 (GLG, 2026-09-16) retired the requirement that this session be named `codex`, and with it the
|
|
173
|
+
rule that an omitted-placement Codex TARGET selected it. The operator still owns the app-server and
|
|
174
|
+
still chooses its room; Entwurf neither creates nor supervises it, and a missing app-server rejects.
|
|
175
|
+
|
|
176
|
+
Since #95 lane B the outbound direction no longer rides the app-server's inherited
|
|
177
|
+
`TMUX`/`TMUX_PANE`: a Codex CALLER with no explicit placement opens its sibling beside its own
|
|
178
|
+
pane, found by matching the caller's `_meta.threadId` against the `thread-id` the TUI writes
|
|
179
|
+
into that pane's title. Zero matching panes (`codex-caller-seat-unresolved`), two or more
|
|
180
|
+
(`codex-caller-seat-ambiguous`) and a caller whose `[tui].terminal_title` lacks `thread-id`
|
|
181
|
+
(`codex-caller-title-missing`) are all named refusals with no fallback seat. An attached TUI
|
|
182
|
+
still does not lend its pane to the server, and N arbitrary clients whose threads nobody named
|
|
183
|
+
still have no adjacency claim: the anchor resolves only the pane displaying the thread the
|
|
184
|
+
caller itself put on the request.
|
|
185
|
+
|
|
186
|
+
The seat order, for all five backends, and it follows the CALLER rather than what is being
|
|
187
|
+
opened: explicit `placement` wins; then a Codex CALLER's own pane (`codex-title-anchor`, no
|
|
188
|
+
session name on the receipt because a session was observed rather than requested); then the
|
|
189
|
+
caller's own session.
|
|
190
|
+
|
|
191
|
+
The DIRECTORY follows the caller the same way (#95 lane C, 2026-09-16): requested `cwd` wins;
|
|
192
|
+
then a Codex CALLER's own RECORD cwd, because this bridge is the app-server's MCP child and its
|
|
193
|
+
process directory is the app-server's rather than that caller's; then the launching process's
|
|
194
|
+
own, which is what tmux gives a window opened with no `-c`. Codex alone also carries that one
|
|
195
|
+
value into its argv as `-C <dir>`, and omitting it is a wrong answer rather than a neutral one:
|
|
196
|
+
an explicit `--remote` target takes its new thread's cwd from that override ALONE
|
|
197
|
+
(`codex-rs/tui/src/app_server_session.rs:2022-2033` at rust-v0.153.4), so without it the THREAD
|
|
198
|
+
opens in the app-server's repo while its pane sits elsewhere — measured on 2026-09-16 as three
|
|
199
|
+
citizens of one chain recording a directory none of them was in. The receipt names which rule
|
|
200
|
+
chose the directory (`requested` / the Codex caller's own record directory) and invents nothing
|
|
201
|
+
for the inherited case.
|
|
202
|
+
|
|
203
|
+
Four ownership atoms remain separate:
|
|
204
|
+
|
|
205
|
+
1. `entwurf install-codex-birth` owns **one `SessionStart` declaration inside**
|
|
206
|
+
`$CODEX_HOME/hooks.json` — not the file — plus its launcher and import closure under
|
|
207
|
+
`$XDG_DATA_HOME/entwurf/codex-birth`. The vendor keys trust per
|
|
208
|
+
`<path>:<event>:<group>:<handler>`, so a neighbouring integration (Herdr's official Codex
|
|
209
|
+
integration appends its own group) coexists: entwurf certifies the NORMALIZED digest and
|
|
210
|
+
shape of its own group, requires it exactly once, reads the vendor receipt at the index that
|
|
211
|
+
group was measured at, and reports every other group as present-but-foreign — certified by
|
|
212
|
+
nothing, rewritten never. Install appends and uninstall splices by text, so a neighbour's
|
|
213
|
+
bytes survive both unchanged. The ownership receipt is `codex-birth-install-state/v2`; a v1
|
|
214
|
+
receipt (whole-file digest) is refused by name and superseded forward by one reinstall.
|
|
175
215
|
No root: every path belongs to the operator. The vendor gates a user-layer declaration on
|
|
176
216
|
ONE interactive "Trust all", which entwurf never answers, pre-seeds or computes; the
|
|
177
217
|
receipt is read as its own doctor axis, and until it exists setup is honestly non-green.
|
|
@@ -181,8 +221,17 @@ Three ownership atoms remain separate:
|
|
|
181
221
|
keeps custom roots and the app-server seat intact without storing their values.
|
|
182
222
|
3. `entwurf install-codex-statusline` owns only `thread-title` within
|
|
183
223
|
`tui.status_line`. Birth calls `thread/name/set`, so the visible title is the garden id.
|
|
184
|
-
|
|
185
|
-
|
|
224
|
+
4. `entwurf install-codex-terminal-title` owns only `thread-id` within `tui.terminal_title`
|
|
225
|
+
— a different key and a different axis from atom 3: `status_line` is what a human reads
|
|
226
|
+
inside the TUI, `terminal_title` is what the multiplexer reports back as `#{pane_title}`
|
|
227
|
+
and is the only value a caller seat can be matched against. The seeded list is
|
|
228
|
+
`["activity", "project-name", "thread-id"]` (`activity` leads because the herdr Codex
|
|
229
|
+
detector keys on the spinner/action-required prefix) and an existing operator list is
|
|
230
|
+
appended to, never reordered. A tmux server with `allow-set-title off` replaces every pane
|
|
231
|
+
title with the hostname, so the seat refuses there even with the atom installed — that is
|
|
232
|
+
a repair condition the refusal names, not an inference this rail makes.
|
|
233
|
+
|
|
234
|
+
All four have state-backed doctors and inverses. Symlinked or foreign config is a
|
|
186
235
|
named refusal, not an adoption. Entwurf never installs Codex or its credentials.
|
|
187
236
|
|
|
188
237
|
Sender identity is request-scoped. The bridge requires the complete Codex metadata
|
|
@@ -211,17 +260,44 @@ name its caller and the callback was uncorrelatable.
|
|
|
211
260
|
Visible fresh runs:
|
|
212
261
|
|
|
213
262
|
```text
|
|
214
|
-
|
|
263
|
+
operator-owned tmux session A (any name — the operator seats it)
|
|
215
264
|
operator-owned app-server + supported Codex TUIs
|
|
216
|
-
codex --remote unix://<default socket> --model <explicit model>
|
|
265
|
+
codex --remote unix://<default socket> -C <launch directory> --model <explicit model>
|
|
217
266
|
--dangerously-bypass-approvals-and-sandbox <callback-first prompt>
|
|
218
267
|
```
|
|
219
268
|
|
|
220
|
-
With `placement` omitted
|
|
221
|
-
to
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
269
|
+
With `placement` omitted the seat follows the CALLER: a Codex citizen's own TUI pane, resolved from
|
|
270
|
+
the `thread-id` in that pane's title to a native `$id` before mutation, with 0 or 2+ matches refused
|
|
271
|
+
and no fallback; every other caller keeps caller-session default placement. An explicit seat remains
|
|
272
|
+
an expert override. The preflight must certify the state-backed birth closure digests, vendor trust
|
|
273
|
+
receipt, exact MCP/env boundary, `thread-title`, `terminal_title`, app-server socket, and a narrow LOCAL
|
|
274
|
+
read of the LAUNCH DIRECTORY's folder consent before tmux mutation — a guard on the cases the user
|
|
275
|
+
config decides, not a certification of the vendor's verdict. That last axis is the vendor's, not ours:
|
|
276
|
+
`[source rust-v0.153.4]` a `--remote` startup always runs `check_directory_trust` on the `-C` value
|
|
277
|
+
(`tui/src/lib.rs:1699-1725`) and nothing on that path reads the approval or sandbox policy
|
|
278
|
+
(`tui/src/onboarding/directory_trust.rs:33-130`), so
|
|
279
|
+
`--dangerously-bypass-approvals-and-sandbox` does not cover it — approvals and folder consent are
|
|
280
|
+
two different gates. A DIRECT decision is keyed to the exact directory: for `ProjectTrustHost::Remote` that lookup is
|
|
281
|
+
`vec![cwd_key]` alone (`tui/src/config_update.rs:290-296`), with no project-root marker, git root or
|
|
282
|
+
parent inheritance. An UNDECIDED directory renders a consent screen, and a TUI waiting on one has started no turn —
|
|
283
|
+
no rollout, no birth, no callback. Entwurf NOTES that as `codex-launch-cwd-undecided` and opens the
|
|
284
|
+
window anyway: the screen is self-repairing for the human this rail exists to put a window in front
|
|
285
|
+
of, and one answer teaches the vendor that directory for good. The unattended case is answered
|
|
286
|
+
where it belongs — `smoke-codex-fresh-live` asserts the same leaf up front, so a gate with nobody
|
|
287
|
+
at the keyboard reads a named precondition instead of a callback timeout. A directory the operator deliberately answered `untrusted`
|
|
288
|
+
is NOT refused: on a remote target the vendor skips that screen
|
|
289
|
+
(`onboarding/directory_trust.rs:94-96`; `uses_remote_workspace()` is `matches!(self, Self::Remote
|
|
290
|
+
{ .. })` at `tui/src/lib.rs:307-309`), so the turn starts and refusing it would be entwurf
|
|
291
|
+
inventing a policy the vendor does not have. A cwd INSIDE an explicitly `untrusted` project is a
|
|
292
|
+
third answer with its own reason, `codex-launch-cwd-untrusted-ancestor`: there the vendor returns
|
|
293
|
+
`pass the repository root explicitly with --cd` (`config_update.rs:357-371`) rather than a screen,
|
|
294
|
+
so answering a prompt at the child would only reproduce that error.
|
|
295
|
+
|
|
296
|
+
**The preflight leaf is narrower than the vendor's decision and does not claim otherwise.** It
|
|
297
|
+
reads one TOML file while the vendor reads a layered config through its app-server, where an
|
|
298
|
+
enabled project layer can consent with no entry at all (`config_update.rs:346-354`). Every case the
|
|
299
|
+
leaf cannot see resolves to "proceed", so it may fail to catch a hang but never refuses a launch
|
|
300
|
+
the vendor would have run. The callback spelling is `mcp__entwurf_bridge__entwurf_v2`; the new garden id
|
|
225
301
|
comes only from its sender envelope. There is no Codex resume, watcher, session/app-server creator,
|
|
226
302
|
or lifecycle supervisor.
|
|
227
303
|
|
|
@@ -246,7 +322,8 @@ preserved all three records/transcripts, and left operator app-server PID `15516
|
|
|
246
322
|
|
|
247
323
|
That receipt established the mechanism of the now-selected deployment, but its initial Pi also sat
|
|
248
324
|
in the app-server session and predates the fixed-home default. The stronger explicit-home acceptance
|
|
249
|
-
passed on 2026-09-12
|
|
325
|
+
passed on 2026-09-12, under the `codex` home topology #95 D1 later retired. Receipt
|
|
326
|
+
`.probe-artifacts/20260912T140745-codex-home-live-green.log`
|
|
250
327
|
(SHA-256 `09e79bd1b62962f8a11d647ee456a71b11965b6792c7972d97ac4992119c91d7`)
|
|
251
328
|
records 57 assertions and exit 0. The operator app-server stayed at `$158/@390/%390`; real initial Pi
|
|
252
329
|
`20260912T140748-355654` opened outside the home at `$150/@397`, omitted-placement Codex
|
|
@@ -298,7 +375,8 @@ omitted-placement Codex `20260913T065151-4b6078` (thread `01a0979a-edc4-7570-a29
|
|
|
298
375
|
`$158/@448`, Codex-opened Pi `20260913T065213-0811e6` at `$158/@449`, against the same operator-owned
|
|
299
376
|
app-server at `$158/@390/%390`. Artifact `.probe-artifacts/codex-fresh-live-2oId4C/` (`run-manifest.json`
|
|
300
377
|
sha256 `2cff6f08…`). Note the fixture seat differs from the standalone run: the aggregate builds its own
|
|
301
|
-
tmux session (`$187`), while the Codex legs
|
|
378
|
+
tmux session (`$187`), while the Codex legs resolved to the exact `codex` home that was the contract
|
|
379
|
+
at the time. That gate reported
|
|
302
380
|
MUST PASS=24 FAIL=0 SKIP=0 and `cut: OK`. Keep the three Codex acceptances apart — 57-assertion
|
|
303
381
|
pre-amendment first admission, 48-assertion standalone amended (`fZccoK`), and this 48-assertion
|
|
304
382
|
aggregate repeat (`2oId4C`).
|
|
@@ -308,6 +386,66 @@ as an observation rather than a defect: a callback nonce arrived one character s
|
|
|
308
386
|
instruction carried the wrong payload. Joined source receipts showed target, rail, and delivery were all
|
|
309
387
|
correct, so both oracles refused for the right reason and nothing in the product was changed.
|
|
310
388
|
|
|
389
|
+
**Caller-seat acceptance (#95 lane B), 2026-09-16, Codex 0.153.4 — 56 assertions, exit 0.** This is the
|
|
390
|
+
receipt for the topology that replaced the fixed home: `ENTWURF_CODEX_FRESH_MODEL=gpt-5.6-sol`,
|
|
391
|
+
`ENTWURF_CODEX_FRESH_PI_MODEL=openai-codex/gpt-5.6-luna`, final source audit `codex=3/3 completed exact`.
|
|
392
|
+
On one tmux server (`36728`) the operator-owned app-server `737636` stayed in ITS OWN session
|
|
393
|
+
A = `$30`/`@41`/`%45`, while all three smoke citizens opened in S = `$2`: initial Pi
|
|
394
|
+
`20260916T144205-e876ff` at `@46`, omitted-placement Codex `20260916T144230-d050d7` at `@47`, and the
|
|
395
|
+
Codex-opened outbound Pi at `@48`. **A ≠ S is what makes the run decisive rather than merely green:**
|
|
396
|
+
the app-server's inherited `TMUX` names A, so the pre-#95 environment fallback would have put that
|
|
397
|
+
outbound Pi there. It landed in S, and its receipt names the rule that chose the seat —
|
|
398
|
+
`seat: $2 (the Codex caller's own pane, found by its thread-id terminal title — an OBSERVED session,
|
|
399
|
+
not a requested name)`, recorded as `seat-source=codex-title-anchor`. Artifact
|
|
400
|
+
`.probe-artifacts/codex-fresh-live-nYcGC1/`, stdout preserved at `run-stdout.log`
|
|
401
|
+
(SHA-256 `ed60c2bdb46c13c51bf1d2dcf147b88c3c9f368a86a2bf17e972d3ecfde5fde9`). Cleanup reclaimed exactly
|
|
402
|
+
`@46`, `@47`, `@48` with no CLEANUP FAILURE, preserved every born record and transcript, and left the
|
|
403
|
+
app-server and `$30` untouched.
|
|
404
|
+
|
|
405
|
+
**Caller-directory acceptance (#95 lane C), 2026-09-16, Codex 0.153.4 — 65 assertions, exit 0.** Same
|
|
406
|
+
host, same models, same four-coordinate topology (app-server `737636` in A = `$30`/`@41`/`%45`; initial
|
|
407
|
+
Pi `20260916T154902-556eb5` at `$2`/`@58`, omitted-placement Codex `20260916T155037-10ddaa` at `$2`/`@59`,
|
|
408
|
+
Codex-opened outbound Pi `20260916T155102-9add8a` at `$2`/`@61` with `seat-source=codex-title-anchor`),
|
|
409
|
+
final source audit `initial-pi=3/3` and `codex=3/3 completed exact`. **65 and 56 are different
|
|
410
|
+
contracts, not a regression:** lane C added the cwd axis (seven assertions) and the pane-directory
|
|
411
|
+
reader it needs, on top of everything lane B already asserted.
|
|
412
|
+
|
|
413
|
+
The two decisive receipts, both measured against the app-server's own live directory
|
|
414
|
+
`/home/junghan/repos/gh/entwurf` (read from `/proc/737636/cwd`):
|
|
415
|
+
|
|
416
|
+
- **hop 1, cwd REQUESTED** (`8b-codex-thread-cwd`): the fresh Codex's pane `#{pane_current_path}`, the
|
|
417
|
+
vendor's own rollout `session_meta.cwd`, its Entwurf record and the requested scratch are ONE
|
|
418
|
+
directory — `/tmp/entwurf-codex-fresh-live-db65N2` — and it is not the app-server's. Four authorities
|
|
419
|
+
that cannot borrow from each other; before `-C` the rollout carried the app-server's path while the
|
|
420
|
+
pane sat in the scratch.
|
|
421
|
+
- **hop 2, cwd NOT REQUESTED** (`13b-outbound-pi-cwd`): the leg named neither placement nor cwd, and the
|
|
422
|
+
outbound Pi still opened in `/tmp/entwurf-codex-fresh-live-db65N2` — its pane, its own birth-written
|
|
423
|
+
record, and the Codex caller's record all agree. Its launch receipt names the rule rather than
|
|
424
|
+
borrowing the other one's noun: `cwd: /tmp/entwurf-codex-fresh-live-db65N2 (the Codex caller's own
|
|
425
|
+
record directory, used because no cwd was requested — not an observation)`, while both requested legs
|
|
426
|
+
still read `requested start directory`.
|
|
427
|
+
|
|
428
|
+
Artifact `.probe-artifacts/codex-fresh-live-w4yJBw/` (run manifest, mailbox observations/selections and
|
|
429
|
+
a 13-file pre-cleanup snapshot including all three records, both Pi transcripts and the Codex rollout);
|
|
430
|
+
stdout preserved at `.probe-artifacts/lane-c-live-20260916T154900.log`
|
|
431
|
+
(SHA-256 `712050e7a8cf03ece98e7f34029ae98a92e9b2a7fd96aff87d0c1e6ba2ee9af2`). Cleanup reclaimed exactly
|
|
432
|
+
`@58`, `@59`, `@61` with no CLEANUP FAILURE, removed its own scratch and fixture record, preserved every
|
|
433
|
+
born citizen record and transcript, and left the app-server and `$30` untouched.
|
|
434
|
+
|
|
435
|
+
Two earlier attempts that day are kept as their own receipts, because each stopped at a different
|
|
436
|
+
truth. The FIRST stopped at 26 assertions, before any window opened: `codex-birth-unit-missing`, because
|
|
437
|
+
`~/.codex/hooks.json` carried a second `SessionStart` entry added after install, so the declaration no
|
|
438
|
+
longer matched its recorded digest. The unit was not broken — entwurf's own atom and all six helper
|
|
439
|
+
digests were intact — but this rail owns that file whole, and the preflight refused rather than run
|
|
440
|
+
against bytes it could not vouch for. The SECOND stopped at 43 assertions with hop 1 already green, and
|
|
441
|
+
its log is kept as the D1 measurement: `.probe-artifacts/codex-fresh-live-4aFCDD/run-stdout.log`
|
|
442
|
+
(SHA-256 `8392a603dcfe7e89549942d55e515f97724398fb261bd8873d25806f71086afe`) carries
|
|
443
|
+
`omitted placement opened fresh Codex in the caller's own session S, away from the app-server's A` —
|
|
444
|
+
the retirement of the fixed home, observed on a real host. It then failed on the gate's own defect
|
|
445
|
+
rather than the product's: the source-call oracle compared arguments with `isDeepStrictEqual`, so a
|
|
446
|
+
model that omitted the optional `wants_reply` instead of passing it explicitly read as drift. That
|
|
447
|
+
oracle now normalizes exactly that key to its schema default and nothing else.
|
|
448
|
+
|
|
311
449
|
Unrestricted attached-TUI parity (**B**) is explicitly outside this support claim. The exact vendor
|
|
312
450
|
checkout `rust-v0.153.4` at `3d2ee51ca2d5db578f328aa75e20aa22c0197c9a` found no public
|
|
313
451
|
request→attached-TUI-seat carrier: process-local `ConnectionId` does not cross into thread/core/MCP/hook
|
|
@@ -317,7 +455,7 @@ behaviour remain invalid; explicit `placement.tmuxSession` is an operator-named
|
|
|
317
455
|
an inferred seat. The gate strips ambient `PI_SESSION_ID`/`PI_AGENT_ID`; a fixture may preserve receipts
|
|
318
456
|
but cannot substitute for the initial visible record-backed Pi turn.
|
|
319
457
|
|
|
320
|
-
Run the clause-7 invocation from a tmux session
|
|
458
|
+
Run the clause-7 invocation from a tmux session OTHER than the app-server's own; the explicit app-server PID must belong to the operator-owned app-server, and that session's NAME is not a requirement — #95 D1 retired the fixed `codex` home on 2026-09-16, so A ≠ S is the precondition and no particular name is. Both models are explicit, and the launch directory must already be answered in this Codex (`VERIFY.md` owns the derivation and the one-time `Trust`):
|
|
321
459
|
|
|
322
460
|
```bash
|
|
323
461
|
LIVE=1 \
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm package: <https://www.npmjs.com/package/@junghanacs/entwurf>
|
|
|
10
10
|
|
|
11
11
|
Legacy package: [`@junghanacs/pi-shell-acp`](https://www.npmjs.com/package/@junghanacs/pi-shell-acp). `entwurf` is its 0.12+ successor line: the same work renamed around the garden-citizen dispatch substrate rather than the pi adapter.
|
|
12
12
|
|
|
13
|
-
> **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one adapter, not the project subject. Claude Code, GitHub Copilot CLI, and OMP (`omp`) are shipped self-fetch citizens; Antigravity (`agy`) is shipped native-push. Codex CLI is a **native-push citizen, supported in 0.21.0 on Linux** over the operator-owned app-server: vendor-trusted user-scope birth, strict request-scoped identity, loaded-thread probe, one-shot queue delivery, and visible fresh
|
|
13
|
+
> **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one adapter, not the project subject. Claude Code, GitHub Copilot CLI, and OMP (`omp`) are shipped self-fetch citizens; Antigravity (`agy`) is shipped native-push. Codex CLI is a **native-push citizen, supported in 0.21.0 on Linux** over the operator-owned app-server: vendor-trusted user-scope birth, strict request-scoped identity, loaded-thread probe, one-shot queue delivery, and visible fresh beside the CALLER's own pane. Unrestricted attached-TUI seat inference is not supported or claimed; #95 D1 retired the fixed `codex` tmux home on 2026-09-16, so the operator seats the app-server wherever they like and a Codex caller opens its sibling beside its own TUI pane. Codex remains native so it keeps its vendor tools, delegation, and work context; this is not another way to buy or expose GPT through ACP.
|
|
14
14
|
|
|
15
15
|
```text
|
|
16
16
|
Claude Code / Copilot / Codex / agy / omp / pi
|
|
@@ -19,7 +19,7 @@ Claude Code / Copilot / Codex / agy / omp / pi
|
|
|
19
19
|
→ control-socket | meta-mailbox | native-push
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
[`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, meta-mailbox enqueue, and native-push into a live Antigravity conversation or loaded Codex thread. It starts no process on any rail. `entwurf_fresh_call` is separate lifecycle. With
|
|
22
|
+
[`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, meta-mailbox enqueue, and native-push into a live Antigravity conversation or loaded Codex thread. It starts no process on any rail. `entwurf_fresh_call` is separate lifecycle. With placement omitted the seat follows the CALLER: a Codex citizen opens beside its own TUI pane, found by the `thread-id` in that pane's terminal title, and 0 or 2+ matching panes reject with no fallback; every other caller opens in its own session. A missing app-server rejects before launch, and Entwurf never creates or supervises it. An explicit `placement.tmuxSession` remains an expert override. Exact 0.153.4 source confirms that arbitrary attached-TUI request→seat inference is unavailable, so Entwurf makes no such claim. Codex has no resume surface.
|
|
23
23
|
|
|
24
24
|
**Garden id is deliberate vocabulary.** It is not a decorative synonym for session id, worker, delegate, or subagent. The unfamiliar word is a guard: each harness keeps its own identity and transcript, while `entwurf` supplies a narrow addressable surface between siblings.
|
|
25
25
|
|
|
@@ -78,6 +78,19 @@ A few words that look unusual for a coding tool.
|
|
|
78
78
|
|
|
79
79
|
## Install
|
|
80
80
|
|
|
81
|
+
**Two installation routes, and the one you want depends on where you work.**
|
|
82
|
+
|
|
83
|
+
- **Direct** — `npm install -g @junghanacs/entwurf`, then `entwurf setup <project>`. This is the
|
|
84
|
+
general route: it composes every harness it FINDS on the host — pi, Claude Code, Codex, Copilot,
|
|
85
|
+
Antigravity, the ACP rail — and reports each one PASS / SKIP / FAIL. Use it in an ordinary shell,
|
|
86
|
+
in tmux, anywhere.
|
|
87
|
+
- **Herdr workbench** — `herdr plugin install` from inside a Herdr session. It activates Entwurf for
|
|
88
|
+
the pi and Claude Code sessions Herdr has integrated, and only those. Contract, evidence grades
|
|
89
|
+
and its refusals: [plugins/herdr/README.md](plugins/herdr/README.md).
|
|
90
|
+
|
|
91
|
+
Neither route installs a harness, a subscription, or a login (Hard Rule 17). Everything below this
|
|
92
|
+
paragraph describes the direct route.
|
|
93
|
+
|
|
81
94
|
**Platform evidence, in one breath.** The Entwurf-only install surface on
|
|
82
95
|
macOS is CERTIFIED (CI) (`macos-install-surface`). On macOS every
|
|
83
96
|
garden-native harness rail, marker/request join, ACP turn, and mux is NOT CERTIFIED —
|
|
@@ -152,6 +165,55 @@ entwurf check-bridge
|
|
|
152
165
|
whatever native harnesses are present. The global install is the easiest path when
|
|
153
166
|
Claude Code's USER-scope MCP registration should work from every cwd.
|
|
154
167
|
|
|
168
|
+
#### Codex: what you still type by hand
|
|
169
|
+
|
|
170
|
+
`setup` writes every byte Entwurf owns, and three things are deliberately left to you. They
|
|
171
|
+
are the only manual steps between a fresh install and calling a Codex sibling, and all three are
|
|
172
|
+
answers only you can give:
|
|
173
|
+
|
|
174
|
+
1. **Trust the birth hook once, in a visible plain Codex.** Answer `Trust all and continue`
|
|
175
|
+
and send one first turn. Nobody can do this for you: the receipt is the vendor's record of
|
|
176
|
+
*your* security decision, and writing it ourselves would turn a prompt into a silent
|
|
177
|
+
install. Until it exists, `setup` and `doctor-codex-birth` stay honestly non-green and say
|
|
178
|
+
exactly this.
|
|
179
|
+
2. **Start the app-server, in a detached tmux session that is NOT where you work.**
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
entwurf codex-app-server
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
One command for the vendor string nobody wants to type. It `exec`s
|
|
186
|
+
`codex app-server --listen unix://<default socket>` right there — so Ctrl-C is yours, and
|
|
187
|
+
Entwurf neither supervises nor restarts it. The session matters because the MCP bridge is
|
|
188
|
+
this server's child and inherits its `TMUX`: that tmux server is the one caller-seat
|
|
189
|
+
lookups read. The launcher prints which seat it got rather than guessing one for you.
|
|
190
|
+
3. **Answer the folder-consent prompt once for each directory you open siblings in.** Codex
|
|
191
|
+
records a direct decision per EXACT directory — not the parent, not the git root — and a
|
|
192
|
+
directory with no answer anywhere opens a consent screen instead of running a first turn.
|
|
193
|
+
`entwurf_fresh_call` prints `codex-launch-cwd-undecided` and opens the window anyway — the
|
|
194
|
+
screen is self-repairing, and answering it once is the whole fix. (Two neighbours differ: a
|
|
195
|
+
project `.codex` layer can consent with no entry at all, and a directory inside an explicitly
|
|
196
|
+
untrusted project is noted as `codex-launch-cwd-untrusted-ancestor` because the vendor answers
|
|
197
|
+
that one with an error naming the repository root instead of a prompt.) You can also do it up
|
|
198
|
+
front:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
codex -C <the directory you will launch siblings in> # answer the prompt, then quit
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Same reason as step 1: it is your security decision, and writing it ourselves would turn a
|
|
205
|
+
prompt into a silent install.
|
|
206
|
+
|
|
207
|
+
Then `entwurf_fresh_call` with `backend: "codex"` works. Attaching your own visible Codex TUI
|
|
208
|
+
(`codex --remote "unix://$CODEX_HOME/app-server-control/app-server-control.sock"`) is optional
|
|
209
|
+
and independent — a sibling Entwurf opens brings its own.
|
|
210
|
+
|
|
211
|
+
> **Observation, not a verb (#95).** That `--remote` string is exactly as unfriendly as the
|
|
212
|
+
> `--listen` one was, and it has no managed spelling. It is deliberately left alone: it is how
|
|
213
|
+
> an operator attaches their OWN terminal, not how a sibling is opened, and `entwurf_fresh_call`
|
|
214
|
+
> already spells it internally for the siblings it launches. Whether the operator-facing half
|
|
215
|
+
> deserves its own verb is open.
|
|
216
|
+
|
|
155
217
|
### From npm — project-local install
|
|
156
218
|
|
|
157
219
|
```bash
|
|
@@ -259,7 +321,7 @@ repair surface: each unit has its own installer, doctor, and inverse.
|
|
|
259
321
|
- **Antigravity / agy** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — `install-agy-bridge`, `install-agy-statusline`, `install-agy-hooks`, each with a matching `doctor-agy-*`.
|
|
260
322
|
- **GitHub Copilot CLI** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — four independent units, four independent failure modes: `install-copilot-bridge` (birth: garden id + who-sent, on the first prompt), `install-copilot-mcp` (the entwurf tool hand, where `entwurf_inbox_read` lives), `install-copilot-receive` (the receiver extension: doorbell + receiver marker), `install-copilot-statusline` (optional for a manual citizen, required for supported fresh) — each with a matching `doctor-copilot-*` and `uninstall-copilot-*`.
|
|
261
323
|
- **OMP (`omp`)** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — four units, in-process extensions rather than launchers: `install-omp-bridge` (birth: the `mode === "tui"` visible host, its garden id on the status line, and who-sent), `install-omp-mcp` (the omp-native `entwurf-bridge` entry), `install-omp-config` (the one operator setting `tools: xdev: false`, without which the vendor mounts MCP tools as `xd://` devices the model cannot call), `install-omp-receive` (the receiver extension: mailbox watch + announce-only doorbell) — each with a matching `uninstall-omp-*`, and a `doctor-omp-*` for all but the setting, whose runtime axis `doctor-omp-mcp` owns. The setting writer owns exactly the lines it adds and refuses an explicit operator `tools: xdev: true` by name rather than overwriting it.
|
|
262
|
-
- **OpenAI Codex CLI** (supported in 0.21.0 on Linux; macOS NOT CERTIFIED — pending physical host) — `install-codex-birth` publishes the `SessionStart` declaration into `$CODEX_HOME/hooks.json` with its launcher closure under `$XDG_DATA_HOME/entwurf/codex-birth`, all operator-owned; the vendor trust receipt
|
|
324
|
+
- **OpenAI Codex CLI** (supported in 0.21.0 on Linux; macOS NOT CERTIFIED — pending physical host) — `install-codex-birth` publishes the `SessionStart` declaration into `$CODEX_HOME/hooks.json` with its launcher closure under `$XDG_DATA_HOME/entwurf/codex-birth`, all operator-owned; it owns **that declaration, not the file** — another integration's `SessionStart` group coexists (the vendor keys trust per `<path>:<event>:<group>:<handler>`), so install appends, uninstall splices only ours out, foreign bytes survive both, and `doctor-codex-birth` reports neighbours in a FOREIGN section it certifies nothing in; the vendor trust receipt is read at the index our declaration was measured at, is the operator's single answer, and `doctor-codex-birth` reports it as its own axis (present, or red with the exact instruction — never computed, never written); `install-codex-mcp` owns `[mcp_servers.entwurf-bridge]`, including the `env_vars` boundary for `CODEX_HOME`, Entwurf garden/control roots, and the app-server's `TMUX`/`TMUX_PANE`; `install-codex-statusline` owns `thread-title` in `tui.status_line`; `install-codex-terminal-title` owns `thread-id` in `tui.terminal_title`. Each has a matching doctor and inverse. **One Codex precondition is the vendor's own and no installer supplies it: the directory a sibling starts in must already be answered in this Codex.** A direct consent decision is recorded per exact directory on this rail — not the parent, not the git root — and a directory the vendor has no answer for at all opens a consent screen instead of running a first turn, so `entwurf_fresh_call` prints `codex-launch-cwd-undecided` and opens the window anyway; that screen is self-repairing, and answering it once teaches the vendor the directory for good. **Nothing here refuses a launch** — the note is a diagnostic, and the one caller that treats it as a precondition is `smoke-codex-fresh-live`, where nobody is at the keyboard. Two neighbouring cases read differently: a directory answered `untrusted` is not noted at all (the vendor skips its screen on this rail, so that turn starts), and a directory INSIDE an explicitly untrusted project is noted as `codex-launch-cwd-untrusted-ancestor` because the vendor answers that one with an error naming the repository root rather than a prompt. The check is a narrow local read of the operator's own config, never the vendor's verdict: the vendor also consents through project layers it reads from its app-server, and everything the check cannot see stays silent. The operator owns the app-server and seats it wherever they like; Entwurf never creates or supervises it. Omitted fresh placement follows the CALLER, never the backend being opened: an explicit `placement` always wins, then a Codex CALLER opens beside its own TUI pane — located by the `thread-id` that pane's terminal title carries, with 0 or 2+ matching panes refused and no fallback — then the caller's own session. (#95 first shipped a fixed `codex` home for omitted-placement Codex TARGETS; #95 D1 retired it on 2026-09-16 once the anchor made the room unnecessary.) A pane title is a placement input only, never an address, liveness or delivery fact. Exact 0.153.4 source's missing per-client carrier still bounds placing a sibling beside a TUI whose thread nobody named as unsupported.
|
|
263
325
|
|
|
264
326
|
Run them as `entwurf <command>`. Which unit a doctor's refusal names, and the clean-host
|
|
265
327
|
walk-through for each harness, live in [docs/setup-clean-host.md](./docs/setup-clean-host.md).
|
|
@@ -454,8 +516,8 @@ The Claude ACP backend keeps its native model / API / tools; entwurf shapes only
|
|
|
454
516
|
|
|
455
517
|
Codex is not an ACP backend here. Its native-push lane, supported in 0.21.0, uses `SessionStart`
|
|
456
518
|
for birth, strict request `_meta` for who-sent, and a loaded app-server thread as the live route.
|
|
457
|
-
|
|
458
|
-
|
|
519
|
+
A Codex CALLER with placement omitted opens its sibling beside its own TUI pane, matched by the
|
|
520
|
+
`thread-id` its terminal title carries; a pane whose thread nobody named is never inferred.
|
|
459
521
|
|
|
460
522
|
Antigravity is also not an ACP backend. It is a native-push citizen: `PreInvocation` supplies birth/sender identity, `entwurf_v2` probes and direct-injects replies into the live conversation, and no mailbox/receiver marker is involved.
|
|
461
523
|
|
|
@@ -473,7 +535,7 @@ pnpm run check:full # full deterministic floor (adds the her
|
|
|
473
535
|
./run.sh check-agy-sender-identity # record-backed pid/start-key sender identity
|
|
474
536
|
./run.sh check-codex-native-push # deterministic Codex UDS probe + no-replay queue contract
|
|
475
537
|
./run.sh smoke-codex-config-state # Codex MCP/status-line install, doctor, and inverse
|
|
476
|
-
./run.sh check-codex-birth-hook # sandboxed payload + declaration: one V3 per top-level thread, the envelope's own axes, no markers
|
|
538
|
+
./run.sh check-codex-birth-hook # sandboxed payload + declaration: one V3 per top-level thread, the envelope's own axes, no markers, and the declaration leaf (normalized digest, selection by launcher command, byte-preserving splice)
|
|
477
539
|
|
|
478
540
|
# source-maintainer only — qualification snapshots the git work surface, and both
|
|
479
541
|
# commands are source-contract gates rather than installed operator checks:
|
|
@@ -487,16 +549,18 @@ LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live
|
|
|
487
549
|
# Codex loaded-thread probe (pre-amendment evidence; not first-release acceptance):
|
|
488
550
|
LIVE=1 CODEX_LIVE_THREAD_ID=<threadId> ./run.sh smoke-codex-native-push-live
|
|
489
551
|
|
|
490
|
-
# Codex
|
|
491
|
-
#
|
|
492
|
-
#
|
|
552
|
+
# Codex caller-seat acceptance. The operator-owned app-server must sit in a session OTHER than
|
|
553
|
+
# the one the Pi/Codex pair runs in — that separation is what tells the caller-pane anchor apart
|
|
554
|
+
# from the app-server's inherited environment. This smoke ALSO needs its one stable launch
|
|
555
|
+
# directory answered once (manual step 3 above); VERIFY.md's Codex section owns that procedure,
|
|
556
|
+
# including how to derive the exact path instead of retyping it. Record all four coordinates:
|
|
493
557
|
LIVE=1 ENTWURF_CODEX_APP_SERVER_PID=<existing-app-server-pid> \
|
|
494
558
|
ENTWURF_CODEX_FRESH_MODEL=<codex-model> \
|
|
495
559
|
ENTWURF_CODEX_FRESH_PI_MODEL=<pi-model> \
|
|
496
560
|
./run.sh smoke-codex-fresh-live
|
|
497
|
-
# This accepts only a real visible Pi → visible Codex → visible Pi run: initial Pi
|
|
498
|
-
# the
|
|
499
|
-
#
|
|
561
|
+
# This accepts only a real visible Pi → visible Codex → visible Pi run: initial Pi in a session
|
|
562
|
+
# other than the app-server's, then Codex and its outbound Pi beside it. A fixture first leg may
|
|
563
|
+
# collect receipts but does not satisfy the topology. No arbitrary attached-TUI parity is claimed.
|
|
500
564
|
|
|
501
565
|
# ACP plugin LIVE acceptance — need the operator's local Claude auth/credit:
|
|
502
566
|
LIVE=1 ./run.sh smoke-acp-socket-citizen-live # turn-free socket citizenship (S1)
|
|
@@ -572,7 +636,7 @@ A two-pane recording covers the pre-0.12 v1 surface end-to-end — sibling resum
|
|
|
572
636
|
|
|
573
637
|
Live peer messaging carries a sender envelope `{ sessionId, agentId, cwd, timestamp, origin?, replyable? }`; `entwurf_self` returns that authoritative envelope for the current pi session or trusted meta-session. Plain external MCP hosts are non-replyable. A garden-native meta-session carries a trusted `meta-session` envelope, but **`replyable` is a fact its own rail decides, not a consequence of being trusted** — a self-fetch citizen needs a live armed receiver, a native-push citizen needs an alive adapter probe, and a pi session needs its control socket. `entwurf_self` also reports which rail a meta-session reply would ride, because a native-push citizen has no mailbox to name. `wants_reply` is an etiquette marker rendered as a `(wants reply)` badge — not a transport contract, no wait, no polling. **v2 never gates on it:** a `wants_reply` from an external/non-replyable caller is passed through and surfaced honestly beside that sender's `replyable: false`, not rejected — the decider routes on target + intent, never on sender replyability. (The retired v1 `entwurf_send` did reject it; that behaviour went with the verb.)
|
|
574
638
|
|
|
575
|
-
In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / native-push (Antigravity, Codex), and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — it launches one of five fixed backends (`pi`, `claude-code`, `copilot`, `omp`, `codex`) as a visible window on the operator's own tmux server, requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. Omitted placement
|
|
639
|
+
In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / native-push (Antigravity, Codex), and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — WHERE it opens is a fact about the calling process, not a parameter: inside herdr (`HERDR_ENV=1`) it opens a NEW TAB in the caller's own workspace, without taking focus, and admits only `pi` and `claude-code`, refusing `placement` by name; everywhere else it launches one of five fixed backends (`pi`, `claude-code`, `copilot`, `omp`, `codex`) as a visible window on the operator's own tmux server. There is no fallback between the two — an incomplete herdr context is refused by its herdr reason rather than quietly opening a tmux window nobody inside herdr can see. Everything that follows in this paragraph describes the tmux rail; the herdr rail's coordinates, refusals and reclaim are owned by [docs/herdr-launch-rail.md](./docs/herdr-launch-rail.md). On tmux it requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. Omitted placement follows the CALLER rather than the backend being opened: a Codex CALLER opens its sibling beside its own TUI pane, located by the `thread-id` that pane's terminal title carries (0 or 2+ matching panes reject with no fallback); every other caller opens in its own seat. (0.21.0 shipped a backend-selected default instead — an omitted-placement `codex` TARGET resolved a fixed operator-owned session named `codex` — and #95 D1 retired it on 2026-09-16.) Any of the five can still be sent to an operator-owned existing seat with an explicit `placement.tmuxSession`, and named `claude`/`pi` seats remain available that way, but they are not omitted-placement defaults. Read tmux here as the VISIBLE LAUNCH SEAT, never as an address: the garden id is the address, and it arrives in the callback envelope. Sibling symmetry means each backend keeps its own transcript, auth, native tools and visibility — not that the transports are made identical. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
|
|
576
640
|
|
|
577
641
|
### `entwurf_v2` — canonical dispatch verb
|
|
578
642
|
|
|
@@ -591,7 +655,7 @@ In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes sev
|
|
|
591
655
|
|
|
592
656
|
**`entwurf_v2` is the canonical surface for garden-id delivery.** When you have a garden id and want to reach whoever it names — message, reply, or hand-off — `entwurf_v2` is the one surface that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, Copilot or OMP, or native-push Antigravity or Codex, and routes correctly; *when unsure which transport, use `entwurf_v2`*. This prevents callers from guessing a rail from the shape of an id.
|
|
593
657
|
|
|
594
|
-
What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb.
|
|
658
|
+
What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb. Inside herdr it opens a Pi or Claude Code sibling in a new tab in the caller's own workspace; everywhere else it opens one of five fixed runtimes — Pi, Claude Code, Copilot, OMP or Codex — visibly on the caller's tmux server. The rest of this paragraph is the tmux rail. Omitting placement follows the CALLER rather than the backend being opened: an explicit seat always wins; then a Codex CALLER opens beside its own TUI pane, found by the `thread-id` in that pane's terminal title (0 or 2+ matching panes reject, with no fallback); then the caller's own session. An explicit `placement.tmuxSession` sends ANY of the five to an existing named session instead (see the `entwurf_fresh_call` paragraph above for the owning detail). Each launch carries a required explicit model passed in that runtime's measured CLI dialect and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. Copilot opens through `entwurf copilot`, never the bare vendor; OMP is the opposite — the bare `omp` runtime with no positional prompt at all, because that vendor connects its MCP tools after the session has started, so the task rides a two-stage `--entwurf-bootstrap` payload the installed birth extension releases once the callback tool has actually answered. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
|
|
595
659
|
|
|
596
660
|
A live pi target is *reached* over its control socket, but the socket is dispatch-internal transport, never identity (#50 C4). A control socket that no meta-record claims — a pre-record-era resident, an unreadable store, or a stale/planted file — is refused for **every** intent as `record-less-socket`, and the reject names the fix (restart the resident so `session_start` births its record, or quiesce and run the fresh-cut). `entwurf_peers` reports the same state as an aggregated `record-less-socket` diagnostic rather than a peer row.
|
|
597
661
|
|
|
@@ -605,6 +669,32 @@ A `--entwurf-control` session needs **no special launcher** (#50 C2): pi mints i
|
|
|
605
669
|
pi --entwurf-control
|
|
606
670
|
```
|
|
607
671
|
|
|
672
|
+
**Opening pi inside herdr.** herdr starts a supported agent with its canonical executable and no
|
|
673
|
+
extra argv — measured on herdr 0.9.0, `herdr agent start <name> --kind pi --pane <id>` runs plain
|
|
674
|
+
`pi`. Citizenship is argv-gated (`pi-extensions/entwurf-control.ts`), so a pi opened that way is a
|
|
675
|
+
running pi and *not* a garden citizen: no record, no control socket, no garden id. Pass the flag
|
|
676
|
+
through herdr's own argv passthrough:
|
|
677
|
+
|
|
678
|
+
```bash
|
|
679
|
+
herdr agent start <name> --kind pi --pane <pane-id> -- --entwurf-control
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
A sibling that entwurf itself opens already carries the flag; this is only for a pi you start from
|
|
683
|
+
herdr by hand. The claude axis needs nothing extra — its record is minted by the meta-bridge plugin's
|
|
684
|
+
own `SessionStart` hook, which entwurf installs and herdr does not touch. Where such a citizen is
|
|
685
|
+
visible is reported as evidence in `entwurf_peers` (`placement=herdr <pane>`), never as an address:
|
|
686
|
+
[`docs/mux-launch-rail.md`](./docs/mux-launch-rail.md) §7-a.
|
|
687
|
+
|
|
688
|
+
The wiring those two axes need does not have to be installed by hand on a Herdr host: the Entwurf
|
|
689
|
+
Herdr plugin's install-time build reads Herdr's own `integration status`, and for every harness in
|
|
690
|
+
`{pi, claude-code}` that Herdr reports as integrated it acquires an Entwurf runtime at a stable
|
|
691
|
+
Entwurf-owned address and registers the user-scope wiring against it — no Entwurf clone, no manual
|
|
692
|
+
`npm install`. `herdr plugin install junghan0611/entwurf/plugins/herdr` is the whole command, every
|
|
693
|
+
reinstall is the refresh trigger, and `entwurf herdr-plugin-deactivate` is the explicit inverse
|
|
694
|
+
(Herdr's own uninstall removes only its checkout and calls no cleanup hook).
|
|
695
|
+
[`plugins/herdr/README.md`](./plugins/herdr/README.md) owns that lane, including which acquisition
|
|
696
|
+
source the committed lock names.
|
|
697
|
+
|
|
608
698
|
**Resuming an existing garden session.** Use `entwurf_resume_call {target}`. It reopens a DORMANT pi citizen under the SAME garden id in a visible window in the caller's own tmux session, resolving the transcript, model, provider and cwd from the record — so it takes only the target id, and it runs no turn: the window comes back with the conversation and waits, and talking to it is still `entwurf_v2`. Two receipts arrive and mean different things: a LAUNCH receipt (tmux made a window and was asked to start pi) and an OBSERVATION receipt (the control socket answered under the same id, or `resume-unobserved`). Unobserved is a real outcome, not an error to retry — the window is visible, so read it. A citizen that is already live is refused; so is a non-pi target (`target-not-pi`), because only pi stands a control socket up. The predecessor, `entwurf_v2 intent=owned-outcome`, resumed by launching a hidden window-less background child and was withdrawn under the visible-first rule; delivery still starts no process. Identity preconditions live in `resume-launch-identity.ts`, gated by `check-resume-launch-identity`.
|
|
609
699
|
|
|
610
700
|
**Starting a new session in-process — pi's own `/new`.** Since the #50 C2 cut there is nothing to replace it with: `/new`, `/fork`, `/clone` and RPC session replacement are pi's again. The replacement session fires `session_start`, which upserts its own meta-record and rebinds the control socket to that record's garden id; the old socket is dropped. pi's session id (a uuidv7) is recorded as the citizen's `nativeSessionId` and is never an address. Gate: `run.sh smoke-resident-garden-guard` REPLACEMENT section (0-token RPC E2E).
|