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.
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/ARCS.md +4 -1
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/CONTRIBUTING.md +2 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/PKG-INFO +2 -1
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/README.md +1 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/claude-code.md +201 -1
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/concepts.md +6 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/encryption.md +7 -2
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/quickstart.md +2 -2
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/upgrading.md +65 -0
- agent_switchboard-2.2.0/extras/session-capsule/README.md +260 -0
- agent_switchboard-2.2.0/extras/session-capsule/cc.sh +7 -0
- agent_switchboard-2.2.0/extras/session-capsule/claude_session_capsule.py +275 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/pyproject.toml +1 -1
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/ROADMAP.md +253 -0
- agent_switchboard-2.2.0/roadmap/items/cross-key-rendezvous.yaml +73 -0
- agent_switchboard-2.2.0/roadmap/items/known-rooms-address-book.yaml +114 -0
- agent_switchboard-2.2.0/roadmap/items/stale-token-in-session-env.yaml +46 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/__init__.py +1 -1
- agent_switchboard-2.2.0/src/switchboard/claude_session.py +658 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/cli.py +971 -109
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/client.py +12 -14
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/crypto.py +94 -20
- agent_switchboard-2.2.0/src/switchboard/handoff.py +599 -0
- agent_switchboard-2.2.0/src/switchboard/knownrooms.py +388 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/mcp_server.py +144 -4
- agent_switchboard-2.2.0/src/switchboard/skill/history/022-SKILL.md +772 -0
- agent_switchboard-2.2.0/src/switchboard/skill/history/023-SKILL.md +253 -0
- agent_switchboard-2.2.0/src/switchboard/skill/history/024-SKILL.md +261 -0
- agent_switchboard-2.2.0/src/switchboard/skill/history/025-SKILL.md +283 -0
- agent_switchboard-2.2.0/src/switchboard/skill/switchboard-coordinate/SKILL.md +311 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/store.py +20 -2
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/writekey.py +3 -1
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/conftest.py +10 -1
- agent_switchboard-2.2.0/tests/test_claude_session.py +430 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_global_flags.py +3 -0
- agent_switchboard-2.2.0/tests/test_cli_session.py +318 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_crypto.py +9 -0
- agent_switchboard-2.2.0/tests/test_evening_replayed.py +229 -0
- agent_switchboard-2.2.0/tests/test_handoff.py +591 -0
- agent_switchboard-2.2.0/tests/test_known_rooms.py +278 -0
- agent_switchboard-2.2.0/tests/test_mcp_session.py +228 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_rendezvous.py +6 -3
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_transient_failures.py +5 -1
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_wake_listener.py +190 -1
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_whisper.py +80 -12
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.dockerignore +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/ci.yml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/deploy.yml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/pages.yml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/publish-viewer.yml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/publish.yml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.github/workflows/roadmap.yml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/.gitignore +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/AGENTS.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/Dockerfile +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/LICENSE +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/demo/README.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/demo/chess.sh +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/demo/run.sh +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docker-compose.yml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/adaptive-timing.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/api.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/codex-cli.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/deployment.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/drills.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/environments.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/images/viewer.png +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/layers.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/managed-hub.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/model.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/seam.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/testing.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/viewer.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/docs/why-this-exists.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/examples/coordinated_worker.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/README.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/pyproject.toml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/__init__.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/__main__.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/viewer.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/web/index.html +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/web/render.js +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/web/switchboard-open.js +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/extras/viewer/switchboard_viewer/web/switchboard-room.js +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/README.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/arcs/distribution.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/arcs/hub-boundary.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/arcs/setup-and-first-run.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/arcs/ttl-ceilings.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/a-lobby-derived-from-the-key.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/abuse-control-after-authorization.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/board-ttl-ceiling.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/ci-workspace-is-public.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/clients-that-cannot-post.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/connect-failure-message.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/every-silent-failure-looks-like-a-quiet-room.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/hooks-warning-false-positive.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/hub-origin-reachable-bypassing-the-edge.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/identity-rebinds-on-branch-change.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/init-writes-rooms-file.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/intermittent-suite-failure.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/joining-agent-sees-empty-inbox.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/one-resolved-context-across-surfaces.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/presence-ttl-is-not-one-size.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/provisioned-token-is-stale-and-nothing-says-so.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/publish-hub-container-image.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/read-only-rooms.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/robots-policy-for-public-hosts.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/roles-and-authority-between-agents.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/seal-agent-meta.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/selective-wake-for-the-listener.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/stale-resolver-references.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/standing-checks-that-nothing-runs.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/timing-cold-start-in-ephemeral-environments.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/ttl-clamped-silently.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/unread-dms-not-shown-outside-mcp.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/roadmap/items/write-parity-across-surfaces.yaml +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/scripts/deploy.sh +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/auth.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/config.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/drill.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/guidance.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/holds.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/invite.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/load.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/notify.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/peers.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/rendezvous.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/rooms.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/scripts/wake-on-message.sh +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/server.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/signing.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/001-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/002-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/003-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/004-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/005-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/006-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/007-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/008-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/009-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/010-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/011-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/012-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/013-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/014-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/015-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/016-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/017-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/018-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/019-SKILL.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/skill/history/020-SKILL.md +0 -0
- /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
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/spec.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/testing.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/src/switchboard/timing.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/README.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/__init__.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/analysis.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/economy.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/flow.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/llm.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/manager.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/report.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/run.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier1.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier1_labour.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier1_rounds.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_bound.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_built.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_free.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_paid.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_spend.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed1_told.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/tier2_seed41_bound.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter/traders.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter_experiment.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/barter_llm_experiment.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_harness.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/RESULTS.md +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_A_s31337.txt +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_A_s4242.txt +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_A_s7777.txt +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_B_s31337.txt +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_B_s4242.txt +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_B_s7777.txt +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_C_s31337.txt +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_C_s4242.txt +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/prompt_C_s7777.txt +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_A_s31337.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_A_s4242.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_A_s7777.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_B_s31337.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_B_s4242.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_B_s7777.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_C_s31337.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_C_s4242.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_haiku_C_s7777.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_A_s31337.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_A_s4242.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_A_s7777.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_B_s31337.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_B_s4242.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_B_s7777.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_C_s31337.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_C_s4242.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_opus_C_s7777.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_A_s31337.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_A_s4242.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_A_s7777.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_B_s31337.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_B_s4242.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_B_s7777.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_C_s31337.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_C_s4242.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_pilot/response_sonnet_C_s7777.json +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/tier2_run.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/experiments/timing_experiment.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_api.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_arrive.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_auth.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_barter.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_barter_llm.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_board.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_checkin.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_config.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_init.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_serve.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cli_timing.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_client_invite.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_config.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_cors_write_headers.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_dash_ids.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_declare.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_deployment.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_drill.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_example_viewer.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_failure_messages.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_help.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_invite.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_isolated_hub.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_leave_a_ghost.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_load.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_lobby.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_mcp.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_notify.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_rooms.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_spec.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_store.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_surface_parity.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_testing.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_tier2_harness.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_timing.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_timing_experiment.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_version.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_web_invite.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_web_page.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_web_reader.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_web_snapshot.py +0 -0
- {agent_switchboard-2.0.1 → agent_switchboard-2.2.0}/tests/test_workflow_token.py +0 -0
- {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` ·
|
|
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
|
|
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
|
|
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
|
|
704
|
-
>
|
|
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
|
|
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
|
|
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
|