Skip to content

Build 5/7 — Session naming: official names, immutable labels, self-report only for unnamed Codex #78

Closed

Description

@Simon-AI-coding

Part of #67 · Build group 5 of 7 from the signed port table (amended 2026-08-25: Codex self-report route dropped). Revised after the Codex consistency review.

Rows covered

P10 Stable Session naming — adapt Claude to the official name; adapt Codex to daemon Thread.name with a <project> · <short thread id> display fallback.

Legacy citations (verbatim from the table)

legacy@1d32845:bridge/hook.py:215-253; legacy@1d32845:bridge/labels.py:20-70,73-106 (<project> · <title>, Git common-dir project name, immutable once accepted, never a target); legacy@1d32845:bridge/daemon.py:1504-1544; legacy@1d32845:bridge/store.py:1875-1902 (left behind). Official sources: claude agents --json name; Codex Thread.name (app-server-protocol/src/protocol/v2/thread.rs:2185-2255 @3a469a2).

Shape

  • seams/identity.py SessionLabel stays the value (:1-15,28-82 already rejects empty/malformed halves). A label is never a target (seams/identity.py:3-10); targetability depends only on reach and child (Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74) — never on whether a name exists.
  • adapters/agent/_naming.py (shared private module), ≤ 100 lines, pure: def compose(project_name: str, title: str) -> SessionLabel with legacy's title validation (non-empty, single line). The I/O boundary is the caller: each lane adapter resolves project_name from the Session's workspace via git rev-parse --git-common-dir (as legacy labels.py:20-70) inside its inspect, and passes the string in.
  • Claude: title = the roster name. Codex attached: title = daemon Thread.name when present, else <short thread id> (first 8 chars) — composed locally, nothing asked of the Session. Unattached rows are labelled the same way from their provenance facts; they are still not targets, for reach reasons only.
  • Core applies a label exactly once per exact SessionTarget for the row's lifetime. Two rows with an identical label in one project are not disambiguated by Core: match_label() (core/sessions.py:120-150) keeps refusing ambiguity and the user is asked; the roster payload may show first_seen order for display only.
  • Children (Build 6/7 — Child-process rule: seen in the roster, never spoken to, never announced #79) get no invented name — table evidence risk 1.

Left behind

Transcript-derived ai-title, mutable label history, durable label rows, the legacy self-report ingress (hook.py:215-253, daemon.py:1504-1544 — cited as behaviour evidence for immutability only).

Red first

Acceptance step stable name red before, green after: the same name across three consecutive status calls and across a Stop, both lanes.

Done when

Step green; pytest + ruff green.

Blocked by

#74.

Activity

  1. Simon-AI-coding commented on Aug 26, 2026

    @Simon-AI-coding
    OwnerAuthor

    Advisor note from #74 (2026-08-26)

    Decided while #74 was locking the Core Session row:

  2. Simon-AI-coding commented on Aug 26, 2026

    @Simon-AI-coding
    OwnerAuthor

    Advisor note (2026-08-26) — an exit item inherited from #76

    #76 exited with claude progress green and codex progress gated here: the acceptance drives no codex turn until stable name passes (journey.py:433-441), and an unnamed Codex thread reports name: null until this ticket's self-report rule exists. Done-when addition: in this ticket's acceptance run, codex progress is green (run 20260826T070925Z on #76 is the before-evidence). #76 already delivers the daemon join, the thread identity, the reader and the bound; only the name is missing.

    Watch for, unconfirmed: on that run thread/read includeTurns:true failed 3/3 on the daemon-held live thread (list_turns is not supported yet) and succeeded on the same thread once the TUI had exited. If this ticket's run reproduces it, file it then with the mechanism; it would mean the deep read fails exactly while a Session works.

  3. Simon-AI-coding commented on Aug 26, 2026

    @Simon-AI-coding
    OwnerAuthor

    Correction (advisor, 2026-08-26): the before-evidence run for the codex progress exit item is 20260826T074208Z (bundle byte-identical to 2dfcefe, the tree PR #101 merges), not 20260826T070925Z. The two runs are identical step-for-step on both lanes.

  4. Simon-AI-coding commented on Aug 26, 2026

    @Simon-AI-coding
    OwnerAuthor

    Advisor ruling (2026-08-26) — one name field, and no unnamed Sessions outside a repo

    The label/name rename collapses to one field: SessionLabel → SessionName, SessionInspection.name: SessionName | None composed by the lane, core.Session.name the only field (label dropped), match_label → match_name, wire "name" carries <project> · <title>, fallback chain name → target. Freezing: first non-None wins per exact SessionTarget (legacy store.py:1875-1902); a target change (Codex pid-only row gaining its thread id; /new moving a pid to a new thread, observed on #77) re-composes.

    Project half: git common-dir name when the workspace is a repo, else the workspace directory's basename; name=None only when there is no workspace. Adapted from legacy labels.py:28-70, which raised on a non-repo — v1.0 covers every Session the user starts, so a non-repo Session must stay voice-addressable. Same-basename collisions are the existing AmbiguousNameError. The git call lives once in adapters/agent/_project.py, cached per realpath'd workspace; _naming.compose stays pure. Unnamed Codex rows take <project> · <first 8 of thread id> once an id exists (P10); children are unnamed. Catalogue ids renamed with the prose, old→new listed in the resolution.

  5. Simon-AI-coding commented on Aug 26, 2026

    @Simon-AI-coding
    OwnerAuthor

    Advisor (2026-08-27): _naming.compose returns SessionName | None — the body's raising signature is amended. A title that fails legacy's validation (empty, multi-line, carries the separator) yields an unnamed row, logged once, never an exception: the callers are per-row passes on the whole-machine cadence, and one bad string must not cost an enumeration. Adapted from legacy@1d32845:bridge/labels.py:100-105, which raised because its only caller was the self-report ingress. Reviewer finding closed by ruling. Also landed: a Child Process is never named (Core-side, holds regardless of how #79 finds children).

  6. Simon-AI-coding commented on Aug 26, 2026

    @Simon-AI-coding
    OwnerAuthor

    Advisor (2026-08-27) — exit items answered; two reds ruled outside this ticket

    Run 20260826T122216Z (bundle = 76b9c98): codex progress green — #76's inherited exit item is discharged. Codex stable name green as 'workspace-codex · 01a03e08': the shared daemon held the thread ~2 s after the TUI started, before any turn, so the <project> · <first 8 of thread id> rule applied with nothing asked of the Session. Claude stable name now carries the project half.

    list_turns watch item — reproduced once, mechanism corrected, no issue. thread/read includeTurns:true (and thread/resume) answered list_turns is not supported yet at 12:25:59, when the thread had zero turns; after the first turn ended (12:26:17) the deep read succeeded with the TUI still live. So it is a first-read condition on a turnless thread, not "fails while a Session works" as hypothesised on #76/#82. The lane already answers None, logs, and keeps the cheap projection. Closed here; #82 cross-noted.

    Ruled outside #78: codex approval red — Codex raised no permission because the journey's instruction (a Write inside the workspace) is one Codex's default policy does not ask about; acceptance-design gap, filed as a ticket and sequenced before #79, since its green is the proof of #77's Codex approval route. child red both lanes — #79. Probe 0b red (0 remote audio frames in 30 s) — engine-free, unattributed, reported to Simon.

  7. Simon-AI-coding commented on Aug 26, 2026

    @Simon-AI-coding
    OwnerAuthor

    Resolution — PR #106, branch 78-session-naming

    Provenance. Base e2090aa. Standards review 23089836-b11a-4163-a42a-2321b66cb897 (next: fix and stop, all fixed). Spec review c10107ad-b977-4933-8110-b4b76236bd0c — 3 findings, re-reviewed 2 CLOSED / 1 RETAINED, the retained one closed by the advisor ruling below. Acceptance run 20260826T122216Z, bundle byte-identical to 76b9c98, claude 2.1.246 / codex-cli 0.149.1.

    Rulings cited in the code. The label/name collapse into one Session Name, and the non-repository fallback to the workspace basename — adapted, because legacy raised there and v1.0 covers every Session the user starts (2026-08-26). compose answering None rather than raising — the ticket's -> SessionLabel amended, because legacy's caller was one self-report ingress and this one is a per-row pass over the whole machine, where one bad string must not cost an enumeration (2026-08-27). Both are quoted at the code that implements them.

    Shape as built. _naming.compose(project_name, task) -> SessionName | None, pure, 64 lines. _project.ProjectNames is the one place either lane runs git, cached per realpath'd workspace. SessionInspection.name carries the composed value; SessionRegistry freezes the first one per exact SessionTarget, re-composing only when the identity itself changes (#73's thread id arriving, #77's /new). Children are never named — the rule sits in the registry, so it holds however #79 finds them. Left behind as the ticket required: ai-title, mutable history, durable label rows, and the legacy self-report ingress.

    Exit item 1 — stable name, red before and green after, both lanes

    lane before 20260826T074208Z now 20260826T122216Z
    claude PASS, but as the bare 'workspace-claude-25' — no project half PASS as 'workspace-claude · workspace-claude-5b', 3 reads and a Stop
    codex FAIL — "has no name in the roster after 3 reads" PASS as 'workspace-codex · 01a03e08', 3 reads and a Stop

    Exit item 2 — codex progress green

    Red in the before-run (blocked behind the missing name, which stopped the lane at stop notice and skipped the rest). Green here. The codex lane went from 1 PASS / 3 FAIL / 5 SKIPPED to 7 PASS / 2 FAIL; claude is 8 PASS / 1 FAIL.

    Still red, neither this ticket's: child on both lanes (#79, unbuilt) and approval on codex (#105 — codex raised no permission at all for the relayed instruction; approval.none twice in the journal).

    Exit item 3 — the list_turns watch item: reproduced once, not in the shape feared

    From engine-codex/engine.log and journal.jsonl of 20260826T122216Z:

    UTC what
    12:25:53 codex TUI started (pid 78415)
    12:25:55 roster: the harness's oracle has no session id (no rollout — no turn yet), while the engine's roster already names it codex:01a03e08-…:78415. The shared daemon held the thread ~2 s after launch, with an id and name: null.
    12:25:59 thread/read includeTurns:true → list_turns is not supported yet; thread/resume → the same message, the same second
    12:26:00–12:26:17 stable name: three reads and a Stop, all 'workspace-codex · 01a03e08'
    12:26:19 progress green off a successful deep read — with the TUI still live

    Two log lines for the whole run, both at 12:25:59. So it is not "the deep read fails exactly while a Session works": it succeeded with the TUI live and up. The one fact true at 12:25:59 and false afterwards is that the thread had zero turns. read_thread already answers None and logs, and the roster row keeps its cheap projection, so there is no user-visible cost. Ruled no issue (advisor, 2026-08-27) and corrected on #82.

    That same timeline is also why the codex lane could be named before its first turn: the daemon had the thread id when nothing else did, which is exactly what <project> · <first 8 of the thread id> is for.

    Gates. ruff check, ruff format --check, pytest green locally; CI on #106.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions