Repository navigation
Description
Found while working barelyworkingcode/relay#531.
User story
As the owner, I want the verify harness to adopt a real idle Pi session through Eve's Adopt button, so that a Pi adoption regression shows up in a journey and not only in a hand check.
Acceptance criteria
- The journey starts installed Pi in tmux under a Pi-enabled project and waits for its first reply.
- The idle Pi row shows Adopt; one tap answers 201.
- The outside row leaves the rail and the Pi pane exits.
- A second turn lands in the same transcript file, and no second file appears.
- Audit holds the
session_adoptintent and an ok completion. - When stack.json names
pi_agent_dir, the outsidepistarts withPI_CODING_AGENT_DIRfrom it and the broker key, as the outside Claude does.
What I'd notice
Nothing in the app; the harness gains one journey.
Context
A scratch version passed on barelyworkingcode/relay#531's head (branch scratch/r531-pi-journey, local to the devbox, with its body in /tmp). It needs a Pi-enabled project in the stack world; project creation is presence-gated, so a stack seed must carry it. Eve's adopt route returns no agent field.
Contract
Quality check: one role, one goal, one reason; problem-oriented. It passes. Scope: widened on 2026-10-21 by the coordinator (owner asked) to a twin outside-adopt-codex journey of the same shape. Criteria P1 to P6 are the issue's Pi lines in order; C1 to C6 are the Codex lines this contract adds.
Start simple: two journeys in the existing outside-journey file, built on the outside-adopt pattern, and one word in the stack seed. No new file, flag, env name, route or product code.
Provisioning the Pi project (decision)
The stack seed carries it. stack-up writes relay's settings.json directly and raises no presence prompt (devboxWorld docs/WORLD.md, "Stacks"). A stack run has no console, so Eve's harness cannot create a project there: project create is presence-gated. Acme Corp already allows codex in the seed; it gains pi. The journeys then use Acme Corp scratch folders, as outside-adopt does. The seeded pi template grants ~/.pi read-write, which covers Pi's default transcript root, so the adopt's transcript grant check passes with no new grant.
Prerequisite P is merged: barelyworkingcode/devboxWorld#52 made Acme Corp allow pi in the seed. P6 also needs barelyworkingcode/devboxWorld#51, which writes pi_model and pi_agent_dir into stack.json.
Paths
- Simple: the owner taps Adopt on an idle Pi or Codex row in the Chief of Staff rail (G6.107). The journeys drive that tap.
- Power:
node devboxverify/main.js --checkout <wt> --stack <stack.json> --only outside-adopt-pi(oroutside-adopt-codex). No new flag; the scratch'sR531_PI_PROJECTis not carried over.
Interfaces (all in devboxverify/journeys-cos-outside.js)
newTools(env, id, { agent = 'claude' } = {}):agentnames the binary the problem check requires (claude,pi,codex). The default keeps the four existing callers unchanged.function piTranscriptDir(realDir):<home>/.pi/agent/sessions/--<realDir without its leading "/", each "/", "\" and ":" as "-">--. The one rule for the reads and the cleanup.async function adoptRow(page, seen, base): waits for an idle row whosedirectoryholdsbase, checksrail-adopt-<id>is visible, taps it, and returns{ listed, name, sentAt, answer: { status, body } }, or{ verdict, detail }on a problem. Only the two new journeys call it.outsideAdoptis not edited (deliberate: no change to a passing journey's assertions).async function adoptAudit(env, sentAt, agent):relay audit --event session_adopt --json --tail 200; returns{ intent, ok, args }over rows sincesentAt - 1000whoseargs.agent === agent.async function sendFromThread(page, sessionId, text): tapsrail-agent-<sessionId>, fillschat-input, clickschat-submit, and returns whether amessage-assistantholding the text's marker shows.- Journeys
outsideAdoptPi(id: 'outside-adopt-pi') andoutsideAdoptCodex(id: 'outside-adopt-codex'): eachtimeoutMs: 480000,areas: ['chief-of-staff'],needs: ['project:acme'], noclaudeflag, noscreenflag. Both journeys run no Claude, so neither registration injourneys-cos-outside.jscarriesclaude: true(Amendment 2). Registered injourneys.jsaftercosOutside.outsideAdopt. - Outside Pi start:
tmux new-session -d -s verify-<nonce>-outside-adopt-pi -c <dir> "<piShellPrefix(env, piTranscriptDir(realDir))>pi 'Reply with exactly: <marker>-1'"; every later target is=<name>. devboxverify/main.jsreadStack: readspi_modelandpi_agent_diraspiModelandpiAgentDir, eachnullwhen absent. Both or neither;pi_agent_diris an absolute path; both needclaude_base_urlandclaude_token_file. Otherwise a usage error names the key.journeyStackkeeps both.devboxverify/outside-claude.jspiShellPrefix(env, sessionDir):''whenenv.stack.piAgentDiris null. Otherwise the same token-file guard asshellPrefix, thenPI_CODING_AGENT_DIR=<quoted piAgentDir> PI_CODING_AGENT_SESSION_DIR=<quoted sessionDir> PI_STACK_KEY="$(cat <quoted claudeTokenFile>)". The token never enters argv. The session dir keeps the transcript where relay's adopt discovery reads it.- Outside Codex start:
<home>/.codex/packages/app-server-daemon/current/bin/codex --remote unix:// -C <dir> -a on-request -s read-only -c projects."<dir>".trust_level="trusted" 'Reply with exactly: <marker>-1'in tmux; targets are=<name>:. Deliberate: the daemon's own binary, because a terminal codex of another version runs its own server and lists no row.
Acceptance mapping
| # | Criterion | Satisfied by |
|---|---|---|
| P1 | Installed Pi starts in tmux under a Pi-enabled project; the journey waits for its first reply | W0, the Pi start, then one transcript under piTranscriptDir holds an assistant record with stopReason: "stop" and <marker>-1 |
| P2 | The idle Pi row shows Adopt; one tap answers 201 | adoptRow; 201 with a sessionId |
| P3 | The Outside row leaves the rail; the Pi pane exits | Outside rows for base reach 0; tmux has-session -t =<name> fails |
| P4 | A second turn lands in the same transcript file; no second file | sendFromThread with <marker>-2; the same file holds an assistant record with <marker>-2 and grew; piTranscriptDir holds exactly one .jsonl |
| P5 | Audit holds the intent and an ok completion | adoptAudit(…, 'pi'): intent and ok |
| P6 | With pi_agent_dir, the outside Pi uses it and the broker key |
piShellPrefix in the Pi start; the first assistant record in the transcript has provider: "relay" and model equal to env.stack.piModel. The static selftest checks readStack and piShellPrefix |
Codex criteria:
- C1. The journey starts the daemon's own codex with
--remote unix://in tmux in an Acme Corp scratch folder, and waits for its first reply. - C2. The idle Codex row shows Adopt, and one tap answers 201.
- C3. The Outside row leaves the rail, and the terminal pane keeps running with the same pane pid.
- C4. The codex control socket keeps the same device and inode from before the start to after the second turn.
- C5. A turn sent from Eve's thread gets a reply in Eve and shows in the terminal pane.
- C6. Audit holds a
session_adoptintent withmethod: "attach"andrelay_sandbox: false, and an ok completion.
| # | Satisfied by |
|---|---|
| C1 | W0, the Codex start; the pane shows <marker>-1 twice and one more Worked for line than before |
| C2 | adoptRow |
| C3 | Outside rows reach 0; tmux has-session succeeds and display-message -p '#{pane_pid}' is unchanged |
| C4 | fs.stat of <home>/.codex/app-server-control/app-server-control.sock (dev, ino), before and after |
| C5 | sendFromThread with <marker>-2; the pane shows <marker>-2 |
| C6 | adoptAudit(…, 'codex') with args.method and args.relay_sandbox checked |
Waits
Waits: Task A, both journeys:
- W0, preflight, no wait. BLOCKED, never FAIL:
env.cosSetupProblemor no outside inventory (asoutside-adopt); tmux,pior the daemon's codex binary absent;GET /api/terminal/templates?project=<acme id>lists nopi("Acme Corp does not allow the pi template; the stack seed needs barelyworkingcode/devboxWorld#52") or nocodex; the control socket absent ("no codex daemon"); for Pi,env.stack.claudeModelset andenv.stack.piAgentDirnull ("the stack names no pi_agent_dir; it needs barelyworkingcode/devboxWorld#51"). - First outside reply: none possible: model output in a vendor TUI has no hook. Bounded poll of the Pi transcript, or of the Codex pane, within
REPLY_WITHIN_MS. - Codex folder-trust prompt: none possible: TUI text. Bounded poll of the pane, then one Enter.
- Idle row: the
cos_snapshotandcos_outside_sessionsframes thatframes(page)captures. - Adopt answer:
page.waitForResponseon eve'sPOST /api/outside-sessions/:id/adopt. - Row leaves: a locator count.
- Pi pane exit: none possible: tmux tells no other client that a session ended. Bounded poll of
has-session, asoutside-adoptdoes. - Second turn: the
message-assistantlocator in Eve. Then the Pi transcript or the Codex pane: none possible (a file write or TUI text after the reply has no hook); one bounded poll each. - Audit rows: none possible: relay writes the completion after it answers. A bounded poll of the CLI, as
outside-adoptdoes. - Cleanup:
deleteSession's completion and the tmux CLI exit.
Cleanup
- Both: delete the adopted session (registered before the tap); end tmux (Codex: two
C-c, thenkill-session); the existingnewToolscleanup removes the scratch folder. - Pi: after the delete, remove
piTranscriptDir(dir)only when its name holdsverify-<nonce>-; refuse any other path. - Codex: nothing in
~/.codexis removed or archived (deliberate: the daemon owns that store).
File ownership
Task A (dev), exclusive:
devboxverify/journeys-cos-outside.js: the helpers and the two journeys above;newToolsgains only the option. Task A removesclaude: truefrom theoutsideAdoptPiandoutsideAdoptCodexregistrations (Amendment 2).devboxverify/journeys.js: two registration lines aftercosOutside.outsideAdopt, nothing else.devboxverify/main.js:readStackreadspi_modelandpi_agent_dir;journeyStackkeeps them. Nothing else.devboxverify/outside-claude.js:piShellPrefixand its export. Nothing else.
Task B (test writer), exclusive: test/static/devboxverify-stack-selftest.js: readStack for the two keys (present, absent, one without the other, a relative path, no broker) and piShellPrefix (empty without a stack dir; the token only as $(cat …)).
test/static/devboxverify-login-selftest.js, section 8 ("marks") only:outside-adopt-piandoutside-adopt-codexmust not carryclaude: true; every otherchief-of-staffjourney still must. Nothing else in the file (Amendment 2).devboxverify/README.md: two feature-map paragraphs after outside-adopt, in its style (BLOCKED rules, cleanup, the run-alone line).test/static/frozen.json: twojourneysentries afteroutside-adopt.docs/FEATURES.md: G6.107's journey cell addsoutside-adopt-piandoutside-adopt-codex; nothing else.
The journeys are the tests for P1 to P5 and C1 to C6; Task B covers P6's helpers. Task C: execution and evidence only. #610 edits journeys.js, README.md, frozen.json and docs/FEATURES.md in other hunks; the second to merge rebases on main. A file outside this list goes back to the planner before the edit.
Test surface
Journeys on a stack, plus the static selftest for P6 (check:static). No Playwright: no product file changes. The pre-push check:static and check:coverage cover frozen.json and the feature map.
Evidence lines: Harness weakening: none (additions only); Journey: outside-adopt-pi PASS, outside-adopt-codex PASS; Break-and-restore: copy public/agent-board.js aside, make _adoptButton's onClick return at once, run --only outside-adopt-pi on a stack, confirm FAIL at P2, restore byte for byte (cmp with the copy).
Tasks
- Prerequisite P: barelyworkingcode/devboxWorld#52, merged.
- barelyworkingcode/devboxWorld#51 merged (stack.json
pi_model,pi_agent_dir). - Task A (dev): the helpers, both journeys, registration, README,
frozen.json, the G6.107 cell,readStackkeys,piShellPrefix. - Task B (test writer): the selftest cases for P6.
- Task C, last (verification): on the PR head with main merged in:
npx playwright test; unlock the devid keychain;npm ciin the eve worktree;stack-up e609 --relay <relay main checkout> --eve <eve worktree>(devboxWorld at main, with test(e2e): wait for initApp after navigation (#44) #52 and Flaky e2e: terminal-reconnect openTerminal times out waiting for activeTerminalId #51);node devboxverify/main.js --checkout <wt> --stack <stack.json>;stack-down e609; break-and-restore on a fresh stack. No real-app (nothing user-visible). No devlock. PostVerified: <head sha> · repo-suite: <result> · devboxverify: <result> · log: <path>.
Budget
Task A: 7 files, about 410 to 510 added lines (journeys and helpers 330 to 400, README about 45, readStack about 12, piShellPrefix about 15, the rest about 6). Task B: 2 files, about 45 lines (the login selftest about 3). Going past either goes back to the planner.
Merge order
barelyworkingcode/devboxWorld#52 (merged), then barelyworkingcode/devboxWorld#51, then this issue. Independent of #610 apart from the shared-file rebase.
Open questions
- BEYOND:
outside-adopt-codexspends the box's codex account on two short turns in every eve stack run and needs the per-user codex daemon up. Recommendation: run it in every stack run on the box's codex login until Serve the Responses API for a model that speaks only Chat Completions relayLLM#36 gives Codex a local Responses API (devboxWorld#51 keeps Codex on its login); a missing daemon reports BLOCKED, never FAIL. - INSIDE: the Pi second turn goes through Eve's thread, not
relay session messageas the scratch did. Recommendation: Eve's thread, the door a person uses. A failure there that the CLI does not show is an Eve defect, filed withFound while working #609. - Superseded 2026-10-21: the outside Pi uses the stack's broker (P6), with the interface barelyworkingcode/devboxWorld#51 pins.
- INSIDE: each Codex run leaves its rollout and a folder-trust entry in
~/.codex. Recommendation: accept; the harness writes nothing into codex's store. Task C records whether the thread stays loaded in the daemon after cleanup; if it does, file afrom-devissue, because relay lists at most 256 loaded threads. - INSIDE: the Codex journey asserts adoption only. The scratch's interrupt, approval-decline and delete-detaches steps stay out; relay's e2e tests for G1.53 prove them. Recommendation: accept.
Amendment 1 (2026-10-21): P6 added from the coordinator's decision on devboxWorld#51 Q2; Task B and the main.js and outside-claude.js edits added for it; Prerequisite P marked merged as devboxWorld#52; Q1 now names relayLLM#36; Q3 superseded.
Amendment 2 (2026-10-21): Task B may edit test/static/devboxverify-login-selftest.js section 8 only: the two new journeys must not carry claude: true, since they run no Claude. Task A removes claude: true from both registrations. Nothing else changes.
Activity
barelyworkingcode commentedon Oct 22, 2026
Coordinator approval: owner asked, 2026-10-21. It belongs to the Pi and Codex adoption epic.
barelyworkingcode commentedon Oct 22, 2026
Amendment 1 (2026-10-21), from the coordinator's decision on barelyworkingcode/devboxWorld#51:
pi_agent_dir, the outsidepistarts withPI_CODING_AGENT_DIRfrom it and the broker key, as the outside Claude does."readStack(pi_model,pi_agent_dir) indevboxverify/main.jsandpiShellPrefixindevboxverify/outside-claude.js. Task B (test writer) owns the selftest cases.barelyworkingcode commentedon Oct 22, 2026
Contract Amendment 2 (2026-10-21): Task B may edit section 8 of
test/static/devboxverify-login-selftest.js. The test asserts thatoutside-adopt-piandoutside-adopt-codexcarry noclaude: true. Task A removes the flag from both registrations. No other change.Merge pull request #614 from barelyworkingcode/feat/609-adopt-journeys