Cross-space addressing: send by (space name, tab name), not by terminal id — and fleet_list does not even report the space name yet #771

Open
opened 2026-10-05 11:31:10 +02:00 by ltms · 1 comment
Owner

Operator requirement, 2026-10-05, following #770:

then, with cross spaces communicate, we need to identify the space -> tab (names)

#770 is the identity half (who is a lead). This is the addressing half (how one team reaches another).

Today there are three address kinds and none of them is a name

fleet_send accepts exactly:

param what it is
sessionId a herdr terminal_id, e.g. term_65d106559d7e43 — opaque, and local to one herdr daemon
turnId routes an answer back into a worker's blocked fleet_ask
coordId a peer lead's mailbox on the shared broker — cross-host, not cross-space

So on one host, reaching another team means knowing an opaque terminal id. That is the wrong unit: a team knows "team B's lead", not term_65d1…. Terminal ids also change when a pane is recreated, so any id an agent writes down goes stale silently.

coordId is not the answer either. It is per-daemon (coordinator.selfId is the host, mac here), so two teams under one daemon share one coordId and it cannot tell them apart.

The names exist in herdr. The bridge just does not pass them on

Measured today with herdr workspace list:

{"workspace_id":"w1","label":"mgnl","number":1,"tab_count":9}
{"workspace_id":"w2","label":"fleet","number":2,"tab_count":1,"active_tab_id":"w2:tY"}
{"workspace_id":"w9","label":"vms","number":3,"tab_count":1}
{"workspace_id":"wA","label":"trinotes",...}

So every space has a human label and an ordering number, and LeadTabScanner already calls workspace.list, tab.list and pane.list.

But fleet_list's panes rows report only the id:

{"sessionId":"term_65d106559b02e1","paneId":"w2:p1D","workspaceId":"w2",
 "tabId":"w2:tY","label":"lead: opus","role":"lead","deliverable":true}

workspaceId: "w2" — not "fleet". The tab has a label; the space does not. So an agent cannot construct a (space name, tab name) address from the bridge at all today, even though the daemon holds both names. That is the first thing to fix, and it is small.

Asked for

  1. Report the space name. Add the workspace label to each panes row (and wherever else a pane or member is described). Without this, nothing below is usable.
  2. Accept a name address on fleet_send. Something like space + tab, resolved to a terminal id by the daemon, mutually exclusive with sessionId/turnId/coordId. The resolution must be explicit about failure: no such space, no such tab in that space, and a tab that is not deliverable are three different answers and must not collapse into one.
  3. Say what happens when a name is ambiguous. Two tabs with the same label in one space is possible in herdr. Refuse, rather than pick one — picking one silently sends a brief to the wrong agent.
  4. Decide whether a name address is a task channel or a coordination channel. Under #770 each space is a team, and the canonical block is firm that a lead never delegates to another lead: traffic between teams is coordination only. A name address that reaches another team's lead must therefore carry the same restriction coordId has, or it becomes a cross-team task route by accident.

A disclosure question to settle on purpose, not by accident

An observer's panes array is deliberately filtered and cut to sessionId, label, status, role, deliverable (#758) — no workspaceId, because a space is host shape. If space names start being reported, decide explicitly whether an observer learns other teams' space names. "The roster carries no secrets" was the reasoning for READ; a team name is a stronger claim than a pane id, and under multi-team it also reveals how many teams exist and what they are called.

My reading: a name address should resolve within the caller's own space by default, and reaching another space should be a separate, more restricted thing. That keeps the common case simple and does not hand every observer a map of the host. Not settled — record the decision here.

Depends on

#770. The two must agree: if lead identity keys on (space, "lead"), then (space, tab) is exactly the address, and space means the same thing in both. Doing the addressing first would bake in today's label-keyed identity.

Operator requirement, 2026-10-05, following #770: > then, with cross spaces communicate, we need to identify the space -> tab (names) #770 is the identity half (who is a lead). This is the **addressing** half (how one team reaches another). ## Today there are three address kinds and none of them is a name `fleet_send` accepts exactly: | param | what it is | |---|---| | `sessionId` | a herdr `terminal_id`, e.g. `term_65d106559d7e43` — opaque, and local to one herdr daemon | | `turnId` | routes an answer back into a worker's blocked `fleet_ask` | | `coordId` | a peer lead's mailbox on the shared broker — cross-**host**, not cross-space | So on one host, reaching another team means knowing an opaque terminal id. That is the wrong unit: a team knows "team B's lead", not `term_65d1…`. Terminal ids also change when a pane is recreated, so any id an agent writes down goes stale silently. `coordId` is not the answer either. It is per-daemon (`coordinator.selfId` is the **host**, `mac` here), so two teams under one daemon share one coordId and it cannot tell them apart. ## The names exist in herdr. The bridge just does not pass them on Measured today with `herdr workspace list`: ```json {"workspace_id":"w1","label":"mgnl","number":1,"tab_count":9} {"workspace_id":"w2","label":"fleet","number":2,"tab_count":1,"active_tab_id":"w2:tY"} {"workspace_id":"w9","label":"vms","number":3,"tab_count":1} {"workspace_id":"wA","label":"trinotes",...} ``` So every space has a human `label` and an ordering `number`, and `LeadTabScanner` already calls `workspace.list`, `tab.list` and `pane.list`. But `fleet_list`'s `panes` rows report only the **id**: ```json {"sessionId":"term_65d106559b02e1","paneId":"w2:p1D","workspaceId":"w2", "tabId":"w2:tY","label":"lead: opus","role":"lead","deliverable":true} ``` `workspaceId: "w2"` — not `"fleet"`. The tab has a `label`; the space does not. **So an agent cannot construct a (space name, tab name) address from the bridge at all today**, even though the daemon holds both names. That is the first thing to fix, and it is small. ## Asked for 1. **Report the space name.** Add the workspace label to each `panes` row (and wherever else a pane or member is described). Without this, nothing below is usable. 2. **Accept a name address on `fleet_send`.** Something like `space` + `tab`, resolved to a terminal id by the daemon, mutually exclusive with `sessionId`/`turnId`/`coordId`. The resolution must be explicit about failure: no such space, no such tab in that space, and a tab that is not deliverable are three different answers and must not collapse into one. 3. **Say what happens when a name is ambiguous.** Two tabs with the same label in one space is possible in herdr. Refuse, rather than pick one — picking one silently sends a brief to the wrong agent. 4. **Decide whether a name address is a task channel or a coordination channel.** Under #770 each space is a team, and the canonical block is firm that a lead never delegates to another lead: traffic between teams is coordination only. A name address that reaches another team's lead must therefore carry the same restriction `coordId` has, or it becomes a cross-team task route by accident. ## A disclosure question to settle on purpose, not by accident An **observer**'s `panes` array is deliberately filtered and cut to `sessionId`, `label`, `status`, `role`, `deliverable` (#758) — no `workspaceId`, because a space is host shape. If space names start being reported, decide explicitly whether an observer learns other teams' space names. "The roster carries no secrets" was the reasoning for `READ`; a team name is a stronger claim than a pane id, and under multi-team it also reveals how many teams exist and what they are called. My reading: a name address should resolve **within the caller's own space** by default, and reaching another space should be a separate, more restricted thing. That keeps the common case simple and does not hand every observer a map of the host. Not settled — record the decision here. ## Depends on #770. The two must agree: if lead identity keys on `(space, "lead")`, then `(space, tab)` is exactly the address, and `space` means the same thing in both. Doing the addressing first would bake in today's label-keyed identity.
Author
Owner

Step 1 is merged and live. 364b229 on main, deployed 11:43:52 (pid 65456, jar b968e25ebf43). Steps 2, 3 and 4 are untouched and still wait on the two decisions. Keeping this open.

What landed

fleet_list's full pane row now carries workspaceLabel next to workspaceId, read by a new PaneLocator.workspaceLabelsByWorkspaceId() that reuses the existing workspace.list call and Workspace model type.

What I checked myself, not from the worker's report

  • Diff read in full. 3 files, +95/−17. workspaceLabel is inside the same !observerView block as workspaceId, and workspaceLabelsOrEmpty is an exact mirror of the existing tabLabelsOrEmpty, including catching HerdrException — which is the only thing HerdrClient.call declares.
  • Test count reconciled: +1 @Test, −0 against origin/main, so 2167 → 2168. Both the surefire XML sum (179 files) and Maven's own aggregate line report 2168 tests, 0 failures, 0 errors, MVN_EXIT=0. My first parse of the XML returned 0 tests — that was a broken regex of mine, not a broken build; re-parsed with a real XML parser.
  • Tree parity: the merge was a fast-forward, and the merged tree hash equals the tree I built (f84ce9c…). So the bytes on main are the bytes that passed.
  • The negative assertions are not blind. The observer test asserts the field is absent, which would also pass if the field were deleted outright. A separate test asserts the positive case ("workspaceLabel":"ltms"), so the pair holds.

Live proof against real herdr, which the tests could not give

The suite uses a fake herdr, so the real workspace.list parsing was unproven until the redeploy. fleet_list on the live daemon now returns, and every value matches herdr workspace list:

workspaceId workspaceLabel
w2 fleet
w1 mgnl
w9 vms
wA trinotes
wB anki

So the (space, tab) pair the requirement asks for is now readable from the bridge — for example fleet / lead: opus, and mgnl / 70-tidy.

Documented

wiki/11-Features.md — extended the existing fleet_list reports every agent pane entry rather than opening a second one, with the two gotchas (an observer deliberately does not get it; a failed or missing lookup costs the label only). Wiki 8aa5676, pushed and verified by ref.

No CLAUDE.md change was needed, and I checked rather than assumed: the canonical block enumerates only the observer's reduced field set, which this does not change. The full row is never enumerated there.

One thing the worker raised and I am leaving

It flagged that docs/MCP-Contract.md was not updated. That is correct to leave alone: that page is flows only now, and its hand-maintained tool catalogue and parameter tables were deliberately deleted in CB-609/#114 because a second copy of the tool surface drifted for a month. The live MCP schema is the reference.

**Step 1 is merged and live.** `364b229` on `main`, deployed 11:43:52 (pid 65456, jar `b968e25ebf43`). Steps 2, 3 and 4 are untouched and still wait on the two decisions. Keeping this open. ## What landed `fleet_list`'s full pane row now carries `workspaceLabel` next to `workspaceId`, read by a new `PaneLocator.workspaceLabelsByWorkspaceId()` that reuses the existing `workspace.list` call and `Workspace` model type. ## What I checked myself, not from the worker's report - **Diff read in full.** 3 files, +95/−17. `workspaceLabel` is inside the same `!observerView` block as `workspaceId`, and `workspaceLabelsOrEmpty` is an exact mirror of the existing `tabLabelsOrEmpty`, including catching `HerdrException` — which is the only thing `HerdrClient.call` declares. - **Test count reconciled:** `+1 @Test, −0` against `origin/main`, so 2167 → 2168. Both the surefire XML sum (179 files) and Maven's own aggregate line report **2168 tests, 0 failures, 0 errors**, `MVN_EXIT=0`. My first parse of the XML returned 0 tests — that was a broken regex of mine, not a broken build; re-parsed with a real XML parser. - **Tree parity:** the merge was a fast-forward, and the merged tree hash equals the tree I built (`f84ce9c…`). So the bytes on `main` are the bytes that passed. - **The negative assertions are not blind.** The observer test asserts the field is *absent*, which would also pass if the field were deleted outright. A separate test asserts the positive case (`"workspaceLabel":"ltms"`), so the pair holds. ## Live proof against real herdr, which the tests could not give The suite uses a fake herdr, so the real `workspace.list` parsing was unproven until the redeploy. `fleet_list` on the live daemon now returns, and every value matches `herdr workspace list`: | `workspaceId` | `workspaceLabel` | |---|---| | `w2` | `fleet` | | `w1` | `mgnl` | | `w9` | `vms` | | `wA` | `trinotes` | | `wB` | `anki` | So the `(space, tab)` pair the requirement asks for is now readable from the bridge — for example `fleet` / `lead: opus`, and `mgnl` / `70-tidy`. ## Documented `wiki/11-Features.md` — extended the existing `fleet_list reports every agent pane` entry rather than opening a second one, with the two gotchas (an observer deliberately does not get it; a failed or missing lookup costs the label only). Wiki `8aa5676`, pushed and verified by ref. No `CLAUDE.md` change was needed, and I checked rather than assumed: the canonical block enumerates only the **observer's** reduced field set, which this does not change. The full row is never enumerated there. ## One thing the worker raised and I am leaving It flagged that `docs/MCP-Contract.md` was not updated. That is correct to leave alone: that page is flows only now, and its hand-maintained tool catalogue and parameter tables were deliberately deleted in CB-609/#114 because a second copy of the tool surface drifted for a month. The live MCP schema is the reference.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#771