Skip to content

fix(session_start): report which pin a re-pin moved (#517) - #533

Merged
atlas-from-plumb merged 4 commits into
mainfrom
atlas/fix-517-repin-report
Oct 1, 2026
Merged

atlas-from-plumb merged 4 commits into
mainfrom
atlas/fix-517-repin-report

Conversation

@atlas-from-plumb

Copy link
Copy Markdown
Collaborator

Why

After a re-pin, session_start printed a neutral Re-pinned: <from> → <to>. It could not say which pin had moved: the caller's own per-agent pin, or the connection's pin, which every agent without a pin of its own follows. The daemon makes that choice, but the tool only got the previous root back, and it computed "from" from the caller's own root. That gave two wrong outputs:

  • Anonymous default-scope re-pin on a shared connection. It moves the connection's pin, and the seeded peer shards follow it. The caller was not told that other agents moved too.
  • scope: "connection" from an agent that has its own pin. The agent's own root was shown as "from", and the # Workspace: header showed the connection's new root. The agent itself still resolves against its own pin, so the header named a root its relative paths do not use.

Change

  • The WithRepin callback now returns a tools.RepinReport instead of a bare root. The report holds:
    • Root: the resolved root;
    • Scope: agent or connection;
    • From: that pin's previous root;
    • Followers: how many other agents' shards followed a connection move;
    • Effective: the root the caller's next relative-path call resolves against, read through workspaceFor under the caller's own ctx after the move.
  • Daemon side (internal/cli):
    • repinWorkspace is the callback and returns the report.
    • repinWorkspaceFrom and repinConnection return a repinOutcome.
    • repinAgent also returns the shard's previous root, read under the same sh.mu lock that moves it.
    • followConnectionShards returns the ids it moved.
    • repinReport (new conn_repin_report.go) leaves the caller out of the follower count.
  • Tool side: the full and brief packets both print one of two headlines, followed by a next-call line:
    • Re-pinned your pin: A → B
    • Re-pinned this connection's pin: A → B (N other agents follow it)
    • Next relative-path call resolves against: <root>. When that root is the agent's own pin rather than the connection's new root, the line adds (your own pin, not the connection's).
  • # Workspace: (and the rest of the packet) now uses the caller's own root.
  • Every variant starts with Re-pinned, so the existing absence checks on that bare prefix still apply.
  • The brief-packet budget test (TestSessionStartBrief_ByteBudget) and the brief re-pin case (1536 bytes) still pass.

Tests

New tests in internal/cli/session_start_repin_report_test.go. They run through the real tools.SessionStart wiring (newSessionStartTool), and each one runs with both detail: full and detail: brief:

  • identified agent-scope re-pin on a shared connection: reports your pin, and the connection pin is unchanged;
  • anonymous default-scope re-pin on a shared connection: reports the connection's pin with 2 other agents follow it, and both peers really moved;
  • scope: "connection" from an agent with its own pin: reports the connection's previous root, the header and next-call line name the agent's own root, and the peer followed;
  • scope: "connection" from a seeded agent: the caller follows too but is not counted;
  • single-agent connection: no other agent follows it, with a same-root control that prints no Re-pinned at all.

TestRepinAnnouncement is now table-driven and covers every variant. The existing stubs and assertions are updated to the new signature and wording.

Evidence:

  • Main vs this branch: all 5 new cli tests fail on origin/main (b5a4392) with the new test file copied in. They fail on the wording, on the wrong "from" and on the wrong header. They pass here.
  • Mutation checks: 11 of 11 mutants killed, including:
    • counting the caller as a follower;
    • Effective falling back to Root;
    • followConnectionShards reporting nothing;
    • labelling an agent move as a connection move;
    • taking "from" from the caller's root;
    • dropping the same-root suppression;
    • using the requested root as the header;
    • dropping the own-pin suffix;
    • dropping the block from the brief packet, and from the full packet.
  • go test ./... -count=1 exits 0.
  • go test -tags=integration ./internal/cli/ -count=1 exits 0.
  • golangci-lint is clean.
  • check-changelog-placement --require-base is OK.

Closes #517.

🤖 Generated with Claude Code

golimpio pushed a commit that referenced this pull request Sep 30, 2026
- attachOrRepinTo returns the connection's previous root read inside its
  mutation lane. It was read before the lane, so two callers racing to one
  target both reported moving the pin, and followConnectionShards could be
  handed a stale root, missing shards seeded at an intermediate one.
- The caller's next-call root is derived from the move rather than re-read
  through workspaceFor afterwards, where a peer's concurrent move could
  mislabel it. An empty one is rendered as "nothing" instead of the
  connection's root.
- The unattached path (a caller whose declaration is refused resolves to
  nothing while the connection is pinned) renders the report too.
- repinConnection clears the caller's own refused-declaration marker; the
  identity-stripped move used to clear only the anonymous one.
- The #516 CHANGELOG bullet is folded into this change's entry, since its
  interim wording never shipped.

Tests: a concurrent same-target regression, an agent re-pinning its own
pin (pins "from" on the agent path), and a connection-scoped move from a
refused declaration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
golimpio pushed a commit that referenced this pull request Sep 30, 2026
…#533 review 2)

connScopeCallerRoot read the caller's shard root after a connection-scoped
move and the report labelled any difference "(your own pin, not the
connection's)". A shard that never chose a root follows the connection,
so a peer's concurrent connection move dragged it on and the caller was
told it was on its own pin (107 of 320 reports in the reviewer's run).
Decide by whether the shard follows instead: a shard that is neither
self-pinned nor restored resolves against the root this move left the
connection at; only a self-pinned or restored shard reports its own root.
selfPinned never goes back to false, and a self-pinned shard's root moves
only through its own agent, so the one window left is that agent's own
concurrent session_start.

Tests: a bounded stress test of concurrent connection-scoped moves by two
following agents asserting zero mislabels, a restored-pin case, and a
guard that a refused connection-scoped move keeps the caller's
refused-declaration marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
golimpio
golimpio previously approved these changes Sep 30, 2026

@golimpio golimpio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved: two review rounds, both blockers fixed and confirmed (stress guard red on prior head); green CI on 604745a.

golimpio pushed a commit that referenced this pull request Sep 30, 2026
- attachOrRepinTo returns the connection's previous root read inside its
  mutation lane. It was read before the lane, so two callers racing to one
  target both reported moving the pin, and followConnectionShards could be
  handed a stale root, missing shards seeded at an intermediate one.
- The caller's next-call root is derived from the move rather than re-read
  through workspaceFor afterwards, where a peer's concurrent move could
  mislabel it. An empty one is rendered as "nothing" instead of the
  connection's root.
- The unattached path (a caller whose declaration is refused resolves to
  nothing while the connection is pinned) renders the report too.
- repinConnection clears the caller's own refused-declaration marker; the
  identity-stripped move used to clear only the anonymous one.
- The #516 CHANGELOG bullet is folded into this change's entry, since its
  interim wording never shipped.

Tests: a concurrent same-target regression, an agent re-pinning its own
pin (pins "from" on the agent path), and a connection-scoped move from a
refused declaration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
golimpio pushed a commit that referenced this pull request Sep 30, 2026
…#533 review 2)

connScopeCallerRoot read the caller's shard root after a connection-scoped
move and the report labelled any difference "(your own pin, not the
connection's)". A shard that never chose a root follows the connection,
so a peer's concurrent connection move dragged it on and the caller was
told it was on its own pin (107 of 320 reports in the reviewer's run).
Decide by whether the shard follows instead: a shard that is neither
self-pinned nor restored resolves against the root this move left the
connection at; only a self-pinned or restored shard reports its own root.
selfPinned never goes back to false, and a self-pinned shard's root moves
only through its own agent, so the one window left is that agent's own
concurrent session_start.

Tests: a bounded stress test of concurrent connection-scoped moves by two
following agents asserting zero mislabels, a restored-pin case, and a
guard that a refused connection-scoped move keeps the caller's
refused-declaration marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@golimpio
golimpio force-pushed the atlas/fix-517-repin-report branch from 604745a to 7a86ec8 Compare September 30, 2026 21:25
atlas-from-plumb and others added 3 commits October 1, 2026 07:44
The re-pin callback (repinWorkspace, wired through WithRepin) now returns
a tools.RepinReport: the pin the daemon moved (the caller's own shard or
the connection's), that pin's previous root, how many OTHER agents'
shards followed a connection move, and the root the caller's next
relative path resolves against. repinWorkspaceFrom, repinConnection and
repinAgent carry the outcome up; followConnectionShards returns the ids
it moved.

session_start renders "Re-pinned your pin: A → B" or "Re-pinned this
connection's pin: A → B (N other agents follow it)" plus a
"Next relative-path call resolves against:" line in both the full and
brief packets, and the "# Workspace:" header now names the caller's own
effective root. Previously an anonymous default-scope re-pin on a shared
connection dragged its peers silently, and scope "connection" from an
agent holding its own pin showed its own root as "from" and the
connection's new root as its workspace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- attachOrRepinTo returns the connection's previous root read inside its
  mutation lane. It was read before the lane, so two callers racing to one
  target both reported moving the pin, and followConnectionShards could be
  handed a stale root, missing shards seeded at an intermediate one.
- The caller's next-call root is derived from the move rather than re-read
  through workspaceFor afterwards, where a peer's concurrent move could
  mislabel it. An empty one is rendered as "nothing" instead of the
  connection's root.
- The unattached path (a caller whose declaration is refused resolves to
  nothing while the connection is pinned) renders the report too.
- repinConnection clears the caller's own refused-declaration marker; the
  identity-stripped move used to clear only the anonymous one.
- The #516 CHANGELOG bullet is folded into this change's entry, since its
  interim wording never shipped.

Tests: a concurrent same-target regression, an agent re-pinning its own
pin (pins "from" on the agent path), and a connection-scoped move from a
refused declaration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#533 review 2)

connScopeCallerRoot read the caller's shard root after a connection-scoped
move and the report labelled any difference "(your own pin, not the
connection's)". A shard that never chose a root follows the connection,
so a peer's concurrent connection move dragged it on and the caller was
told it was on its own pin (107 of 320 reports in the reviewer's run).
Decide by whether the shard follows instead: a shard that is neither
self-pinned nor restored resolves against the root this move left the
connection at; only a self-pinned or restored shard reports its own root.
selfPinned never goes back to false, and a self-pinned shard's root moves
only through its own agent, so the one window left is that agent's own
concurrent session_start.

Tests: a bounded stress test of concurrent connection-scoped moves by two
following agents asserting zero mislabels, a restored-pin case, and a
guard that a refused connection-scoped move keeps the caller's
refused-declaration marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@golimpio
golimpio force-pushed the atlas/fix-517-repin-report branch from 7a86ec8 to 4159b4f Compare September 30, 2026 21:45

@golimpio golimpio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taken over to merge at the owner's request. Its own two review rounds are recorded on the PR, with both blockers fixed and confirmed; updated with main by a clean merge.

@atlas-from-plumb
atlas-from-plumb merged commit 3a1aa2e into main Oct 1, 2026
9 checks passed
golimpio pushed a commit that referenced this pull request Oct 1, 2026
…ask-config

#533 changed session_start's struct (repin now returns a RepinReport) and
repinAgent's return values. Conflict resolved by keeping main's struct with
this branch's ctx-taking tasksFn, and updating the new #522 test helper to
repinAgent's three return values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plumb-Session: vivid-mink
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.

session_start re-pin output cannot say which pin moved (agent shard vs connection)

2 participants