Skip to content

Connect sessions across projects, and report what a review is using - #9

Open
tdwd wants to merge 4 commits into
mainfrom
connected-sessions
Open

Connect sessions across projects, and report what a review is using#9
tdwd wants to merge 4 commits into
mainfrom
connected-sessions

Conversation

@tdwd

@tdwd tdwd commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Four commits, each reviewable on its own.

  • Give the session label one owner — extracts sessionLabel out of the sidebar into internal/ui/format.go, so the two callers that follow do not each write their own copy. No behaviour change.
  • Connect sessions across two projectsstore.Connection{A, B}, coord.sees, the c key and its picker. A connection widens what a session can read and never narrows a project. Claims deliberately do not widen, and there is a test named for that exception.
  • Report what a review is using while it runs--output-format stream-json and a usage callback on agent.RunClaude. The badge shows tokens during a run and dollars after, because the CLI reports cost only in the result event.
  • Record a shell as a session in the backlog — a planning note, no code.

go test ./... and make race pass on the final commit. Each commit was built and tested on its own before the next was made.

The follow-up branch adds a focus guard so x and c cannot act on a session the keyboard is not driving.

tdwd added 4 commits September 7, 2026 12:52
The sidebar worked out what a session is called by hand: the title, or the
generated name when the title is empty. Two callers are about to want the
same answer — the connect picker, and the notices that name a session on
another project — and each would have written its own copy. The author of a
second copy is the last person who ever compares them.

format.go is named for the category rather than its first occupant. What a
project is called, and how a figure is abbreviated to sit beside these, belong
in it too.
Sessions on one project already see each other. The case scope cannot express
is the opposite one: an API changing in one repository while its consumer
changes in another. So a connection is an escape from the project, never a
subdivision of it.

Four decisions worth keeping.

A pair, not a named set. store.Connection{A, B}. The motivating case is exactly
two sessions, and a set would need a name, a member editor and a rule for the
last member leaving. Connecting A to B and A to C lets A see both without
making B and C visible to each other.

Claims deliberately do not widen. Every other scoping test became sees; Claim
kept ProjectID. A claim is a repo-relative path, so two sessions in different
repositories both claiming internal/api/client.go would be reported as
colliding over a file they do not share, and an agent that meets one false
conflict stops trusting the mechanism. This is the exception most likely to be
tidied away by a later reader, which is why it has a test named for it.

The read widens, the write does not. A note still goes to the writer's own
project log. A reader gets that merged with what connected sessions wrote in
theirs, filtered to those sessions — handing over the far project's whole log
would publish the notes of every session there.

The coordinator is told the whole set, never a delta. SetConnections replaces.
The store owns the document and the coordinator holds a copy; a copy updated by
deltas is free to drift the first time an update is missed, and the drift is
invisible.

Register, Registered and Unregister move to registry.go, leaving coord.go the
type and its lifecycle. Two documentation catch-ups ride along because they
touch the same table and paragraph: the README gains the `e` row it never had,
and the architecture tool list names the five tools added since it was written.
The badge could only show a cost, and a cost arrives once, in the result
event. So a review in flight showed $0.00 for its whole run and read as free.

The run now uses --output-format stream-json --verbose
--include-partial-messages, and agent.RunClaude takes a callback that fires on
every event carrying usage.

What a captured stream showed, and what the design follows from: only the
output count moves. The first event of a turn already carries the final input,
cache-read and cache-write figures — in one measured run, 15,888 read and 7,954
written were known before a single word was generated. And cost is not in any
event but the last, so a run in flight can report what it is using and not what
it will cost. The sidebar therefore shows tokens while a review runs and
dollars once it lands.

One format, not two. The non-live path could have kept --output-format json,
but a second parser is a second place for claude's field names to drift, and
the totals are the thing least affordable to get quietly wrong.

It also closed a gap the old code's own doc comment denied. RunClaude promised
to return an error "only when there is no accounting at all", while cmd.Output
turned any non-zero exit into an error and discarded the result envelope with
it. Reading stdout to the end before waiting means a result that arrived is
returned whatever the process does afterwards.

The backlog's open entry 11 asked for exactly this, so it goes; the struck-out
entry above it is the record now.
Not every session wants an agent. Reaching a server, running a migration or
watching a log is work that belongs beside the agents rather than in a separate
terminal, and almost all of it already works: agent.Start runs whatever
store.Session.Agent names, coordArgs gives no coordination flags to a program
it does not know, and willResume refuses --continue to anything but claude.

What is missing is the menu entry and three decisions around it, which the
entry states. No code changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant