Skip to content

Add an outside-adopt-pi journey to the verify harness #609

Closed

Description

@barelyworkingcode

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_adopt intent and an ok completion.
  • When stack.json names pi_agent_dir, the outside pi starts with PI_CODING_AGENT_DIR from 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 (or outside-adopt-codex). No new flag; the scratch's R531_PI_PROJECT is not carried over.

Interfaces (all in devboxverify/journeys-cos-outside.js)

  • newTools(env, id, { agent = 'claude' } = {}): agent names 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 whose directory holds base, checks rail-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. outsideAdopt is 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 since sentAt - 1000 whose args.agent === agent.
  • async function sendFromThread(page, sessionId, text): taps rail-agent-<sessionId>, fills chat-input, clicks chat-submit, and returns whether a message-assistant holding the text's marker shows.
  • Journeys outsideAdoptPi (id: 'outside-adopt-pi') and outsideAdoptCodex (id: 'outside-adopt-codex'): each timeoutMs: 480000, areas: ['chief-of-staff'], needs: ['project:acme'], no claude flag, no screen flag. Both journeys run no Claude, so neither registration in journeys-cos-outside.js carries claude: true (Amendment 2). Registered in journeys.js after cosOutside.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.js readStack: reads pi_model and pi_agent_dir as piModel and piAgentDir, each null when absent. Both or neither; pi_agent_dir is an absolute path; both need claude_base_url and claude_token_file. Otherwise a usage error names the key. journeyStack keeps both.
  • devboxverify/outside-claude.js piShellPrefix(env, sessionDir): '' when env.stack.piAgentDir is null. Otherwise the same token-file guard as shellPrefix, then PI_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_adopt intent with method: "attach" and relay_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.cosSetupProblem or no outside inventory (as outside-adopt); tmux, pi or the daemon's codex binary absent; GET /api/terminal/templates?project=<acme id> lists no pi ("Acme Corp does not allow the pi template; the stack seed needs barelyworkingcode/devboxWorld#52") or no codex; the control socket absent ("no codex daemon"); for Pi, env.stack.claudeModel set and env.stack.piAgentDir null ("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_snapshot and cos_outside_sessions frames that frames(page) captures.
  • Adopt answer: page.waitForResponse on eve's POST /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, as outside-adopt does.
  • Second turn: the message-assistant locator 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-adopt does.
  • 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, then kill-session); the existing newTools cleanup removes the scratch folder.
  • Pi: after the delete, remove piTranscriptDir(dir) only when its name holds verify-<nonce>-; refuse any other path.
  • Codex: nothing in ~/.codex is 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; newTools gains only the option. Task A removes claude: true from the outsideAdoptPi and outsideAdoptCodex registrations (Amendment 2).
  • devboxverify/journeys.js: two registration lines after cosOutside.outsideAdopt, nothing else.
  • devboxverify/main.js: readStack reads pi_model and pi_agent_dir; journeyStack keeps them. Nothing else.
  • devboxverify/outside-claude.js: piShellPrefix and 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-pi and outside-adopt-codex must not carry claude: true; every other chief-of-staff journey 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: two journeys entries after outside-adopt.
  • docs/FEATURES.md: G6.107's journey cell adds outside-adopt-pi and outside-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, readStack keys, 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 ci in 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. Post Verified: <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

  1. BEYOND: outside-adopt-codex spends 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.
  2. INSIDE: the Pi second turn goes through Eve's thread, not relay session message as 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 with Found while working #609.
  3. Superseded 2026-10-21: the outside Pi uses the stack's broker (P6), with the interface barelyworkingcode/devboxWorld#51 pins.
  4. 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 a from-dev issue, because relay lists at most 256 loaded threads.
  5. 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

  1. added
    enhancementNew feature or request
    from-devIdea found during development, not asked for by the owner
    and removed on Oct 21, 2026
  2. barelyworkingcode commented on Oct 22, 2026

    @barelyworkingcode
    OwnerAuthor

    Coordinator approval: owner asked, 2026-10-21. It belongs to the Pi and Codex adoption epic.

  3. barelyworkingcode commented on Oct 22, 2026

    @barelyworkingcode
    OwnerAuthor

    Amendment 1 (2026-10-21), from the coordinator's decision on barelyworkingcode/devboxWorld#51:

    • New criterion P6: "When stack.json names pi_agent_dir, the outside pi starts with PI_CODING_AGENT_DIR from it and the broker key, as the outside Claude does."
    • Task A also owns readStack (pi_model, pi_agent_dir) in devboxverify/main.js and piShellPrefix in devboxverify/outside-claude.js. Task B (test writer) owns the selftest cases.
    • Prerequisite P is merged as barelyworkingcode/devboxWorld#52. This issue now merges after barelyworkingcode/devboxWorld#51.
  4. barelyworkingcode commented on Oct 22, 2026

    @barelyworkingcode
    OwnerAuthor

    Contract Amendment 2 (2026-10-21): Task B may edit section 8 of test/static/devboxverify-login-selftest.js. The test asserts that outside-adopt-pi and outside-adopt-codex carry no claude: true. Task A removes the flag from both registrations. No other change.

  5. added a commit that references this issue on Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfrom-devIdea found during development, not asked for by the owner

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions