You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Build 5/7 — Session naming: official names, immutable labels, self-report only for unnamed Codex #78
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 --jsonname; Codex Thread.name (app-server-protocol/src/protocol/v2/thread.rs:2185-2255 @3a469a2).
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.
match_label() and the Stop Notice text fall back label → name → target from Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74 on, so a discovered Session is voice-addressable by its official name before this ticket lands. This ticket's job is then to stabilise the name and compose the project half — not to plumb a new field through three layers.
The rename is this ticket's, not Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74's.CONTEXT.md lists label under Session Name's Avoid. When this ticket touches SessionLabel / match_label / router.py / bridge.py / persistence.py, bring the vocabulary in line with the glossary in the same change, once — do not leave name and label as two long-lived fields meaning almost the same thing.
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.
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.
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.
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).
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.
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.
Activity
Simon-AI-coding commentedon Aug 26, 2026
Advisor note from #74 (2026-08-26)
Decided while #74 was locking the Core
Sessionrow:name: str | None— the agent's own name, filled fromSessionInspection.name(Claude: rostername; Codex: daemonThread.namewhen known). It is an observed fact, so Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74 keeps it rather than dropping it at the Core boundary.label: SessionLabel | Nonestays optional and Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74 does not fill it — it is this ticket's composed<project> · <title>value.match_label()and the Stop Notice text fall backlabel → name → targetfrom Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74 on, so a discovered Session is voice-addressable by its official name before this ticket lands. This ticket's job is then to stabilise the name and compose the project half — not to plumb a new field through three layers.CONTEXT.mdlists label under Session Name's Avoid. When this ticket touchesSessionLabel/match_label/router.py/bridge.py/persistence.py, bring the vocabulary in line with the glossary in the same change, once — do not leavenameandlabelas two long-lived fields meaning almost the same thing.Simon-AI-coding commentedon Aug 26, 2026
Advisor note (2026-08-26) — an exit item inherited from #76
#76 exited with claude
progressgreen and codexprogressgated here: the acceptance drives no codex turn untilstable namepasses (journey.py:433-441), and an unnamed Codex thread reportsname: nulluntil this ticket's self-report rule exists. Done-when addition: in this ticket's acceptance run, codexprogressis green (run20260826T070925Zon #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:truefailed 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.Simon-AI-coding commentedon Aug 26, 2026
Correction (advisor, 2026-08-26): the before-evidence run for the codex
progressexit item is20260826T074208Z(bundle byte-identical to2dfcefe, the tree PR #101 merges), not20260826T070925Z. The two runs are identical step-for-step on both lanes.Ask one Session how far it has got, and bound the answer once for bot…
Carry the user's words over a Session's own inbox, and claim only wha…
Simon-AI-coding commentedon Aug 26, 2026
Advisor ruling (2026-08-26) — one name field, and no unnamed Sessions outside a repo
The
label/namerename collapses to one field:SessionLabel→SessionName,SessionInspection.name: SessionName | Nonecomposed by the lane,core.Session.namethe only field (labeldropped),match_label→match_name, wire"name"carries<project> · <title>, fallback chain name → target. Freezing: first non-Nonewins per exactSessionTarget(legacystore.py:1875-1902); a target change (Codex pid-only row gaining its thread id;/newmoving 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=Noneonly when there is no workspace. Adapted from legacylabels.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 existingAmbiguousNameError. The git call lives once inadapters/agent/_project.py, cached per realpath'd workspace;_naming.composestays 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.Simon-AI-coding commentedon Aug 26, 2026
Advisor (2026-08-27):
_naming.composereturnsSessionName | 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 fromlegacy@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).Simon-AI-coding commentedon Aug 26, 2026
Advisor (2026-08-27) — exit items answered; two reds ruled outside this ticket
Run
20260826T122216Z(bundle =76b9c98): codexprogressgreen — #76's inherited exit item is discharged. Codexstable namegreen 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. Claudestable namenow carries the project half.list_turnswatch item — reproduced once, mechanism corrected, no issue.thread/read includeTurns:true(andthread/resume) answeredlist_turns is not supported yetat 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 answersNone, logs, and keeps the cheap projection. Closed here; #82 cross-noted.Ruled outside #78: codex
approvalred — 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.childred both lanes — #79. Probe 0b red (0 remote audio frames in 30 s) — engine-free, unattributed, reported to Simon.Simon-AI-coding commentedon Aug 26, 2026
Resolution — PR #106, branch
78-session-namingProvenance. Base
e2090aa. Standards review23089836-b11a-4163-a42a-2321b66cb897(next: fix and stop, all fixed). Spec reviewc10107ad-b977-4933-8110-b4b76236bd0c— 3 findings, re-reviewed 2 CLOSED / 1 RETAINED, the retained one closed by the advisor ruling below. Acceptance run20260826T122216Z, bundle byte-identical to76b9c98, claude 2.1.246 / codex-cli 0.149.1.Rulings cited in the code. The
label/namecollapse 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).composeansweringNonerather than raising — the ticket's-> SessionLabelamended, 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.ProjectNamesis the one place either lane runsgit, cached per realpath'd workspace.SessionInspection.namecarries the composed value;SessionRegistryfreezes the first one per exactSessionTarget, 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 lanes20260826T074208Z20260826T122216Z'workspace-claude-25'— no project half'workspace-claude · workspace-claude-5b', 3 reads and a Stop'workspace-codex · 01a03e08', 3 reads and a StopExit item 2 — codex
progressgreenRed in the before-run (blocked behind the missing name, which stopped the lane at
stop noticeand 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:
childon both lanes (#79, unbuilt) andapprovalon codex (#105 — codex raised no permission at all for the relayed instruction;approval.nonetwice in the journal).Exit item 3 — the
list_turnswatch item: reproduced once, not in the shape fearedFrom
engine-codex/engine.logandjournal.jsonlof20260826T122216Z:roster: the harness's oracle has no session id (no rollout — no turn yet), while the engine's roster already names itcodex:01a03e08-…:78415. The shared daemon held the thread ~2 s after launch, with an id andname: null.thread/read includeTurns:true→list_turns is not supported yet;thread/resume→ the same message, the same secondstable name: three reads and a Stop, all'workspace-codex · 01a03e08'progressgreen off a successful deep read — with the TUI still liveTwo 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_threadalready answersNoneand 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,pytestgreen locally; CI on #106.Give a Session one name, composed once and never changed again (#78) (#…
Drop the threads codex runs for itself before they become Sessions (#112
Rebase onto #110, bring its sixth chat read under the rule, and corre…
Attribute a chat message to the Session it names (#109) (#115)
Refuse the prompt codex hands back as a name, and follow the rename (#…
The task half of a Session Name describes the Session and climbs (#273,