agent-switchboard 2.0.1__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/ARCS.md +4 -1
  2. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/CONTRIBUTING.md +2 -0
  3. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/PKG-INFO +2 -1
  4. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/README.md +1 -0
  5. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/claude-code.md +201 -1
  6. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/concepts.md +6 -0
  7. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/encryption.md +7 -2
  8. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/quickstart.md +2 -2
  9. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/upgrading.md +65 -0
  10. agent_switchboard-2.2.0/extras/session-capsule/README.md +260 -0
  11. agent_switchboard-2.2.0/extras/session-capsule/cc.sh +7 -0
  12. agent_switchboard-2.2.0/extras/session-capsule/claude_session_capsule.py +275 -0
  13. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/pyproject.toml +1 -1
  14. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/ROADMAP.md +253 -0
  15. agent_switchboard-2.2.0/roadmap/items/cross-key-rendezvous.yaml +73 -0
  16. agent_switchboard-2.2.0/roadmap/items/known-rooms-address-book.yaml +114 -0
  17. agent_switchboard-2.2.0/roadmap/items/stale-token-in-session-env.yaml +46 -0
  18. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/__init__.py +1 -1
  19. agent_switchboard-2.2.0/src/switchboard/claude_session.py +658 -0
  20. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/cli.py +971 -109
  21. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/client.py +12 -14
  22. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/crypto.py +94 -20
  23. agent_switchboard-2.2.0/src/switchboard/handoff.py +599 -0
  24. agent_switchboard-2.2.0/src/switchboard/knownrooms.py +388 -0
  25. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/mcp_server.py +144 -4
  26. agent_switchboard-2.2.0/src/switchboard/skill/history/022-SKILL.md +772 -0
  27. agent_switchboard-2.2.0/src/switchboard/skill/history/023-SKILL.md +253 -0
  28. agent_switchboard-2.2.0/src/switchboard/skill/history/024-SKILL.md +261 -0
  29. agent_switchboard-2.2.0/src/switchboard/skill/history/025-SKILL.md +283 -0
  30. agent_switchboard-2.2.0/src/switchboard/skill/switchboard-coordinate/SKILL.md +311 -0
  31. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/store.py +20 -2
  32. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/writekey.py +3 -1
  33. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/conftest.py +10 -1
  34. agent_switchboard-2.2.0/tests/test_claude_session.py +430 -0
  35. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_global_flags.py +3 -0
  36. agent_switchboard-2.2.0/tests/test_cli_session.py +318 -0
  37. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_crypto.py +9 -0
  38. agent_switchboard-2.2.0/tests/test_evening_replayed.py +229 -0
  39. agent_switchboard-2.2.0/tests/test_handoff.py +591 -0
  40. agent_switchboard-2.2.0/tests/test_known_rooms.py +278 -0
  41. agent_switchboard-2.2.0/tests/test_mcp_session.py +228 -0
  42. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_rendezvous.py +6 -3
  43. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_transient_failures.py +5 -1
  44. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_wake_listener.py +190 -1
  45. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_whisper.py +80 -12
  46. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.dockerignore +0 -0
  47. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/ci.yml +0 -0
  48. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/deploy.yml +0 -0
  49. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/pages.yml +0 -0
  50. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/publish-viewer.yml +0 -0
  51. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/publish.yml +0 -0
  52. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/roadmap.yml +0 -0
  53. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.gitignore +0 -0
  54. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/AGENTS.md +0 -0
  55. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/Dockerfile +0 -0
  56. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/LICENSE +0 -0
  57. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/demo/README.md +0 -0
  58. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/demo/chess.sh +0 -0
  59. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/demo/run.sh +0 -0
  60. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docker-compose.yml +0 -0
  61. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/adaptive-timing.md +0 -0
  62. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/api.md +0 -0
  63. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/codex-cli.md +0 -0
  64. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/deployment.md +0 -0
  65. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/drills.md +0 -0
  66. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/environments.md +0 -0
  67. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/images/viewer.png +0 -0
  68. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/layers.md +0 -0
  69. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/managed-hub.md +0 -0
  70. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/model.md +0 -0
  71. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/seam.md +0 -0
  72. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/testing.md +0 -0
  73. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/viewer.md +0 -0
  74. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/why-this-exists.md +0 -0
  75. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/examples/coordinated_worker.py +0 -0
  76. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/README.md +0 -0
  77. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/pyproject.toml +0 -0
  78. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/__init__.py +0 -0
  79. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/__main__.py +0 -0
  80. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/viewer.py +0 -0
  81. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/web/index.html +0 -0
  82. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/web/render.js +0 -0
  83. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/web/switchboard-open.js +0 -0
  84. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/web/switchboard-room.js +0 -0
  85. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/README.md +0 -0
  86. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/arcs/distribution.yaml +0 -0
  87. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/arcs/hub-boundary.yaml +0 -0
  88. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/arcs/setup-and-first-run.yaml +0 -0
  89. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/arcs/ttl-ceilings.yaml +0 -0
  90. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/a-lobby-derived-from-the-key.yaml +0 -0
  91. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/abuse-control-after-authorization.yaml +0 -0
  92. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/board-ttl-ceiling.yaml +0 -0
  93. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/ci-workspace-is-public.yaml +0 -0
  94. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/clients-that-cannot-post.yaml +0 -0
  95. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/connect-failure-message.yaml +0 -0
  96. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/every-silent-failure-looks-like-a-quiet-room.yaml +0 -0
  97. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/hooks-warning-false-positive.yaml +0 -0
  98. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/hub-origin-reachable-bypassing-the-edge.yaml +0 -0
  99. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/identity-rebinds-on-branch-change.yaml +0 -0
  100. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/init-writes-rooms-file.yaml +0 -0
  101. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/intermittent-suite-failure.yaml +0 -0
  102. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/joining-agent-sees-empty-inbox.yaml +0 -0
  103. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/one-resolved-context-across-surfaces.yaml +0 -0
  104. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/presence-ttl-is-not-one-size.yaml +0 -0
  105. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/provisioned-token-is-stale-and-nothing-says-so.yaml +0 -0
  106. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/publish-hub-container-image.yaml +0 -0
  107. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/read-only-rooms.yaml +0 -0
  108. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/robots-policy-for-public-hosts.yaml +0 -0
  109. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/roles-and-authority-between-agents.yaml +0 -0
  110. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/seal-agent-meta.yaml +0 -0
  111. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/selective-wake-for-the-listener.yaml +0 -0
  112. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/stale-resolver-references.yaml +0 -0
  113. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/standing-checks-that-nothing-runs.yaml +0 -0
  114. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/timing-cold-start-in-ephemeral-environments.yaml +0 -0
  115. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/ttl-clamped-silently.yaml +0 -0
  116. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/unread-dms-not-shown-outside-mcp.yaml +0 -0
  117. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/write-parity-across-surfaces.yaml +0 -0
  118. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/scripts/deploy.sh +0 -0
  119. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/auth.py +0 -0
  120. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/config.py +0 -0
  121. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/drill.py +0 -0
  122. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/guidance.py +0 -0
  123. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/holds.py +0 -0
  124. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/invite.py +0 -0
  125. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/load.py +0 -0
  126. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/notify.py +0 -0
  127. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/peers.py +0 -0
  128. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/rendezvous.py +0 -0
  129. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/rooms.py +0 -0
  130. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/scripts/wake-on-message.sh +0 -0
  131. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/server.py +0 -0
  132. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/signing.py +0 -0
  133. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/001-SKILL.md +0 -0
  134. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/002-SKILL.md +0 -0
  135. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/003-SKILL.md +0 -0
  136. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/004-SKILL.md +0 -0
  137. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/005-SKILL.md +0 -0
  138. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/006-SKILL.md +0 -0
  139. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/007-SKILL.md +0 -0
  140. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/008-SKILL.md +0 -0
  141. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/009-SKILL.md +0 -0
  142. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/010-SKILL.md +0 -0
  143. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/011-SKILL.md +0 -0
  144. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/012-SKILL.md +0 -0
  145. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/013-SKILL.md +0 -0
  146. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/014-SKILL.md +0 -0
  147. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/015-SKILL.md +0 -0
  148. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/016-SKILL.md +0 -0
  149. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/017-SKILL.md +0 -0
  150. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/018-SKILL.md +0 -0
  151. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/019-SKILL.md +0 -0
  152. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/020-SKILL.md +0 -0
  153. /agent_switchboard-2.0.1/src/switchboard/skill/switchboard-coordinate/SKILL.md → /agent_switchboard-2.2.0/src/switchboard/skill/history/021-SKILL.md +0 -0
  154. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/spec.py +0 -0
  155. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/testing.py +0 -0
  156. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/timing.py +0 -0
  157. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/README.md +0 -0
  158. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/__init__.py +0 -0
  159. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/analysis.py +0 -0
  160. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/economy.py +0 -0
  161. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/flow.py +0 -0
  162. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/llm.py +0 -0
  163. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/manager.py +0 -0
  164. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/report.py +0 -0
  165. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/run.py +0 -0
  166. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier1.json +0 -0
  167. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier1_labour.json +0 -0
  168. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier1_rounds.json +0 -0
  169. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_bound.json +0 -0
  170. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_built.json +0 -0
  171. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_free.json +0 -0
  172. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_paid.json +0 -0
  173. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_spend.json +0 -0
  174. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_told.json +0 -0
  175. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed41_bound.json +0 -0
  176. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/traders.py +0 -0
  177. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter_experiment.py +0 -0
  178. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter_llm_experiment.py +0 -0
  179. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_harness.py +0 -0
  180. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/RESULTS.md +0 -0
  181. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_A_s31337.txt +0 -0
  182. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_A_s4242.txt +0 -0
  183. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_A_s7777.txt +0 -0
  184. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_B_s31337.txt +0 -0
  185. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_B_s4242.txt +0 -0
  186. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_B_s7777.txt +0 -0
  187. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_C_s31337.txt +0 -0
  188. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_C_s4242.txt +0 -0
  189. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_C_s7777.txt +0 -0
  190. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_A_s31337.json +0 -0
  191. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_A_s4242.json +0 -0
  192. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_A_s7777.json +0 -0
  193. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_B_s31337.json +0 -0
  194. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_B_s4242.json +0 -0
  195. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_B_s7777.json +0 -0
  196. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_C_s31337.json +0 -0
  197. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_C_s4242.json +0 -0
  198. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_C_s7777.json +0 -0
  199. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_A_s31337.json +0 -0
  200. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_A_s4242.json +0 -0
  201. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_A_s7777.json +0 -0
  202. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_B_s31337.json +0 -0
  203. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_B_s4242.json +0 -0
  204. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_B_s7777.json +0 -0
  205. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_C_s31337.json +0 -0
  206. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_C_s4242.json +0 -0
  207. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_C_s7777.json +0 -0
  208. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_A_s31337.json +0 -0
  209. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_A_s4242.json +0 -0
  210. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_A_s7777.json +0 -0
  211. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_B_s31337.json +0 -0
  212. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_B_s4242.json +0 -0
  213. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_B_s7777.json +0 -0
  214. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_C_s31337.json +0 -0
  215. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_C_s4242.json +0 -0
  216. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_C_s7777.json +0 -0
  217. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_run.py +0 -0
  218. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/timing_experiment.py +0 -0
  219. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_api.py +0 -0
  220. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_arrive.py +0 -0
  221. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_auth.py +0 -0
  222. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_barter.py +0 -0
  223. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_barter_llm.py +0 -0
  224. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_board.py +0 -0
  225. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_checkin.py +0 -0
  226. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_config.py +0 -0
  227. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_init.py +0 -0
  228. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_serve.py +0 -0
  229. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_timing.py +0 -0
  230. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_client_invite.py +0 -0
  231. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_config.py +0 -0
  232. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cors_write_headers.py +0 -0
  233. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_dash_ids.py +0 -0
  234. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_declare.py +0 -0
  235. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_deployment.py +0 -0
  236. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_drill.py +0 -0
  237. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_example_viewer.py +0 -0
  238. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_failure_messages.py +0 -0
  239. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_help.py +0 -0
  240. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_invite.py +0 -0
  241. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_isolated_hub.py +0 -0
  242. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_leave_a_ghost.py +0 -0
  243. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_load.py +0 -0
  244. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_lobby.py +0 -0
  245. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_mcp.py +0 -0
  246. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_notify.py +0 -0
  247. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_rooms.py +0 -0
  248. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_spec.py +0 -0
  249. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_store.py +0 -0
  250. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_surface_parity.py +0 -0
  251. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_testing.py +0 -0
  252. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_tier2_harness.py +0 -0
  253. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_timing.py +0 -0
  254. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_timing_experiment.py +0 -0
  255. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_version.py +0 -0
  256. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_web_invite.py +0 -0
  257. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_web_page.py +0 -0
  258. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_web_reader.py +0 -0
  259. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_web_snapshot.py +0 -0
  260. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_workflow_token.py +0 -0
  261. {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_write_keys.py +0 -0
@@ -79,7 +79,7 @@ under pressure.
79
79
 
80
80
  ### 🟠 The first ten minutes, for somebody who has not read the docs
81
81
 
82
- `setup-and-first-run` · 15 item(s), 10 startable
82
+ `setup-and-first-run` · 18 item(s), 12 startable
83
83
 
84
84
  `https://github.com/gald33/switchboard/issues/86` · `https://github.com/gald33/switchboard/issues/88` · `https://github.com/gald33/switchboard/issues/89` · `docs/environments.md`
85
85
 
@@ -102,7 +102,9 @@ wrong says so.
102
102
  | item | status | priority |
103
103
  |---|---|---|
104
104
  | `connect-failure-message` | done | next |
105
+ | `cross-key-rendezvous` | ready | next |
105
106
  | `init-writes-rooms-file` | ready | next |
107
+ | `known-rooms-address-book` | done | next |
106
108
  | `a-lobby-derived-from-the-key` | ready | — |
107
109
  | `clients-that-cannot-post` | ready | — |
108
110
  | `every-silent-failure-looks-like-a-quiet-room` | ready | — |
@@ -112,6 +114,7 @@ wrong says so.
112
114
  | `presence-ttl-is-not-one-size` | ready | — |
113
115
  | `robots-policy-for-public-hosts` | deferred | — |
114
116
  | `selective-wake-for-the-listener` | ready | — |
117
+ | `stale-token-in-session-env` | ready | — |
115
118
  | `timing-cold-start-in-ephemeral-environments` | deferred | — |
116
119
  | `unread-dms-not-shown-outside-mcp` | ready | — |
117
120
  | `write-parity-across-surfaces` | ready | — |
@@ -22,6 +22,8 @@ no service to run for the tests — they spin the app up in-process.
22
22
  | `cli.py` | The `switchboard` command. |
23
23
  | `mcp_server.py` | MCP stdio bridge — speaks JSON-RPC directly, no SDK. |
24
24
  | `config.py` | Env-driven settings and the TTL defaults/ceilings. |
25
+ | `claude_session.py` | Claude Code harness adapter: locate, package, install, resume a session's transcript; stdlib only. |
26
+ | `handoff.py` | Session capsules over the hub: board value + signed pointer; hub-neutral. |
25
27
 
26
28
  The dependency direction is one-way: `store` knows nothing about HTTP,
27
29
  `server` knows nothing about the CLI, and `cli`/`mcp_server` both go through
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-switchboard
3
- Version: 2.0.1
3
+ Version: 2.2.0
4
4
  Summary: An ephemeral orchestration hub for AI coding agents: presence, leases, messaging and a shared blackboard, all with TTLs.
5
5
  Project-URL: Homepage, https://github.com/gald33/switchboard
6
6
  Project-URL: Issues, https://github.com/gald33/switchboard/issues
@@ -306,6 +306,7 @@ Agents then get these as native tools:
306
306
  | `say` / `dm` / `inbox` / `history` | channel and direct messaging |
307
307
  | `board_set` / `board_get` / `board_list` | shared scratch space |
308
308
  | `checkin` | heartbeat + renew leases + drain inbox, in one call |
309
+ | `session_handoff` / `session_import` / `session_resume` | move a whole Claude Code session to another environment |
309
310
 
310
311
  `checkin` is the one that matters most in practice: a single tool call that
311
312
  keeps the agent alive, renews everything it holds, and hands back anything
@@ -268,6 +268,7 @@ Agents then get these as native tools:
268
268
  | `say` / `dm` / `inbox` / `history` | channel and direct messaging |
269
269
  | `board_set` / `board_get` / `board_list` | shared scratch space |
270
270
  | `checkin` | heartbeat + renew leases + drain inbox, in one call |
271
+ | `session_handoff` / `session_import` / `session_resume` | move a whole Claude Code session to another environment |
271
272
 
272
273
  `checkin` is the one that matters most in practice: a single tool call that
273
274
  keeps the agent alive, renews everything it holds, and hands back anything
@@ -104,7 +104,7 @@ claude mcp add switchboard \
104
104
  ```
105
105
 
106
106
  Verify with `/mcp` inside Claude Code — you should see `switchboard` connected
107
- with 14 tools.
107
+ with 26 tools.
108
108
 
109
109
  ## 4. Tell the agent to use it
110
110
 
@@ -286,6 +286,53 @@ switchboard listen --until forecast:p50 --effort medium
286
286
  switchboard listen --until +900
287
287
  ```
288
288
 
289
+ One process parks in the rooms that matter: the one named, the lobby every
290
+ holder of this key shares, and every room this machine was put in lately. That is the default because a parked agent is the most
291
+ reachable it ever is, and a peer on another repo holding the same key has
292
+ nowhere else to look for it. The exit line names both, the wake payload says
293
+ which room the message came from (`room` and `role`, beside `messages`), and a
294
+ message in either is the wake. `--no-lobby` parks in the named room only, for a
295
+ session that specifically does not want to be found while it works; without a
296
+ key there is no lobby to derive, and the listener says so. When both rooms
297
+ have a message in the same instant, the process — not the agent — decides
298
+ which is the wake: the named room today, and a rule that lives in one place
299
+ (`_choose_wake`) if that ever needs to change.
300
+
301
+ ### The rooms this machine knows
302
+
303
+ A session accumulates rooms — the repo's, one per invite it was handed, a side
304
+ room per `keygen`, another repo's — and the failure that follows is not
305
+ "no listener" but "a listener in the wrong two rooms". So the tool keeps a
306
+ book of them, per machine, at `~/.switchboard/known-rooms.json`
307
+ (`SWITCHBOARD_KNOWN_ROOMS` moves it; empty disables it), written by the
308
+ commands that already hold the coordinates: `init`, `join`, `keygen
309
+ --as-invite`, and any `--invite <command>`. Nothing is asked of the agent.
310
+
311
+ Two things use it, and they are different questions:
312
+
313
+ - **Looking for someone.** `switchboard rendezvous <topic>` sweeps every
314
+ known room as well as this one — read-only, no note left anywhere but here
315
+ — and reports per room who is there and who has a listener parked.
316
+ `switchboard find <name or branch>` is the same sweep with a peer in mind.
317
+ `--here` keeps `rendezvous` to this room. `switchboard --room <label>
318
+ <command>` then runs one command in the room the sweep pointed at.
319
+ - **Expecting someone.** `listen` parks in this room, the lobby, and every
320
+ room this machine joined, was invited into or minted in the last hour —
321
+ without being told. `--in <label>` adds a known room; `--only <label>`
322
+ parks there and nowhere else.
323
+
324
+ `switchboard rooms --known` shows the book; `--forget <label>` drops an entry;
325
+ `--label <workspace>=<name>` names one.
326
+
327
+ **What the book holds is references, never keys.** An entry records how a
328
+ room's key was acquired — the environment variable that holds it, the
329
+ checkout `init` wrote it to, or the invite it arrived as — and the tool
330
+ resolves it the same way at use time. An invite was already in the session's
331
+ context, so keeping it exposes nothing new; a key that never appeared in the
332
+ conversation is never copied into the file, printed, or retyped. A room whose
333
+ key can no longer be found that way is reported as such, not opened in the
334
+ clear.
335
+
289
336
  `forecast:p50` takes the time from the agent's own
290
337
  [adaptive-timing](adaptive-timing.md) model — the moment it predicts it would
291
338
  next have looked anyway — which turns a forecast from something advisory into
@@ -336,6 +383,156 @@ filled in. One implementation, reachable two ways. The command is the one to
336
383
  reach for otherwise: the shim bakes in a single room and cannot leave it, which
337
384
  is right for a repo's own agents and wrong for anything crossing repos.
338
385
 
386
+ ## 7. Hand a whole session to another environment
387
+
388
+ Everything above moves *facts* between sessions — a claim, a message, a
389
+ payload on the board. Sometimes what should move is the session itself: the
390
+ conversation a laptop started is the one a cloud runner should continue,
391
+ tool results and all, rather than a summary of it the next agent re-derives
392
+ at cold-start prices. A Claude Code session turns out to be portable as one
393
+ file, and Switchboard carries that file the way it carries any other handoff
394
+ — payload on the blackboard, pointer in a message — with nothing added to
395
+ the hub: no endpoint, no schema, no fifth primitive.
396
+
397
+ The facts this rests on were established empirically and are logged in
398
+ [`extras/session-capsule/README.md`](../extras/session-capsule/README.md);
399
+ the short version is what the code relies on. Claude Code writes every turn,
400
+ tool call and tool result of a session to
401
+ `<config-dir>/projects/<project-key>/<id>.jsonl` — `<config-dir>` is
402
+ `$CLAUDE_CONFIG_DIR`, else `~/.claude`; `<project-key>` is the working
403
+ directory with every character outside `[A-Za-z0-9]` replaced by `-` — plus
404
+ an optional `<id>/` sidecar directory when the session spawned subagents.
405
+ Copy that file under any project key on another machine and `claude --resume
406
+ <id>` continues the conversation there, from a different directory, with no
407
+ path rewriting and no other state. `claude --bg --resume <id>` does the same
408
+ as a background session that `claude attach <short-id>` opens later. The
409
+ transcript is carried byte for byte and never interpreted.
410
+
411
+ ### The flow
412
+
413
+ On the side handing off — inside the session, where Claude Code exports
414
+ `CLAUDE_CODE_SESSION_ID` and the MCP bridge and hook commands inherit it:
415
+
416
+ ```
417
+ session_handoff to="<agent id from roster>"
418
+ ```
419
+
420
+ or from a shell, `switchboard session handoff <agent>`, with `<agent>`
421
+ resolved the way `dm` resolves it: a roster id, or a unique name or branch.
422
+ One call exports the session, publishes it on the board under
423
+ `sessions/<session-id>` with a ten-minute TTL (`--ttl` / `ttl` changes it,
424
+ never past the hub's board ceiling), and sends the recipient a signed
425
+ pointer as a direct message. The result says how many bytes went, when the
426
+ capsule expires, and which leases you still hold. Those are listed in the
427
+ pointer and *kept* unless you pass `--release-leases` / `release_leases`:
428
+ the sender is still running when it hands off, the receiver may never
429
+ appear, and a lease nobody holds is exactly the window a third agent walks
430
+ into.
431
+
432
+ On the side receiving:
433
+
434
+ ```
435
+ session_import → installed: [{session_id, resume, …}]
436
+ session_resume session_id="<id>" → attach: "claude attach <short-id>"
437
+ ```
438
+
439
+ or `switchboard session receive`, then `switchboard session resume <id>`
440
+ (`--bg` for a background session, `--command` to print the line instead of
441
+ running it). `receive` reads this agent's own inbox — a committed read, so
442
+ the pointer is consumed rather than collected again on every read for ten
443
+ minutes — verifies the pointer's signature against the roster and the
444
+ capsule's sha256 against what the pointer announced, deletes the board
445
+ entry, and only then installs the files. The delete is the claim: the hub
446
+ answers it inside one transaction, so of two receivers exactly one installs
447
+ and the other is told the capsule was taken. Then it sends the sender a
448
+ receipt. On the CLI the files land where the session already lives on this
449
+ machine if it does (the return leg of a round trip), else under the capsule's
450
+ original project key, unless `--cwd` names the directory you will resume
451
+ from; the MCP tool defaults to this session's project directory. Either way
452
+ the id ends up
453
+ under exactly one key, because `claude --resume` refuses to choose between
454
+ duplicates. Other direct messages the read consumed come back as `other`
455
+ rather than being lost.
456
+
457
+ Local → cloud → local is then three commands and no hub change: `session
458
+ handoff` on the laptop, `session receive` in the cloud session followed by
459
+ `claude --resume`, and the same pair back when the cloud side is done. The
460
+ hub saw two sealed values it cannot name — the pointer's discriminator
461
+ travels inside the sealed body and the message type stays `note`, so not
462
+ even the row type says what moved.
463
+
464
+ `switchboard session export -o FILE` and `switchboard session import FILE`
465
+ are the two halves with no hub between them: a capsule file, created `0600`
466
+ because it is as private as the transcript it carries, that you move however
467
+ you like.
468
+
469
+ ### What is trusted, and what is not
470
+
471
+ A transcript is instructions. Whoever resumes it runs a conversation
472
+ somebody else wrote, with their own credentials and their own repo, so the
473
+ rules here are stricter than for any other payload, and each is a refusal
474
+ rather than a warning:
475
+
476
+ - **Nothing installs from a pointer nobody vouches for.** The sender must be
477
+ on the roster and the pointer's signature must verify, and the capsule
478
+ must be the bytes that pointer announced. A pointer that fails either
479
+ comes back as not installed, with the reason; `--unverified` /
480
+ `unverified` overrides that, on your own say-so.
481
+ - **Nothing resumes on its own.** `session_import` installs; `session_resume`
482
+ is a separate call. On the CLI, `session receive --resume` runs `claude
483
+ --resume` afterwards only for senders named with `--from <agent-id>` (or
484
+ `--any-sender`), and `--bg` makes that a background session.
485
+ - **A plaintext room is refused.** The hub would hold everything the session
486
+ read — every file and secret in a tool result — in the clear. Hand off
487
+ inside a keyed room, or pass `--allow-plaintext` / `allow_plaintext` when
488
+ the hub is yours and local.
489
+ - **A checkpoint has no sender.** `switchboard session publish` (or
490
+ `session_handoff` with no `to`) puts the capsule on the board with nobody
491
+ pointed at it; anyone holding the key can collect it by id — `switchboard
492
+ session receive <id>`, `session_import(session_id=…)` — while it lasts.
493
+ That is trusting the room, and the result says so: `verified: null`.
494
+
495
+ What the design does not protect against, stated plainly. Any holder of the
496
+ room key can delete or replace a board entry — the hub has no owners — so a
497
+ handoff can be made to vanish in transit, though the signature check means
498
+ it cannot be *substituted* without the receiver noticing. In a keyed room
499
+ every `board_list` fetches every value, because keys are blinded and a
500
+ prefix cannot hide one; the ten-minute TTL and the delete-on-claim are what
501
+ bound that cost, and a handoff nobody collected is re-sent, not recovered.
502
+ Version skew between Claude Code releases, and the interactive trust prompt
503
+ on a first resume, are untested. And a session resumed on another host
504
+ derives a different Switchboard agent id unless `SWITCHBOARD_AGENT_ID` is
505
+ pinned: the same conversation, but not, to the roster, the same agent.
506
+
507
+ ### Without a model in the loop
508
+
509
+ Every step is a plain function, so none of this needs an LLM to drive it. A
510
+ Claude Code `Stop` hook can run `switchboard session handoff <agent>` or
511
+ `switchboard session publish` so a session checkpoints itself — opt-in, and
512
+ deliberately not among the hooks `init` installs, which stay runner-agnostic.
513
+ Know what `Stop` means here: it fires at the end of every response, not once
514
+ when the session ends (the installed Stop hook releases leases on the same
515
+ cadence), so a checkpoint hook publishes a capsule per turn. Give it a short
516
+ `--ttl`; in a keyed room every board listing pays for the capsule until it
517
+ expires. And a parked receiver can loop:
518
+
519
+ ```bash
520
+ export SWITCHBOARD_AGENT_ID=cloud-receiver
521
+ while true; do
522
+ switchboard session receive --wait 25 --resume --bg --from <sender-id>
523
+ done
524
+ ```
525
+
526
+ `receive` prints `listening as <agent id>` so the sender knows whom to hand
527
+ off to. Pin `SWITCHBOARD_AGENT_ID` for that loop: an unpinned id is derived
528
+ per process, so each iteration would listen somewhere new and a pointer sent
529
+ to the last one is read by nobody. The exit code is what the loop branches
530
+ on — `0` when something was installed (or nothing was pending, without
531
+ `--wait`), `2` when `--wait` elapsed with nothing, and `1` when a pointer or
532
+ an id named a capsule that could not be installed: expired, claimed by
533
+ another receiver, unverified, or not what was announced. That last one is
534
+ the case to log.
535
+
339
536
  ## Tool reference
340
537
 
341
538
  | Tool | Use it when |
@@ -354,6 +551,9 @@ is right for a repo's own agents and wrong for anything crossing repos.
354
551
  | `board_set` / `board_get` / `board_list` | Hand off structured context |
355
552
  | `keygen` | Starting a private side-conversation with specific peers — see below |
356
553
  | `join_room` | Enter a room somebody sent an invite for — see below |
554
+ | `session_handoff` | Move this whole session to another agent, or with no `to` checkpoint it on the board for anyone holding the key — see section 7 |
555
+ | `session_import` | Collect a session handed to you (or one capsule by id) into this machine's Claude config dir; installs, never resumes |
556
+ | `session_resume` | Start `claude --bg --resume` for an installed session and get the `claude attach` line back. Local; never touches the hub |
357
557
 
358
558
  ### A room somebody sent you
359
559
 
@@ -134,6 +134,12 @@ which is how you do leader election without a lease.
134
134
  Default TTL is 24 hours, the longest in the system, because a handoff has to
135
135
  survive the gap between one session ending and the next starting.
136
136
 
137
+ A whole Claude Code session can travel the same way, as an opaque capsule
138
+ under `sessions/<session-id>` with a TTL of minutes rather than a day, since
139
+ a transcript is everything the session read; the receiver's tool collects and
140
+ deletes it rather than reading it as text — see
141
+ [claude-code.md](claude-code.md#7-hand-a-whole-session-to-another-environment).
142
+
137
143
  ## Putting it together
138
144
 
139
145
  The intended shape of an agent's life on a hub:
@@ -700,8 +700,13 @@ already published.
700
700
  > **Named `ask` in 0.11.0.** The name said "question" when the primitive is
701
701
  > "sealed to one peer" — you may equally want to *tell* someone something the
702
702
  > room should not read. 1.0.0 renamed the tool, CLI command and client method
703
- > to `whisper`. `Client.ask` still works as a deprecated alias; the CLI takes
704
- > `whisper` only. The wire format is unchanged, so 0.11.0 peers stay readable.
703
+ > to `whisper` and left the wire alone: the envelope marker, the HKDF label
704
+ > and the AEAD context all still said `ask`, on the reasoning that a name only
705
+ > humans read was not worth a break. **2.1.0 renamed the wire too**, after a
706
+ > second implementation written to the human name opened whispers under the
707
+ > wrong context for four days without an error. A 2.1.0 reader still opens
708
+ > what any earlier release sealed; an earlier reader cannot open a 2.1.0
709
+ > whisper. `Client.ask` is gone. See [upgrading.md](upgrading.md#201--210).
705
710
 
706
711
  What it costs, stated as plainly as everything above:
707
712
 
@@ -19,7 +19,7 @@ switchboard serve --port 8787
19
19
  You should see:
20
20
 
21
21
  ```
22
- switchboard 2.0.1 → http://127.0.0.1:8787 db=switchboard.db
22
+ switchboard 2.2.0 → http://127.0.0.1:8787 db=switchboard.db
23
23
  ```
24
24
 
25
25
  Leave it running.
@@ -47,7 +47,7 @@ export SWITCHBOARD_AGENT_ID=beta
47
47
 
48
48
  ```bash
49
49
  $ switchboard health
50
- { "ok": true, "version": "2.0.1", "auth": true }
50
+ { "ok": true, "version": "2.2.0", "auth": true }
51
51
 
52
52
  $ switchboard register -c build
53
53
  registered local-main-yourhost (local) in demo
@@ -3,6 +3,71 @@
3
3
  What changes for you between releases that are not backwards compatible, in
4
4
  the order you will hit them. Everything not listed here kept its behaviour.
5
5
 
6
+ ## 2.1.0 → 2.2.0
7
+
8
+ Additive. A whole Claude Code session can be moved between environments
9
+ through the hub: `switchboard session handoff <agent>` publishes the
10
+ conversation as a capsule sealed to the room, and `switchboard session
11
+ receive` collects it and installs it for `claude --resume`. `export` and
12
+ `import` do the two halves against a file instead. The capsule is a
13
+ blackboard entry that expires on its own and is deleted the moment it is
14
+ collected; the hub never opens it. Nothing that worked in 2.1.0 changed.
15
+
16
+ The commands are new, so a peer running 2.1.0 or earlier cannot collect a
17
+ capsule you publish and will not recognise the pointer. Both ends need 2.2.0.
18
+
19
+ ## 2.0.1 → 2.1.0
20
+
21
+ **The whisper envelope says `whisper` on the wire.** From 0.11.0 to 2.0.1 it
22
+ said `ask` in three places a reader never sees — the envelope marker
23
+ `m`, the HKDF label the pair key is derived under, and the AEAD context the
24
+ body is sealed under (`ask.body`) — while the tool, CLI and method had said
25
+ `whisper` since 1.0.0. The split was kept on purpose, to spare a break for a
26
+ name only humans read; it cost more than it saved when a page written to
27
+ the human name opened whispers under `message.body` and every one came back
28
+ unreadable, with no error to say why. Now the wire says what the tool says:
29
+ `m: "whisper"`, `switchboard/v1/whisper`, `whisper.body`.
30
+
31
+ ### Upgrade readers before senders
32
+
33
+ A 2.1.0 client **opens** everything an earlier one sealed — the marker says
34
+ which names an envelope was sealed under, and the old names are still
35
+ accepted on the way in. The other direction does not hold: **a 2.0.1 client
36
+ cannot open a 2.1.0 whisper**, and reports it `unreadable` exactly as it
37
+ would a sender whose exchange key it has not seen. So upgrade the agents
38
+ that *receive* whispers first, and the ones that send them last. In a room
39
+ where one process does both, upgrade it with everybody it whispers to. The
40
+ hub is not involved — it never opens an envelope — and needs no change.
41
+
42
+ ### `Client.ask` is removed
43
+
44
+ The deprecated alias for `whisper`, kept since 1.0.0, is gone from the sync
45
+ and async clients. Nothing else answered to the old name any longer. The
46
+ wire `type` `"ask"` a 0.11.0 sender puts on a message still opens.
47
+
48
+ ### Also new, none of it breaking
49
+
50
+ - **A book of known rooms**, per machine, at `~/.switchboard/known-rooms.json`.
51
+ Written by `init`, `join`, `keygen --as-invite`, any `--invite` command and
52
+ any command run inside an `init`-ed checkout. It holds *references* — the
53
+ variable, the checkout, or the invite a key arrived as — never a key's
54
+ value. `SWITCHBOARD_KNOWN_ROOMS` moves it; set it empty to disable it.
55
+ `switchboard rooms --known`, `--forget`, `--label` manage it.
56
+ - **`rendezvous` sweeps every known room** by default, read-only, and reports
57
+ per room who is there and who has a listener parked; `--here` keeps it to
58
+ this room. New `switchboard find <name or branch>` is the same sweep with a
59
+ peer in mind. `--room <label>` runs any command in a known room.
60
+ - **`listen` parks in more than one room**, in one process: this room, the
61
+ lobby every holder of your key shares, and every room this machine joined,
62
+ was invited into or minted in the last hour. `--no-lobby`, `--in <label>`
63
+ and `--only <label>` adjust that. The wake payload keeps `messages` first
64
+ and adds `room`, `role` and `agent_id`.
65
+ - **Minted keys never start with a hyphen**, so `--key <key>` cannot be read
66
+ as a flag. Existing keys are unaffected.
67
+ - **The coordination skill is a third the length**, with one table naming
68
+ every primitive on both surfaces; `init` upgrades an installed copy in
69
+ place, as it does for every recorded revision.
70
+
6
71
  ## 2.0.0 → 2.0.1
7
72
 
8
73
  Server only. The hub's CORS allow-list now includes the two write-key