Generalize "lend my Mac" → "lend a hands node" (macmini + rpi1 + …)
Problem
The reverse-attach design writes the tools-provider ("hands") as the Mac, everywhere:
- ADR: "The Mac dials the pod", "the human decides which pod the Mac dials", "lend my Mac to this agent".
- requirement: "lend my Mac to this", "macmini chooses the profile at dial time",
POST /attach on the Mac's oab-instance-mcp.
But the strategic direction is Linux-first: the scalable "hands" are cheap Linux nodes (rpi1 now, more later), not a single expensive Mac. So "lend" must not be Mac-specific.
Proposal
Treat the hands side as a registry of hands nodes, each running an instance-mcp (macmini = Swift; rpi1 = the Rust port, #15). The lend action becomes:
"Lend a hands node to this session" → pick a session, then pick a node (macmini / rpi1 / …) + profile + TTL.
The selected node dials the pod and lends its tools:
So a session gets two (soon N) lend options, not one hard-coded Mac.
What this touches
- UI (Connect/Remote): "lend my Mac" → a node picker (list of registered/online hands nodes).
- Grant flow /
POST /attach: the grant must name which node dials; the attach secret is minted for/handed to that node (today it is implicitly the Mac). The node then dials wss://<pod-tailnet>/tools/attach/<session> with the secret it holds.
- Node registry / liveness: which hands nodes exist and are online; enough for the picker.
- Identity: the ADR already hints at
tailscale whois <src> tag-based identity for tagged nodes — a clean multi-node fit (each hands node is a tagged tailnet node), vs. today's "the Mac chooses the profile at dial time, nothing to look up".
Why now
Verified today (see #15): a Rust instance-mcp runs on rpi1 with MCP + sys_info + screenshot(grim) + auth gate, and the reverse-attach disposition state machine + attachURL have Rust↔Swift conformance parity (#24, 23/23). The remaining gap to a real "lend rpi1 to a session" is exactly this: (a) the WS transport (dial loop) on the node, and (b) a node-agnostic grant/lend flow so rpi1 can be the dialer, not just the Mac.
Concretely observed while trying to test end-to-end against a fresh session (kiro-4412): the pod's /tools/attach/{session} route is 404 until a grant exists — i.e. nothing can dial until a lend/grant is created, and today that creation is Mac-bound. That is the blocker this issue removes.
Scope note
This is a design/architecture issue (generalize the hands side); the security invariants stay the same (no egress from the sandbox; node dials the pod; verifier-only in the pod; per-connection tool profile). It refines, not replaces, the reverse-attach ADR.
Refs #15 (Rust Linux port), #24 (reverse-attach conformance), docs/adr/reverse-attach.md, docs/requirements/reverse-attach.md.
Generalize "lend my Mac" → "lend a hands node" (macmini + rpi1 + …)
Problem
The reverse-attach design writes the tools-provider ("hands") as the Mac, everywhere:
POST /attachon the Mac'soab-instance-mcp.But the strategic direction is Linux-first: the scalable "hands" are cheap Linux nodes (rpi1 now, more later), not a single expensive Mac. So "lend" must not be Mac-specific.
Proposal
Treat the hands side as a registry of hands nodes, each running an
instance-mcp(macmini = Swift; rpi1 = the Rust port, #15). The lend action becomes:The selected node dials the pod and lends its tools:
So a session gets two (soon N) lend options, not one hard-coded Mac.
What this touches
POST /attach: the grant must name which node dials; the attach secret is minted for/handed to that node (today it is implicitly the Mac). The node then dialswss://<pod-tailnet>/tools/attach/<session>with the secret it holds.tailscale whois <src>tag-based identity for tagged nodes — a clean multi-node fit (each hands node is a tagged tailnet node), vs. today's "the Mac chooses the profile at dial time, nothing to look up".Why now
Verified today (see #15): a Rust
instance-mcpruns on rpi1 with MCP + sys_info + screenshot(grim) + auth gate, and the reverse-attach disposition state machine + attachURL have Rust↔Swift conformance parity (#24, 23/23). The remaining gap to a real "lend rpi1 to a session" is exactly this: (a) the WS transport (dial loop) on the node, and (b) a node-agnostic grant/lend flow so rpi1 can be the dialer, not just the Mac.Concretely observed while trying to test end-to-end against a fresh session (kiro-4412): the pod's
/tools/attach/{session}route is 404 until a grant exists — i.e. nothing can dial until a lend/grant is created, and today that creation is Mac-bound. That is the blocker this issue removes.Scope note
This is a design/architecture issue (generalize the hands side); the security invariants stay the same (no egress from the sandbox; node dials the pod; verifier-only in the pod; per-connection tool profile). It refines, not replaces, the reverse-attach ADR.
Refs #15 (Rust Linux port), #24 (reverse-attach conformance),
docs/adr/reverse-attach.md,docs/requirements/reverse-attach.md.