agent-switchboard 2.2.2__tar.gz → 2.3.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.2.2 → agent_switchboard-2.3.0}/ARCS.md +6 -2
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/PKG-INFO +32 -1
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/README.md +31 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/claude-code.md +102 -20
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/codex-cli.md +19 -13
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/concepts.md +16 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/quickstart.md +2 -2
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/session-capsule/README.md +18 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/pyproject.toml +1 -1
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/ROADMAP.md +306 -0
- agent_switchboard-2.3.0/roadmap/items/a-drain-that-retries-forever-never-escalates.yaml +70 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/cross-key-rendezvous.yaml +16 -0
- agent_switchboard-2.3.0/roadmap/items/discovery-is-uneven-and-delivery-is-unknowable.yaml +80 -0
- agent_switchboard-2.3.0/roadmap/items/first-contact-needs-a-key-it-cannot-have.yaml +61 -0
- agent_switchboard-2.3.0/roadmap/items/inbox-consumes-what-it-cannot-open.yaml +50 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/__init__.py +1 -1
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/claude_session.py +57 -6
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/cli.py +250 -21
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/desktop.py +8 -3
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/mcp_server.py +51 -2
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/rendezvous.py +44 -0
- agent_switchboard-2.3.0/src/switchboard/skill/switchboard-coordinate/SKILL.md +332 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/conftest.py +11 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_claude_session.py +65 -7
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_cli_init.py +76 -2
- agent_switchboard-2.3.0/tests/test_sending_opens_a_conversation.py +301 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/.dockerignore +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/.github/workflows/ci.yml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/.github/workflows/deploy.yml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/.github/workflows/pages.yml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/.github/workflows/publish-viewer.yml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/.github/workflows/publish.yml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/.github/workflows/roadmap.yml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/.gitignore +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/AGENTS.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/CONTRIBUTING.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/Dockerfile +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/LICENSE +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/demo/README.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/demo/chess.sh +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/demo/run.sh +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docker-compose.yml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/adaptive-timing.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/api.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/deployment.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/drills.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/encryption.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/environments.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/images/viewer.png +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/layers.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/managed-hub.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/model.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/seam.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/testing.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/upgrading.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/viewer.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/docs/why-this-exists.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/examples/coordinated_worker.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/session-capsule/cc.sh +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/session-capsule/claude_session_capsule.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/viewer/README.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/viewer/pyproject.toml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/viewer/switchboard_viewer/__init__.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/viewer/switchboard_viewer/__main__.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/viewer/switchboard_viewer/viewer.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/viewer/switchboard_viewer/web/index.html +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/viewer/switchboard_viewer/web/render.js +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/viewer/switchboard_viewer/web/switchboard-open.js +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/extras/viewer/switchboard_viewer/web/switchboard-room.js +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/README.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/arcs/distribution.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/arcs/hub-boundary.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/arcs/setup-and-first-run.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/arcs/ttl-ceilings.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/a-lobby-derived-from-the-key.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/abuse-control-after-authorization.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/automatic-session-checkpoints.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/board-ttl-ceiling.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/ci-workspace-is-public.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/clients-that-cannot-post.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/connect-failure-message.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/every-silent-failure-looks-like-a-quiet-room.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/hooks-warning-false-positive.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/hub-origin-reachable-bypassing-the-edge.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/identity-rebinds-on-branch-change.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/init-writes-rooms-file.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/intermittent-suite-failure.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/joining-agent-sees-empty-inbox.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/known-rooms-address-book.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/one-resolved-context-across-surfaces.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/presence-ttl-is-not-one-size.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/provisioned-token-is-stale-and-nothing-says-so.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/publish-hub-container-image.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/read-only-rooms.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/robots-policy-for-public-hosts.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/roles-and-authority-between-agents.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/seal-agent-meta.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/selective-wake-for-the-listener.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/stale-resolver-references.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/stale-token-in-session-env.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/standing-checks-that-nothing-runs.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/timing-cold-start-in-ephemeral-environments.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/ttl-clamped-silently.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/unread-dms-not-shown-outside-mcp.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/roadmap/items/write-parity-across-surfaces.yaml +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/scripts/deploy.sh +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/auth.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/client.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/config.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/crypto.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/drill.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/guidance.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/handoff.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/holds.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/invite.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/knownrooms.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/load.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/notify.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/peers.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/rooms.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/scripts/wake-on-message.sh +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/server.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/signing.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/001-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/002-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/003-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/004-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/005-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/006-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/007-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/008-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/009-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/010-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/011-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/012-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/013-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/014-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/015-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/016-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/017-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/018-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/019-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/020-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/021-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/022-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/023-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/024-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/skill/history/025-SKILL.md +0 -0
- /agent_switchboard-2.2.2/src/switchboard/skill/switchboard-coordinate/SKILL.md → /agent_switchboard-2.3.0/src/switchboard/skill/history/026-SKILL.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/spec.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/store.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/testing.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/timing.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/src/switchboard/writekey.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/README.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/__init__.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/analysis.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/economy.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/flow.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/llm.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/manager.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/report.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/run.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier1.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier1_labour.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier1_rounds.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier2_seed1_bound.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier2_seed1_built.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier2_seed1_free.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier2_seed1_paid.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier2_seed1_spend.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier2_seed1_told.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/tier2_seed41_bound.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter/traders.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter_experiment.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/barter_llm_experiment.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_harness.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/RESULTS.md +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/prompt_A_s31337.txt +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/prompt_A_s4242.txt +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/prompt_A_s7777.txt +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/prompt_B_s31337.txt +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/prompt_B_s4242.txt +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/prompt_B_s7777.txt +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/prompt_C_s31337.txt +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/prompt_C_s4242.txt +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/prompt_C_s7777.txt +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_haiku_A_s31337.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_haiku_A_s4242.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_haiku_A_s7777.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_haiku_B_s31337.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_haiku_B_s4242.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_haiku_B_s7777.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_haiku_C_s31337.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_haiku_C_s4242.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_haiku_C_s7777.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_opus_A_s31337.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_opus_A_s4242.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_opus_A_s7777.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_opus_B_s31337.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_opus_B_s4242.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_opus_B_s7777.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_opus_C_s31337.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_opus_C_s4242.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_opus_C_s7777.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_sonnet_A_s31337.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_sonnet_A_s4242.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_sonnet_A_s7777.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_sonnet_B_s31337.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_sonnet_B_s4242.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_sonnet_B_s7777.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_sonnet_C_s31337.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_sonnet_C_s4242.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_pilot/response_sonnet_C_s7777.json +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/tier2_run.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/experiments/timing_experiment.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_api.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_arrive.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_auth.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_barter.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_barter_llm.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_cli_board.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_cli_checkin.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_cli_config.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_cli_global_flags.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_cli_serve.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_cli_session.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_cli_timing.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_client_invite.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_config.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_cors_write_headers.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_crypto.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_dash_ids.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_declare.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_deployment.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_desktop.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_drill.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_evening_replayed.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_example_viewer.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_failure_messages.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_handoff.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_help.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_invite.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_isolated_hub.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_known_rooms.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_leave_a_ghost.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_load.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_lobby.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_mcp.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_mcp_session.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_notify.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_rendezvous.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_rooms.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_spec.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_store.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_surface_parity.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_testing.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_tier2_harness.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_timing.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_timing_experiment.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_transient_failures.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_version.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_wake_listener.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_web_invite.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_web_page.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_web_reader.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_web_snapshot.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_whisper.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_workflow_token.py +0 -0
- {agent_switchboard-2.2.2 → agent_switchboard-2.3.0}/tests/test_write_keys.py +0 -0
|
@@ -41,7 +41,7 @@ issue — read off a line in the README rather than reported by anyone.
|
|
|
41
41
|
|
|
42
42
|
### 🟠 What protects a room once the hub stops authorizing anyone
|
|
43
43
|
|
|
44
|
-
`hub-boundary` ·
|
|
44
|
+
`hub-boundary` · 10 item(s), 7 startable
|
|
45
45
|
|
|
46
46
|
`https://github.com/gald33/switchboard/issues/61` · `https://github.com/gald33/switchboard/issues/73` · `docs/model.md` · `docs/encryption.md`
|
|
47
47
|
|
|
@@ -67,6 +67,7 @@ under pressure.
|
|
|
67
67
|
|
|
68
68
|
| item | status | priority |
|
|
69
69
|
|---|---|---|
|
|
70
|
+
| `a-drain-that-retries-forever-never-escalates` | ready | now |
|
|
70
71
|
| `ci-workspace-is-public` | ready | now |
|
|
71
72
|
| `stale-resolver-references` | ready | now |
|
|
72
73
|
| `seal-agent-meta` | ready | next |
|
|
@@ -79,7 +80,7 @@ under pressure.
|
|
|
79
80
|
|
|
80
81
|
### 🟠 The first ten minutes, for somebody who has not read the docs
|
|
81
82
|
|
|
82
|
-
`setup-and-first-run` ·
|
|
83
|
+
`setup-and-first-run` · 22 item(s), 16 startable
|
|
83
84
|
|
|
84
85
|
`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
86
|
|
|
@@ -101,9 +102,12 @@ wrong says so.
|
|
|
101
102
|
|
|
102
103
|
| item | status | priority |
|
|
103
104
|
|---|---|---|
|
|
105
|
+
| `first-contact-needs-a-key-it-cannot-have` | ready | now |
|
|
106
|
+
| `inbox-consumes-what-it-cannot-open` | ready | now |
|
|
104
107
|
| `automatic-session-checkpoints` | ready | next |
|
|
105
108
|
| `connect-failure-message` | done | next |
|
|
106
109
|
| `cross-key-rendezvous` | ready | next |
|
|
110
|
+
| `discovery-is-uneven-and-delivery-is-unknowable` | ready | next |
|
|
107
111
|
| `init-writes-rooms-file` | ready | next |
|
|
108
112
|
| `known-rooms-address-book` | done | next |
|
|
109
113
|
| `a-lobby-derived-from-the-key` | ready | — |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agent-switchboard
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -81,6 +81,29 @@ live exactly as long as the work does.
|
|
|
81
81
|
That's the whole model. Direct messages aren't a fifth concept — a DM to agent
|
|
82
82
|
`bob` is just a message on channel `@bob`.
|
|
83
83
|
|
|
84
|
+
### A message is half an exchange
|
|
85
|
+
|
|
86
|
+
Agents are turn-based. They run, they end, and something else starts them
|
|
87
|
+
later. So an agent that sends a message is almost never launching a one-off —
|
|
88
|
+
it sends because it needs something back, and the answer is due after its turn
|
|
89
|
+
is over, into an inbox nothing is watching.
|
|
90
|
+
|
|
91
|
+
`switchboard listen` is the other half — not a fifth primitive, but the way an
|
|
92
|
+
agent stays reachable across the gap it is not awake for. It parks on the
|
|
93
|
+
inbox and **exits the moment something arrives**, which is the one event a
|
|
94
|
+
runner already reacts to: Claude Code re-invokes a session when a background
|
|
95
|
+
process exits, so the reply *is* the wake — seconds after it lands, rather than
|
|
96
|
+
at whatever check somebody scheduled.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
switchboard dm reviewer-agent "0142 is mine — does that clash with yours?"
|
|
100
|
+
switchboard listen --until forecast:p50 # as a background process, before the turn ends
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`say`, `dm` and `whisper` report where each end stands — whether a listener is
|
|
104
|
+
parked for you, and for the agent you sent to — so "sent" is never mistaken for
|
|
105
|
+
"will be answered into something that is listening".
|
|
106
|
+
|
|
84
107
|
### Why leases expire
|
|
85
108
|
|
|
86
109
|
This is the part worth dwelling on. A conventional "claim" — a row in a table,
|
|
@@ -306,12 +329,20 @@ Agents then get these as native tools:
|
|
|
306
329
|
| `say` / `dm` / `inbox` / `history` | channel and direct messaging |
|
|
307
330
|
| `board_set` / `board_get` / `board_list` | shared scratch space |
|
|
308
331
|
| `checkin` | heartbeat + renew leases + drain inbox, in one call |
|
|
332
|
+
| *(CLI only)* `switchboard listen` | park until a reply arrives, then exit so the runner wakes the session |
|
|
309
333
|
| `session_handoff` / `session_import` / `session_resume` | move a whole Claude Code session to another environment |
|
|
310
334
|
|
|
311
335
|
`checkin` is the one that matters most in practice: a single tool call that
|
|
312
336
|
keeps the agent alive, renews everything it holds, and hands back anything
|
|
313
337
|
other agents said since last time.
|
|
314
338
|
|
|
339
|
+
The listener has no MCP tool on purpose: parking is a *process* that outlives
|
|
340
|
+
the tool call, so it is the CLI run in the background — with the mechanism the
|
|
341
|
+
runner itself tracks, not `&` or `nohup`, which park and wake nobody. `say`,
|
|
342
|
+
`dm` and `whisper` return a `listener` field saying whether one is parked for
|
|
343
|
+
each end of the message, so an agent finds out before its turn ends rather than
|
|
344
|
+
after.
|
|
345
|
+
|
|
315
346
|
See [`docs/claude-code.md`](docs/claude-code.md) for the full setup including a
|
|
316
347
|
`SessionStart` hook that registers the agent automatically and a `Stop` hook
|
|
317
348
|
that releases its leases.
|
|
@@ -43,6 +43,29 @@ live exactly as long as the work does.
|
|
|
43
43
|
That's the whole model. Direct messages aren't a fifth concept — a DM to agent
|
|
44
44
|
`bob` is just a message on channel `@bob`.
|
|
45
45
|
|
|
46
|
+
### A message is half an exchange
|
|
47
|
+
|
|
48
|
+
Agents are turn-based. They run, they end, and something else starts them
|
|
49
|
+
later. So an agent that sends a message is almost never launching a one-off —
|
|
50
|
+
it sends because it needs something back, and the answer is due after its turn
|
|
51
|
+
is over, into an inbox nothing is watching.
|
|
52
|
+
|
|
53
|
+
`switchboard listen` is the other half — not a fifth primitive, but the way an
|
|
54
|
+
agent stays reachable across the gap it is not awake for. It parks on the
|
|
55
|
+
inbox and **exits the moment something arrives**, which is the one event a
|
|
56
|
+
runner already reacts to: Claude Code re-invokes a session when a background
|
|
57
|
+
process exits, so the reply *is* the wake — seconds after it lands, rather than
|
|
58
|
+
at whatever check somebody scheduled.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
switchboard dm reviewer-agent "0142 is mine — does that clash with yours?"
|
|
62
|
+
switchboard listen --until forecast:p50 # as a background process, before the turn ends
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`say`, `dm` and `whisper` report where each end stands — whether a listener is
|
|
66
|
+
parked for you, and for the agent you sent to — so "sent" is never mistaken for
|
|
67
|
+
"will be answered into something that is listening".
|
|
68
|
+
|
|
46
69
|
### Why leases expire
|
|
47
70
|
|
|
48
71
|
This is the part worth dwelling on. A conventional "claim" — a row in a table,
|
|
@@ -268,12 +291,20 @@ Agents then get these as native tools:
|
|
|
268
291
|
| `say` / `dm` / `inbox` / `history` | channel and direct messaging |
|
|
269
292
|
| `board_set` / `board_get` / `board_list` | shared scratch space |
|
|
270
293
|
| `checkin` | heartbeat + renew leases + drain inbox, in one call |
|
|
294
|
+
| *(CLI only)* `switchboard listen` | park until a reply arrives, then exit so the runner wakes the session |
|
|
271
295
|
| `session_handoff` / `session_import` / `session_resume` | move a whole Claude Code session to another environment |
|
|
272
296
|
|
|
273
297
|
`checkin` is the one that matters most in practice: a single tool call that
|
|
274
298
|
keeps the agent alive, renews everything it holds, and hands back anything
|
|
275
299
|
other agents said since last time.
|
|
276
300
|
|
|
301
|
+
The listener has no MCP tool on purpose: parking is a *process* that outlives
|
|
302
|
+
the tool call, so it is the CLI run in the background — with the mechanism the
|
|
303
|
+
runner itself tracks, not `&` or `nohup`, which park and wake nobody. `say`,
|
|
304
|
+
`dm` and `whisper` return a `listener` field saying whether one is parked for
|
|
305
|
+
each end of the message, so an agent finds out before its turn ends rather than
|
|
306
|
+
after.
|
|
307
|
+
|
|
277
308
|
See [`docs/claude-code.md`](docs/claude-code.md) for the full setup including a
|
|
278
309
|
`SessionStart` hook that registers the agent automatically and a `Stop` hook
|
|
279
310
|
that releases its leases.
|
|
@@ -151,22 +151,28 @@ cloud sessions, and in CI. Switchboard is how you coordinate with them.
|
|
|
151
151
|
which is worth interrupting for in a way general channel traffic is not.
|
|
152
152
|
On this CLI it is a line after `say` and `whisper` (and a field under
|
|
153
153
|
`--json`), printed only when something is actually waiting.
|
|
154
|
-
- **
|
|
154
|
+
- **Before ending a turn in which you sent anything you need an answer to,
|
|
155
|
+
arm the listener.** A message here is the opening of a conversation, not a
|
|
156
|
+
one-off: you send because you need something back, and the answer arrives on
|
|
157
|
+
the other agent's schedule — after this turn has ended, into an inbox no
|
|
158
|
+
process is watching. Run `switchboard listen --until forecast:p50` as a
|
|
159
|
+
background process. It parks on your inbox and exits when something arrives,
|
|
160
|
+
and a runner that re-invokes a session when a background process exits —
|
|
161
|
+
Claude Code does — wakes you seconds after the message lands rather than at
|
|
162
|
+
the next scheduled check. `say`, `dm` and `whisper` tell you where you
|
|
163
|
+
stand: a `listener` field on the MCP results and a line on the CLI, saying
|
|
164
|
+
whether one is parked for you and for them. `--until` is when to give up and
|
|
165
|
+
come back empty; without one it parks indefinitely, which is a promise to be
|
|
166
|
+
reachable that nothing keeps. It peeks rather than drains, so still call
|
|
167
|
+
`inbox` yourself when you wake, and it exits on the first message, so arm it
|
|
168
|
+
again if you are still waiting. It takes the flags every command takes, so
|
|
169
|
+
`-w` or `--invite` parks it in another room for cross-repo work.
|
|
170
|
+
- **If you cannot park one** — no background mechanism your runner tracks, or
|
|
171
|
+
a wait that outlives this session — read
|
|
155
172
|
`.claude/skills/switchboard-coordinate/SKILL.md` for how to schedule a
|
|
156
|
-
check-in instead of leaving the wait unbounded
|
|
173
|
+
check-in instead of leaving the wait unbounded. `unread_dms` only helps
|
|
157
174
|
while you are still making tool calls, and nothing else will interrupt an
|
|
158
175
|
idle session.
|
|
159
|
-
- **If the thing you are waiting for is a message**, arm the listener before
|
|
160
|
-
the turn ends: run `switchboard listen --until forecast:p50` as a background
|
|
161
|
-
process. It parks on your inbox and exits when something arrives, and a
|
|
162
|
-
runner that re-invokes a session when a background process exits — Claude
|
|
163
|
-
Code does — wakes you seconds after the message lands rather than at the
|
|
164
|
-
next scheduled check. `--until` is when to give up and come back empty;
|
|
165
|
-
without one it parks indefinitely, which is a promise to be reachable that
|
|
166
|
-
nothing keeps. It peeks rather than drains, so still call `inbox` yourself
|
|
167
|
-
when you wake, and it exits on the first message, so arm it again if you are
|
|
168
|
-
still waiting. It takes the flags every command takes, so `-w` or `--invite`
|
|
169
|
-
parks it in another room for cross-repo work.
|
|
170
176
|
- **Optionally, when a message precedes a stretch of heads-down work**, pass
|
|
171
177
|
`execution_class` (a short label like "coding") and `effort`
|
|
172
178
|
(`low`/`medium`/`high`) to `say`/`dm`/`checkin`/`inbox`. Your runtime turns
|
|
@@ -249,12 +255,19 @@ guarded hook degrades to a no-op.
|
|
|
249
255
|
The `Stop` hook is a convenience, not a requirement — leases expire on their
|
|
250
256
|
own. Releasing eagerly just frees the resource sooner.
|
|
251
257
|
|
|
252
|
-
## 6.
|
|
258
|
+
## 6. Be woken by a message
|
|
253
259
|
|
|
254
260
|
Everything above assumes the session is awake. A message that lands after the
|
|
255
261
|
turn ends is delivered to nobody: the session only reaches for its inbox when
|
|
256
262
|
it is its turn, and nothing about a hub can make a stopped session run.
|
|
257
263
|
|
|
264
|
+
That is not an edge case, which is why this section is not optional. An agent
|
|
265
|
+
sends because it needs something back — a review, a verdict, a lease it was
|
|
266
|
+
told to wait for — so most turns that send end mid-wait, and the answer is due
|
|
267
|
+
after the turn is over. The listener is the piece that makes the second half of
|
|
268
|
+
the exchange reachable. `say`, `dm` and `whisper` say where you stand on every
|
|
269
|
+
send: whether a listener is parked for you, and for the agent you sent to.
|
|
270
|
+
|
|
258
271
|
What *can* is the runner. Claude Code re-invokes a session when a background
|
|
259
272
|
process exits, so a process that parks on the inbox and exits when something
|
|
260
273
|
arrives turns a message into a wake. `init` installs one beside the hooks in
|
|
@@ -454,18 +467,87 @@ under exactly one key, because `claude --resume` refuses to choose between
|
|
|
454
467
|
duplicates. Other direct messages the read consumed come back as `other`
|
|
455
468
|
rather than being lost.
|
|
456
469
|
|
|
457
|
-
Local → cloud → local is
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
470
|
+
Local → cloud → local is three commands and no hub change: `session handoff`
|
|
471
|
+
on the laptop, `session receive` in the cloud session followed by `claude
|
|
472
|
+
--resume`, and the same pair back when the cloud side is done. The hub saw
|
|
473
|
+
two sealed values it cannot name — the pointer's discriminator travels inside
|
|
474
|
+
the sealed body and the message type stays `note`, so not even the row type
|
|
475
|
+
says what moved.
|
|
476
|
+
|
|
477
|
+
**One account, though — see below.** Cloud → local has been run end to end.
|
|
478
|
+
The other direction was run and *failed*, for a reason that is not
|
|
479
|
+
Switchboard's and not fixable here.
|
|
480
|
+
|
|
481
|
+
### A session belongs to the account that made it
|
|
482
|
+
|
|
483
|
+
A transcript can carry a `bridge-session` record naming the account that
|
|
484
|
+
owned it. Restoring one under a different account installs and resumes
|
|
485
|
+
normally and then comes back **empty**: Claude Code loads the file, sees the
|
|
486
|
+
mismatch, and appends
|
|
487
|
+
|
|
488
|
+
```json
|
|
489
|
+
{"type": "history-suppression", "cause": "restored_owner_mismatch", "vetoedAgainstAccountUuid": "…"}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
instead of replaying the conversation. Observed moving a bridged laptop
|
|
493
|
+
session into a cloud container: 14.7 MB and 36 files arrived with a matching
|
|
494
|
+
sha256, `claude --resume` bound the right id, and the conversation was not
|
|
495
|
+
there.
|
|
496
|
+
|
|
497
|
+
Nothing in the capsule causes this and nothing in it can prevent it — the
|
|
498
|
+
bytes are carried and verified faithfully, and the refusal happens after
|
|
499
|
+
Claude Code has already read them. `session resume` detects the record and
|
|
500
|
+
says so rather than blaming the transport; re-running does not help, because
|
|
501
|
+
the veto is a property of the transcript and the account.
|
|
502
|
+
|
|
503
|
+
**Which sessions carry the marker (observed).** Not most of them. Of six
|
|
504
|
+
transcripts compared on one machine — a plain `claude -p` session, a cloud
|
|
505
|
+
session, three sessions Claude Code started itself, and the bridged one — only
|
|
506
|
+
the bridged session had a `bridge-session` record at all. The cloud session
|
|
507
|
+
did not, and it is a remote session with its own `cse_…` id, so this is not
|
|
508
|
+
local versus cloud: the marker rides on the *bridge*, and it names the account
|
|
509
|
+
that owns the session on the other end of it.
|
|
510
|
+
|
|
511
|
+
So the boundary a handoff cannot cross is an *account*, and only a session
|
|
512
|
+
bridged under one carries the evidence. An ordinary session moves between your
|
|
513
|
+
own environments as the matrix says. A bridged one moves its bytes and not its
|
|
514
|
+
conversation unless the destination is signed in as the same account —
|
|
515
|
+
including where "the same person" is signed into two accounts, which is what
|
|
516
|
+
produced the observation above.
|
|
463
517
|
|
|
464
518
|
`switchboard session export -o FILE` and `switchboard session import FILE`
|
|
465
519
|
are the two halves with no hub between them: a capsule file, created `0600`
|
|
466
520
|
because it is as private as the transcript it carries, that you move however
|
|
467
521
|
you like.
|
|
468
522
|
|
|
523
|
+
### Checkpointing, for the session that does not end politely
|
|
524
|
+
|
|
525
|
+
A handoff saves a session that ends *deliberately*. The first real transfer
|
|
526
|
+
survived only because the cloud agent chose to publish before it finished; a
|
|
527
|
+
container that dies takes its context with it, and an explicit command cannot
|
|
528
|
+
help a session that never got to run one.
|
|
529
|
+
|
|
530
|
+
So `switchboard init` asks, once, whether a session should checkpoint itself
|
|
531
|
+
when it ends. Say yes and the stop hook publishes the transcript as a handoff
|
|
532
|
+
addressed to nobody: sealed to the room, expiring on its own, collectable by
|
|
533
|
+
anyone holding the key — which in practice means your own other machines.
|
|
534
|
+
Publishing happens after the hook releases this agent's leases, so the capsule
|
|
535
|
+
records an agent that holds nothing, which is true once the session is over,
|
|
536
|
+
and it ends in `|| true` because a session finishing must never fail on an
|
|
537
|
+
unreachable hub.
|
|
538
|
+
|
|
539
|
+
It is asked rather than assumed because it is real traffic and real storage: a
|
|
540
|
+
transcript can be megabytes and every session end would publish one. The
|
|
541
|
+
default is no, a non-interactive run leaves it off, and the answer is baked
|
|
542
|
+
into the hook script rather than read at run time, since a hook runs as a plain
|
|
543
|
+
shell and never sees `.mcp.json`'s environment.
|
|
544
|
+
|
|
545
|
+
Collecting stays explicit for now. `switchboard session receive <id>` takes a
|
|
546
|
+
checkpoint by id, and which one a *starting* session should collect
|
|
547
|
+
automatically is an open question — see the `automatic-session-checkpoints`
|
|
548
|
+
roadmap item, which would rather leave it undecided than guess wrong about
|
|
549
|
+
whose conversation lands on your disk.
|
|
550
|
+
|
|
469
551
|
### What a handoff cannot reach, and what to send instead
|
|
470
552
|
|
|
471
553
|
A handoff lands a transcript on a filesystem. That covers every environment
|
|
@@ -73,22 +73,28 @@ sessions, and in CI. Switchboard is how you coordinate with them.
|
|
|
73
73
|
which is worth interrupting for in a way general channel traffic is not.
|
|
74
74
|
On this CLI it is a line after `say` and `whisper` (and a field under
|
|
75
75
|
`--json`), printed only when something is actually waiting.
|
|
76
|
-
- **
|
|
76
|
+
- **Before ending a turn in which you sent anything you need an answer to,
|
|
77
|
+
arm the listener.** A message here is the opening of a conversation, not a
|
|
78
|
+
one-off: you send because you need something back, and the answer arrives on
|
|
79
|
+
the other agent's schedule — after this turn has ended, into an inbox no
|
|
80
|
+
process is watching. Run `switchboard listen --until forecast:p50` as a
|
|
81
|
+
background process. It parks on your inbox and exits when something arrives,
|
|
82
|
+
and a runner that re-invokes a session when a background process exits —
|
|
83
|
+
Claude Code does — wakes you seconds after the message lands rather than at
|
|
84
|
+
the next scheduled check. `say`, `dm` and `whisper` tell you where you
|
|
85
|
+
stand: a `listener` field on the MCP results and a line on the CLI, saying
|
|
86
|
+
whether one is parked for you and for them. `--until` is when to give up and
|
|
87
|
+
come back empty; without one it parks indefinitely, which is a promise to be
|
|
88
|
+
reachable that nothing keeps. It peeks rather than drains, so still call
|
|
89
|
+
`inbox` yourself when you wake, and it exits on the first message, so arm it
|
|
90
|
+
again if you are still waiting. It takes the flags every command takes, so
|
|
91
|
+
`-w` or `--invite` parks it in another room for cross-repo work.
|
|
92
|
+
- **If you cannot park one** — no background mechanism your runner tracks, or
|
|
93
|
+
a wait that outlives this session — read
|
|
77
94
|
`.claude/skills/switchboard-coordinate/SKILL.md` for how to schedule a
|
|
78
|
-
check-in instead of leaving the wait unbounded
|
|
95
|
+
check-in instead of leaving the wait unbounded. `unread_dms` only helps
|
|
79
96
|
while you are still making tool calls, and nothing else will interrupt an
|
|
80
97
|
idle session.
|
|
81
|
-
- **If the thing you are waiting for is a message**, arm the listener before
|
|
82
|
-
the turn ends: run `switchboard listen --until forecast:p50` as a background
|
|
83
|
-
process. It parks on your inbox and exits when something arrives, and a
|
|
84
|
-
runner that re-invokes a session when a background process exits — Claude
|
|
85
|
-
Code does — wakes you seconds after the message lands rather than at the
|
|
86
|
-
next scheduled check. `--until` is when to give up and come back empty;
|
|
87
|
-
without one it parks indefinitely, which is a promise to be reachable that
|
|
88
|
-
nothing keeps. It peeks rather than drains, so still call `inbox` yourself
|
|
89
|
-
when you wake, and it exits on the first message, so arm it again if you are
|
|
90
|
-
still waiting. It takes the flags every command takes, so `-w` or `--invite`
|
|
91
|
-
parks it in another room for cross-repo work.
|
|
92
98
|
- **Optionally, when a message precedes a stretch of heads-down work**, pass
|
|
93
99
|
`execution_class` (a short label like "coding") and `effort`
|
|
94
100
|
(`low`/`medium`/`high`) to `say`/`dm`/`checkin`/`inbox`. Your runtime turns
|
|
@@ -123,6 +123,22 @@ Each message carries a monotonically increasing `seq` alongside its cursor
|
|
|
123
123
|
position, so an agent that wants to defend against acting on the same message
|
|
124
124
|
twice — after a retried call, say — can key on `seq` rather than on content.
|
|
125
125
|
|
|
126
|
+
### Sending is half of it
|
|
127
|
+
|
|
128
|
+
Long-polling covers a wait inside a turn. Most waits are not: an agent sends
|
|
129
|
+
because it needs something back, and the answer is due after the sender's turn
|
|
130
|
+
has ended — into a cursor position nothing is advancing.
|
|
131
|
+
|
|
132
|
+
`switchboard listen` is the half that makes the rest of the exchange reachable.
|
|
133
|
+
It parks on the inbox and exits the moment something arrives, which is the one
|
|
134
|
+
event a runner already reacts to, so the reply itself becomes the wake. It
|
|
135
|
+
peeks rather than drains, so the woken session still reads its `inbox` and the
|
|
136
|
+
cursor stays the session's to advance. While parked it heartbeats a
|
|
137
|
+
`listener/<agent-id>` entry on the blackboard, which is why `say`, `dm` and
|
|
138
|
+
`whisper` can tell each sender whether an answer will reach anybody: parked is
|
|
139
|
+
a live process saying so now, and it expires on its own when that process
|
|
140
|
+
stops. See [claude-code.md](claude-code.md#6-be-woken-by-a-message).
|
|
141
|
+
|
|
126
142
|
## Blackboard
|
|
127
143
|
|
|
128
144
|
A key/value space for things too big to be a message and too transient to be a
|
|
@@ -19,7 +19,7 @@ switchboard serve --port 8787
|
|
|
19
19
|
You should see:
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
switchboard 2.
|
|
22
|
+
switchboard 2.3.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.
|
|
50
|
+
{ "ok": true, "version": "2.3.0", "auth": true }
|
|
51
51
|
|
|
52
52
|
$ switchboard register -c build
|
|
53
53
|
registered local-main-yourhost (local) in demo
|
|
@@ -47,6 +47,24 @@ No transcript was modified. No path translation was needed. The destination
|
|
|
47
47
|
needs neither the repo path nor the repo contents to *resume the conversation*;
|
|
48
48
|
it needs them only for whatever the conversation does next.
|
|
49
49
|
|
|
50
|
+
**One boundary this matrix never crossed (observed later, in production).**
|
|
51
|
+
Every case above moved a session owned by one account — between config dirs,
|
|
52
|
+
between directories, between machines, but never between *accounts*. A
|
|
53
|
+
transcript can carry a `bridge-session` record naming its owner, and restoring
|
|
54
|
+
one under a different account installs, resumes under the right id, and comes
|
|
55
|
+
back empty; Claude Code appends
|
|
56
|
+
`{"type":"history-suppression","cause":"restored_owner_mismatch"}` rather than
|
|
57
|
+
replaying it. So "portable as a single opaque file" holds within an account
|
|
58
|
+
and not across one, and the row above that reads "the real, live 537 KB
|
|
59
|
+
session that ran this investigation" passed because it was *our own*.
|
|
60
|
+
|
|
61
|
+
Comparing six transcripts on one machine afterwards (**observed**): only a
|
|
62
|
+
bridged session carries the record. A plain `claude -p` session, a cloud
|
|
63
|
+
session with its own `cse_…` id, and three sessions Claude Code started itself
|
|
64
|
+
had no owner field anywhere. So the marker is not a property of every session
|
|
65
|
+
and not of cloud sessions — it rides on the bridge, and the account it names
|
|
66
|
+
is the one on the far side.
|
|
67
|
+
|
|
50
68
|
## 1. Where Claude Code stores sessions (observed)
|
|
51
69
|
|
|
52
70
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agent-switchboard"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
description = "An ephemeral orchestration hub for AI coding agents: presence, leases, messaging and a shared blackboard, all with TTLs."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|