Skip to content

fix(plugin): route inbound by the one common session id#227

Merged
sanil-23 merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/plugin-shared-session-id
Jul 6, 2026
Merged

fix(plugin): route inbound by the one common session id#227
sanil-23 merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/plugin-shared-session-id

Conversation

@sanil-23

@sanil-23 sanil-23 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

The plugin resolves a reply to a local session only through the per-conversation convUuid index. A peer that keys on one common session id — e.g. OpenHuman stores our scope.harness_session_id and carries that same id in the reply's wrapper_session_id, with no per-conversation to_session_uuid — misses that index, so the message falls through to primary/unrouted (and never threads back to the originating session).

There is no separate from/to session identity here: both sides key on the one shared harness_session_id.

Fix

  • peekSessionUuidForHarness(id) — a read-only reverse of resolveSessionUuid; unlike it, never mints, so an unknown/attacker-supplied id can't create a binding during inbound routing.
  • labelForSharedSessionId(agent, id) — resolves a shared session id to the owning live local session.
  • In resolveBound, when there's no to_session_uuid, route by the common session id carried in fromSessionUuid (wrapper_session_id). It matches only our own sessions, so an ordinary peer's wrapper convUuid (not one of our harness ids) resolves to null and falls through to the existing label/policy routing — unchanged.

Backward compatible: the convUuid/to_session_uuid path is tried first and untouched.

Tests

routing-test.mjs: shared id → routed to its session + landed in the right inbox; labelForSharedSessionId resolves the live label; an unknown id is not hijacked (falls through to policy); peek of an unknown id returns null (no mint). 42/42 routing + 25/25 registry checks pass.

Context

Pairs with OpenHuman: its SessionEnvelopeV1 already carries the one common id in scope.harness_session_id/wrapper_session_id — no extra tp/to/from fields needed. Useful on its own.

Summary by CodeRabbit

  • New Features
    • Upgraded message routing to follow a shared conversation/thread ID across sessions, improving reply continuity.
    • Added the ability to adopt conversation IDs created by a peer to preserve ongoing threads.
  • Bug Fixes
    • Improved routing when only a sender session identifier is available.
    • Kept closed-session notices and reply threading consistent under the new shared-thread model.
    • Prevented misdelivery and improved fallback behavior for unknown conversation IDs.
  • Tests
    • Expanded/updated routing, envelope, and registry coverage for the new identifiers.

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

@sanil-23 is attempting to deploy a commit to the Vezures Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f990af08-a8d3-459b-b29c-a2c0145b1109

📥 Commits

Reviewing files that changed from the base of the PR and between 5ddaecc and bffe3e2.

📒 Files selected for processing (7)
  • sdk/plugin-tinyplace/envelope-test.mjs
  • sdk/plugin-tinyplace/hooks/agent-daemon.mjs
  • sdk/plugin-tinyplace/mcp/format.mjs
  • sdk/plugin-tinyplace/mcp/registry.mjs
  • sdk/plugin-tinyplace/mcp/routing.mjs
  • sdk/plugin-tinyplace/mcp/server.mjs
  • sdk/plugin-tinyplace/routing-test.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • sdk/plugin-tinyplace/mcp/registry.mjs

📝 Walkthrough

Walkthrough

Updates conversation UUID handling to use shared thread identifiers across encoding, routing, server reply correlation, and closed-target handling. Adds reverse binding for peer-minted conversation IDs and adjusts tests for the new fromSessionUuid-based routing shape.

Changes

Per-pair thread binding and routing

Layer / File(s) Summary
Reverse conversation binding
sdk/plugin-tinyplace/mcp/registry.mjs
Adds bindConversationUuid(convUuid, sessionUuid, peer) to persist the reverse conversation index and updates the live-session ownership comment.
Envelope UUID shape
sdk/plugin-tinyplace/mcp/format.mjs, sdk/plugin-tinyplace/envelope-test.mjs
Removes toSessionUuid from envelope encode/decode paths and updates the envelope test to assert the shared conversation UUID shape.
Reply thread correlation
sdk/plugin-tinyplace/mcp/server.mjs, sdk/plugin-tinyplace/hooks/agent-daemon.mjs
dispatchSend now derives a shared thread UUID, binds peer-opened threads, and aligns outbound envelope and closed-session metadata with the new correlation fields.
Inbound from-session routing
sdk/plugin-tinyplace/mcp/routing.mjs
Routes queued inbound messages by fromSessionUuid, persists that field on routed payloads, and updates closed-target reaping to use the shared conversation UUID when present.
Routing and registry tests
sdk/plugin-tinyplace/registry-test.mjs, sdk/plugin-tinyplace/routing-test.mjs
Adds registry coverage for reverse binding behavior and routing coverage for the per-pair session-id path and its fallback behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • tinyhumansai/tiny.place#215: Shares the same sdk/plugin-tinyplace MCP core and registry/routing/format plumbing that this change updates.
  • tinyhumansai/tiny.place#219: Also changes the conversation/session UUID plumbing across mcp/registry.mjs, mcp/format.mjs, mcp/routing.mjs, and mcp/server.mjs.

Poem

A rabbit taps the thread-line bright,
And hops by shared UUID light.
One reply finds home, then two,
With tidy hops in routingూ.
🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: inbound routing now uses the shared session ID instead of the old per-conversation target ID.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5feac032a5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/plugin-tinyplace/mcp/registry.mjs Outdated
// OpenHuman uses one shared session id, not our per-conversation wrapper uuid) →
// resolve via the harness-session index. Both resolve to the live local session.
export function labelForConversationUuid(agentAddress, id) {
const sessionUuid = sessionUuidForConversation(id) ?? peekSessionUuidForHarness(id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accept non-UUID harness session IDs in the fallback

For default Codex sessions, mcp/server.mjs:127 generates harness_session_id values like tp-codex-... when no real CODEX_SESSION_ID/CODEX_THREAD_ID is present, but inbound tp.to_session_uuid is only forwarded through safeUuid(tp.to_session_uuid) in mcp/format.mjs:162, which drops those non-hex IDs. In that common case this new peekSessionUuidForHarness fallback is never reached; the reply is treated as untargeted and goes to the primary session, misrouting whenever the intended live session is not primary. Please either decode/pass harness-session IDs separately or allow the same ID shape that this registry fallback is meant to resolve.

Useful? React with 👍 / 👎.

@sanil-23 sanil-23 force-pushed the fix/plugin-shared-session-id branch from 5feac03 to 801213a Compare July 6, 2026 06:32
@sanil-23 sanil-23 changed the title fix(plugin): route inbound replies by the shared harness_session_id fix(plugin): route inbound by the one common session id Jul 6, 2026
@sanil-23 sanil-23 marked this pull request as draft July 6, 2026 06:51
…ndex)

The session id is per-pair: the conversation uuid we mint for (this session,
peer) IS the session id. A peer (e.g. OpenHuman) that addresses a reply by that
same per-pair id — carried back in wrapper_session_id, with no separate
to_session_uuid — was not routed, because reply resolution only consulted the
to_session_uuid slot.

In resolveBound, when there's no to_session_uuid, resolve fromSessionUuid
(= wrapper_session_id = the per-pair session id) through our conversation index
(labelForConversationUuid → sessionUuid → live session). It matches ONLY ids we
minted, so an ordinary peer's own wrapper convUuid falls through to policy
routing unchanged.

routing-test: the per-pair id we minted routes back to its session; an id we
never minted falls through. 39 routing + 25 registry checks pass.
@sanil-23 sanil-23 force-pushed the fix/plugin-shared-session-id branch from 801213a to eb24ec2 Compare July 6, 2026 06:55
A reply now reuses the id of the thread it answers as its own
scope.wrapper_session_id, instead of minting a fresh conversation uuid and
echoing the peer's id in tp.to_session_uuid. Every message — whoever sends
it — carries exactly one session id, so both peers key the conversation on
the same id in both directions (incl. peer-initiated threads).

- dispatchSend: conversationUuid = replied-to thread id (replyConvUuid) ??
  fresh mint; only a brand-new thread we open mints an id
- registry.bindConversationUuid: adopt a peer-minted thread id into the
  conversation index (reverse-only, idempotent) so later inbound routes
  deterministically to the owning session, not just via primary policy
- format: stop emitting tp.to_session_uuid (inbound read path kept for
  backward compat with older peers)
- daemon: closed-session notice carries the shared id as conversationUuid
- registry-test: cover bindConversationUuid (adopt / route / idempotent)

Verified: 39 routing + 30 registry offline checks; live OpenHuman<->claude
round-trip threads the reply back into the originating session.
@sanil-23 sanil-23 marked this pull request as ready for review July 6, 2026 12:19

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ddaecce0e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/plugin-tinyplace/mcp/routing.mjs Outdated
Comment on lines +67 to +69
if (fromSessionUuid) {
const label = labelForConversationUuid(agentAddress, fromSessionUuid);
if (label) return { kind: "session", labels: [label] };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not fall through for closed wrapper IDs

When a peer replies by echoing one of our minted wrapper_session_id values in fromSessionUuid and the owning session has gone offline, labelForConversationUuid returns null because it only matches live sessions; this new branch then falls through to routeTarget, so under the default primary policy the bound thread is delivered to another live session. This regresses the reuse-proof behavior already used for toSessionUuid: known-but-not-live conversation IDs should be held/reaped, while only truly unknown IDs should fall through to policy routing.

Useful? React with 👍 / 👎.

Comment on lines +126 to +128
// "peer session id" to echo. (Historically tp.to_session_uuid carried the peer's id
// back; that dual-id addressing is gone — routing keys on wrapper_session_id alone.
// tp.to_session above is an explicit label target, orthogonal to the session id.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore or update to_session_uuid coverage

Removing the tp.to_session_uuid emission here leaves this package's own test script failing: npm test --prefix sdk/plugin-tinyplace stops in envelope-test.mjs with FAIL to_session_uuid roundtrips as toSessionUuid. If dual-ID addressing is intentionally gone, the stale envelope test needs to be updated or removed; otherwise the encoding path should keep serializing the field so the package test suite remains green.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdk/plugin-tinyplace/mcp/registry.mjs`:
- Around line 196-206: The bindConversationUuid flow is still racy because the
readEntryFile check and writeFileSync are not atomic, so concurrent callers can
both bind the same conversation. Update bindConversationUuid to use a
first-writer-wins file creation strategy like resolveConversationUuid, using
openSync with "wx" (or equivalent atomic create) for the binding file and only
writing once the exclusive handle is acquired. Keep the existing guard/return
behavior in bindConversationUuid and preserve the best-effort error handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f9e0e80c-7ee3-43d8-ada0-5be9344d0922

📥 Commits

Reviewing files that changed from the base of the PR and between 5feac03 and 5ddaecc.

📒 Files selected for processing (7)
  • sdk/plugin-tinyplace/hooks/agent-daemon.mjs
  • sdk/plugin-tinyplace/mcp/format.mjs
  • sdk/plugin-tinyplace/mcp/registry.mjs
  • sdk/plugin-tinyplace/mcp/routing.mjs
  • sdk/plugin-tinyplace/mcp/server.mjs
  • sdk/plugin-tinyplace/registry-test.mjs
  • sdk/plugin-tinyplace/routing-test.mjs

Comment thread sdk/plugin-tinyplace/mcp/registry.mjs
…d ids

Address review on tinyhumansai#227 — commit to the single shared session id fully:

- Remove tp.to_session_uuid entirely (no emit AND no decode) — every message
  carries exactly one id in scope.wrapper_session_id. dispatchSend and the
  daemon/routing no longer thread a separate peer id.
- routing (P1): a shared id we minted/adopted whose owner is offline names a
  SPECIFIC closed session — hold it (unrouted) and reap-as-closed, instead of
  falling through to policy and misdelivering to another live session. Only a
  truly unknown id falls through (first-contact → primary).
- reapClosedTargets + the daemon isolated-response gate now key on a known
  shared id (conversation index) rather than to_session_uuid.
- registry.bindConversationUuid: atomic first-owner-wins via openSync(wx) CAS
  (was read-then-write, TOCTOU).
- envelope-test / routing-test: cover no-emit/no-decode + known-but-offline
  → held (label-reuse safety) instead of the old dual-id assertions.

All offline suites green (envelope 40, registry 30, routing 38, + harness/
adapter/branch/lock/inject/hooks).
sanil-23 added a commit to sanil-23/openhuman that referenced this pull request Jul 6, 2026
`classify_message` bucketed inbound harness DMs on `harness_session_id`,
but the tiny.place plugin now sends one shared conversation id per thread in
`scope.wrapper_session_id` (and reuses it on reply). Key on that instead via
a new `SessionEnvelopeV1::session_key()` (wrapper id, `harness_session_id`
fallback for legacy envelopes), so a reply threads back into the session that
originated the conversation — in both directions.

Pairs with the plugin change (tinyhumansai/tiny.place#227). Verified live:
OpenHuman-initiated DM under session S -> claude reply reused S -> ingested
back under S and drove the orchestration graph.
@sanil-23

sanil-23 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review — addressed in bffe3e2c, committing fully to the single shared session id (no legacy dual-id path):

  • P1 (routing.mjs — don't fall through for closed wrapper IDs): fixed. A shared id we minted/adopted whose owner is offline now returns unrouted (held + reaped-as-closed) instead of falling through to policy; only a truly unknown id falls through to first-contact/primary. reapClosedTargets and the daemon's isolated-response gate now key on a known shared id via the conversation index.
  • P2 (format.mjs — envelope-test failing / restore-or-update): the dual-id tp.to_session_uuid is now removed entirely — neither emitted nor decoded. envelope-test.mjs updated to assert no-emit/no-decode, and routing-test.mjs now covers the shared-id + known-but-offline (label-reuse) cases. Full offline suite green.
  • P2 (registry.mjs — non-UUID harness IDs in the fallback): moot — the peekSessionUuidForHarness fallback was removed; routing keys solely on the shared wrapper_session_id.
  • CodeRabbit (registry.mjs — bindConversationUuid integrity): bindConversationUuid now writes atomically via openSync(wx) CAS (first-owner-wins), replacing the read-then-write.

Companion OpenHuman change: tinyhumansai/openhuman#4582.

@sanil-23 sanil-23 merged commit 5e5d406 into tinyhumansai:main Jul 6, 2026
9 of 10 checks passed
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