agent-switchboard 2.2.1__tar.gz → 2.2.2__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.1 → agent_switchboard-2.2.2}/ARCS.md +2 -1
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/PKG-INFO +1 -1
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/claude-code.md +38 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/quickstart.md +2 -2
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/upgrading.md +12 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/viewer/pyproject.toml +1 -1
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/pyproject.toml +1 -1
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/ROADMAP.md +99 -0
- agent_switchboard-2.2.2/roadmap/items/automatic-session-checkpoints.yaml +92 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/__init__.py +1 -1
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/claude_session.py +103 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/cli.py +80 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_claude_session.py +100 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_dash_ids.py +34 -11
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/.dockerignore +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/.github/workflows/ci.yml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/.github/workflows/deploy.yml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/.github/workflows/pages.yml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/.github/workflows/publish-viewer.yml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/.github/workflows/publish.yml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/.github/workflows/roadmap.yml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/.gitignore +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/AGENTS.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/CONTRIBUTING.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/Dockerfile +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/LICENSE +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/README.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/demo/README.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/demo/chess.sh +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/demo/run.sh +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docker-compose.yml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/adaptive-timing.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/api.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/codex-cli.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/concepts.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/deployment.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/drills.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/encryption.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/environments.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/images/viewer.png +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/layers.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/managed-hub.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/model.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/seam.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/testing.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/viewer.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/docs/why-this-exists.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/examples/coordinated_worker.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/session-capsule/README.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/session-capsule/cc.sh +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/session-capsule/claude_session_capsule.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/viewer/README.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/viewer/switchboard_viewer/__init__.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/viewer/switchboard_viewer/__main__.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/viewer/switchboard_viewer/viewer.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/viewer/switchboard_viewer/web/index.html +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/viewer/switchboard_viewer/web/render.js +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/viewer/switchboard_viewer/web/switchboard-open.js +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/extras/viewer/switchboard_viewer/web/switchboard-room.js +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/README.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/arcs/distribution.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/arcs/hub-boundary.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/arcs/setup-and-first-run.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/arcs/ttl-ceilings.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/a-lobby-derived-from-the-key.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/abuse-control-after-authorization.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/board-ttl-ceiling.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/ci-workspace-is-public.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/clients-that-cannot-post.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/connect-failure-message.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/cross-key-rendezvous.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/every-silent-failure-looks-like-a-quiet-room.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/hooks-warning-false-positive.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/hub-origin-reachable-bypassing-the-edge.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/identity-rebinds-on-branch-change.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/init-writes-rooms-file.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/intermittent-suite-failure.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/joining-agent-sees-empty-inbox.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/known-rooms-address-book.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/one-resolved-context-across-surfaces.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/presence-ttl-is-not-one-size.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/provisioned-token-is-stale-and-nothing-says-so.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/publish-hub-container-image.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/read-only-rooms.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/robots-policy-for-public-hosts.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/roles-and-authority-between-agents.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/seal-agent-meta.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/selective-wake-for-the-listener.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/stale-resolver-references.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/stale-token-in-session-env.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/standing-checks-that-nothing-runs.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/timing-cold-start-in-ephemeral-environments.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/ttl-clamped-silently.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/unread-dms-not-shown-outside-mcp.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/roadmap/items/write-parity-across-surfaces.yaml +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/scripts/deploy.sh +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/auth.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/client.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/config.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/crypto.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/desktop.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/drill.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/guidance.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/handoff.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/holds.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/invite.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/knownrooms.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/load.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/mcp_server.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/notify.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/peers.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/rendezvous.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/rooms.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/scripts/wake-on-message.sh +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/server.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/signing.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/001-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/002-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/003-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/004-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/005-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/006-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/007-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/008-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/009-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/010-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/011-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/012-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/013-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/014-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/015-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/016-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/017-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/018-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/019-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/020-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/021-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/022-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/023-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/024-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/history/025-SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/skill/switchboard-coordinate/SKILL.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/spec.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/store.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/testing.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/timing.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/src/switchboard/writekey.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/conftest.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/README.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/__init__.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/analysis.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/economy.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/flow.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/llm.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/manager.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/report.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/run.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier1.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier1_labour.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier1_rounds.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier2_seed1_bound.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier2_seed1_built.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier2_seed1_free.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier2_seed1_paid.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier2_seed1_spend.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier2_seed1_told.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/tier2_seed41_bound.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter/traders.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter_experiment.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/barter_llm_experiment.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_harness.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/RESULTS.md +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/prompt_A_s31337.txt +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/prompt_A_s4242.txt +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/prompt_A_s7777.txt +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/prompt_B_s31337.txt +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/prompt_B_s4242.txt +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/prompt_B_s7777.txt +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/prompt_C_s31337.txt +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/prompt_C_s4242.txt +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/prompt_C_s7777.txt +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_haiku_A_s31337.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_haiku_A_s4242.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_haiku_A_s7777.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_haiku_B_s31337.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_haiku_B_s4242.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_haiku_B_s7777.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_haiku_C_s31337.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_haiku_C_s4242.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_haiku_C_s7777.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_opus_A_s31337.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_opus_A_s4242.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_opus_A_s7777.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_opus_B_s31337.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_opus_B_s4242.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_opus_B_s7777.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_opus_C_s31337.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_opus_C_s4242.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_opus_C_s7777.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_sonnet_A_s31337.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_sonnet_A_s4242.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_sonnet_A_s7777.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_sonnet_B_s31337.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_sonnet_B_s4242.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_sonnet_B_s7777.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_sonnet_C_s31337.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_sonnet_C_s4242.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_pilot/response_sonnet_C_s7777.json +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/tier2_run.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/experiments/timing_experiment.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_api.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_arrive.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_auth.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_barter.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_barter_llm.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_cli_board.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_cli_checkin.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_cli_config.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_cli_global_flags.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_cli_init.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_cli_serve.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_cli_session.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_cli_timing.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_client_invite.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_config.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_cors_write_headers.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_crypto.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_declare.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_deployment.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_desktop.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_drill.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_evening_replayed.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_example_viewer.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_failure_messages.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_handoff.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_help.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_invite.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_isolated_hub.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_known_rooms.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_leave_a_ghost.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_load.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_lobby.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_mcp.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_mcp_session.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_notify.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_rendezvous.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_rooms.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_spec.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_store.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_surface_parity.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_testing.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_tier2_harness.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_timing.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_timing_experiment.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_transient_failures.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_version.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_wake_listener.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_web_invite.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_web_page.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_web_reader.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_web_snapshot.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_whisper.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/tests/test_workflow_token.py +0 -0
- {agent_switchboard-2.2.1 → agent_switchboard-2.2.2}/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` · 19 item(s), 13 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
|
|
|
@@ -101,6 +101,7 @@ wrong says so.
|
|
|
101
101
|
|
|
102
102
|
| item | status | priority |
|
|
103
103
|
|---|---|---|
|
|
104
|
+
| `automatic-session-checkpoints` | ready | next |
|
|
104
105
|
| `connect-failure-message` | done | next |
|
|
105
106
|
| `cross-key-rendezvous` | ready | next |
|
|
106
107
|
| `init-writes-rooms-file` | ready | next |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agent-switchboard
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
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
|
|
@@ -466,6 +466,44 @@ are the two halves with no hub between them: a capsule file, created `0600`
|
|
|
466
466
|
because it is as private as the transcript it carries, that you move however
|
|
467
467
|
you like.
|
|
468
468
|
|
|
469
|
+
### What a handoff cannot reach, and what to send instead
|
|
470
|
+
|
|
471
|
+
A handoff lands a transcript on a filesystem. That covers every environment
|
|
472
|
+
where a session can run a shell — your laptop, another machine, a cloud
|
|
473
|
+
container — because the receiving side collects the capsule and
|
|
474
|
+
`claude --resume` finds it. The first real handoff went cloud-container to
|
|
475
|
+
laptop that way.
|
|
476
|
+
|
|
477
|
+
It does not reach a session running in Claude's own cloud, and it is worth
|
|
478
|
+
saying why rather than leaving somebody to discover it. Checked against CLI
|
|
479
|
+
2.1.259 on 2026-09-05: `--cloud <session_id>` **attaches** to a cloud session
|
|
480
|
+
that already exists, `--environment ccpool_…` creates a **new** one on a
|
|
481
|
+
self-hosted pool, and neither takes a transcript. The `/move-to-cloud` and
|
|
482
|
+
`/teleport` commands exist in the binary but are registered only for an
|
|
483
|
+
interactive terminal, and the teleport path seeds a fresh session with a
|
|
484
|
+
written summary rather than the conversation. There is no endpoint anywhere
|
|
485
|
+
that accepts a transcript, so a conversation cannot be uploaded to become a
|
|
486
|
+
cloud session, and nothing in this repo pretends otherwise.
|
|
487
|
+
|
|
488
|
+
Two things do work in that direction, and between them they cover most of
|
|
489
|
+
what people want from it.
|
|
490
|
+
|
|
491
|
+
**Consult it.** A cloud container can run `session receive` like anything
|
|
492
|
+
else. The transcript lands on its disk and the session *reads* it — greps for
|
|
493
|
+
the decision it needs, opens the twenty records that matter — instead of
|
|
494
|
+
resuming it. This is not a workaround for the missing upload: a 4.5 MB
|
|
495
|
+
transcript could not be injected into a context window even if an endpoint
|
|
496
|
+
existed, and consulting on demand is the only shape that scales.
|
|
497
|
+
|
|
498
|
+
**Send the shape of it.** `switchboard session brief <id>` prints what a
|
|
499
|
+
session was for, read off the record rather than retold by a model: what the
|
|
500
|
+
human asked, which files were touched, how it worked. On the first real
|
|
501
|
+
handoff that is 960 bytes out of 4.5 MB, and it fits in a message you can
|
|
502
|
+
paste anywhere — including a cloud session in a browser that no capsule can
|
|
503
|
+
reach. The filtering is the substance: Claude Code writes tool results, hook
|
|
504
|
+
output and notifications as `user` records too, so 285 user turns there
|
|
505
|
+
contain 14 things a human actually typed.
|
|
506
|
+
|
|
469
507
|
### If you work in the desktop app
|
|
470
508
|
|
|
471
509
|
Installing the transcript is the whole job for a CLI user and half of one for
|
|
@@ -19,7 +19,7 @@ switchboard serve --port 8787
|
|
|
19
19
|
You should see:
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
switchboard 2.2.
|
|
22
|
+
switchboard 2.2.2 → 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.2.
|
|
50
|
+
{ "ok": true, "version": "2.2.2", "auth": true }
|
|
51
51
|
|
|
52
52
|
$ switchboard register -c build
|
|
53
53
|
registered local-main-yourhost (local) in demo
|
|
@@ -47,6 +47,18 @@ Additive in the same release: `--no-subagents` on `session export`,
|
|
|
47
47
|
subagent transcripts out of a capsule, and the viewer can save an opened
|
|
48
48
|
board value to a file. Neither changes an existing default.
|
|
49
49
|
|
|
50
|
+
## 2.2.1 → 2.2.2
|
|
51
|
+
|
|
52
|
+
Additive. `switchboard session brief <id>` prints what a session was for —
|
|
53
|
+
the human's instructions, the files touched, the tools used — read off the
|
|
54
|
+
transcript rather than retold by a model. It exists for the place a capsule
|
|
55
|
+
cannot reach: a session running in Claude's own cloud, which accepts no
|
|
56
|
+
transcript through any endpoint. On a 4.5 MB transcript the brief is under a
|
|
57
|
+
kilobyte, so it fits in a message you can paste anywhere.
|
|
58
|
+
|
|
59
|
+
`docs/claude-code.md` now states plainly what a handoff can and cannot reach,
|
|
60
|
+
checked against Claude Code 2.1.259. Nothing that worked in 2.2.1 changed.
|
|
61
|
+
|
|
50
62
|
## 2.1.0 → 2.2.0
|
|
51
63
|
|
|
52
64
|
Additive. A whole Claude Code session can be moved between environments
|
|
@@ -19,7 +19,7 @@ build-backend = "hatchling.build"
|
|
|
19
19
|
|
|
20
20
|
[project]
|
|
21
21
|
name = "switchboard-viewer"
|
|
22
|
-
version = "0.
|
|
22
|
+
version = "0.11.0"
|
|
23
23
|
description = "A read-only page showing your Switchboard rooms to a human: who is awake, what is claimed, and the conversation as it happens."
|
|
24
24
|
readme = "README.md"
|
|
25
25
|
requires-python = ">=3.10"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agent-switchboard"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.2"
|
|
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"
|
|
@@ -17,8 +17,12 @@ Claim before starting: `roadmap claim <key>`
|
|
|
17
17
|
- ↔ related: **`init-writes-rooms-file`** — Both decide where a room identifier is allowed to live. This one is about an identifier that should not have been committed; that one proposes that `init` start committing a rooms record carrying a workspace token by default. Settle the rule here first, or `init` ships the same mistake as the default for every adopter.
|
|
18
18
|
- ↔ related: **`read-only-rooms`** — Half of that item — anyone who can read the repo can post as CI — is closed by minting the CI room write-protected: the identifier can stay committed, since knowing it no longer lets anyone write into it. The other half, sealing what CI announces, is unchanged.
|
|
19
19
|
- `now` **`stale-resolver-references`** — Delete the comments describing auth machinery that no longer exists
|
|
20
|
+
- `next` **`automatic-session-checkpoints`** — A session that ends anywhere is collectable everywhere, without anyone running a command
|
|
21
|
+
- ↔ related: **`cross-key-rendezvous`** — Shares the addressing problem from the other end. That one asks how a stranger finds you; this one asks which of several checkpoints is the one you meant. Both resolve to "the tool decides, the agent does not choose", so settle the rule in one place rather than twice.
|
|
22
|
+
- ↔ related: **`selective-wake-for-the-listener`** — Both are about a session being reachable across a gap the agent is not awake for. That one wakes a parked session when a message lands; this one makes sure the session's own context survives the gap at all. Read that one first for the hook and background-process conventions this must follow.
|
|
20
23
|
- `next` **`cross-key-rendezvous`** — Two agents that share no key have no room to meet in, and a human had to carry the coordinates
|
|
21
24
|
- ↔ related: **`a-lobby-derived-from-the-key`** — That one made the lobby unguessable by deriving it from the key, deliberately rejecting a well-known room. This is the cost of that decision, observed: holders of different keys have no lobby in common. Read it first; the fix here keeps its rejection and works around it, rather than reopening it.
|
|
25
|
+
- ↔ related: **`automatic-session-checkpoints`** — Shares the addressing problem from the other end. That one asks how a stranger finds you; this one asks which of several checkpoints is the one you meant. Both resolve to "the tool decides, the agent does not choose", so settle the rule in one place rather than twice.
|
|
22
26
|
- ↔ related: **`known-rooms-address-book`** — That one gives a project a room strangers can find. This is what an agent does with rooms once it knows them: a published meeting room, an invite it was handed, a side room it minted, its repo rooms — one list, swept when searching, chosen from when parking. Do that one first; this consumes what it publishes.
|
|
23
27
|
- ↔ related: **`selective-wake-for-the-listener`** — The other half of the same evening: a turn-based session that cannot park a listener returns on shared slots instead, which worked. Meeting failed one layer up — there was no shared room for the slots to be in.
|
|
24
28
|
- `next` **`init-writes-rooms-file`** — Make init produce the rooms record the model says is authoritative
|
|
@@ -60,6 +64,7 @@ Claim before starting: `roadmap claim <key>`
|
|
|
60
64
|
- ↔ related: **`every-silent-failure-looks-like-a-quiet-room`** — The symptom when this fires is a 401 on the published credential, which two agents spent a day mis-attributing — first to a stale client, then to a perimeter that had moved. Read that item for why a wrong credential is hard to tell from a quiet room.
|
|
61
65
|
- **`selective-wake-for-the-listener`** — Wake the listener on what matters, and on the time it promised, not on every message
|
|
62
66
|
- ↔ related: **`a-lobby-derived-from-the-key`** — What makes a lobby worth having rather than merely tidy: an agent parked with `listen` is visible on a roster, so a lobby is a place peers can actually be found waiting rather than a room that is empty whenever nobody is mid-turn.
|
|
67
|
+
- ↔ related: **`automatic-session-checkpoints`** — Both are about a session being reachable across a gap the agent is not awake for. That one wakes a parked session when a message lands; this one makes sure the session's own context survives the gap at all. Read that one first for the hook and background-process conventions this must follow.
|
|
63
68
|
- ↔ related: **`cross-key-rendezvous`** — The other half of the same evening: a turn-based session that cannot park a listener returns on shared slots instead, which worked. Meeting failed one layer up — there was no shared room for the slots to be in.
|
|
64
69
|
- ↔ related: **`every-silent-failure-looks-like-a-quiet-room`** — Cause 8 below is a defect in what that item builds on — a parked listener watches the inbox of the id it started under, and a `git checkout` re-derives that id without telling either side. The fix belongs in `listen`, not here.
|
|
65
70
|
- ↔ related: **`known-rooms-address-book`** — Same primitive, other axis. That one is about which *messages* wake a parked listener; this is about which *rooms* it is parked in, and why it need not be all of them.
|
|
@@ -110,6 +115,7 @@ _Nothing blocked._
|
|
|
110
115
|
graph TD
|
|
111
116
|
a_lobby_derived_from_the_key["Give every key a lobby, so agents that share one can find each other without naming a room"]
|
|
112
117
|
abuse_control_after_authorization["Replace the abuse control that per-token authorization used to provide"]
|
|
118
|
+
automatic_session_checkpoints["A session that ends anywhere is collectable everywhere, without anyone running a command"]
|
|
113
119
|
board_ttl_ceiling["Decide whether a board value has earned seven times a lease's lifetime"]
|
|
114
120
|
ci_workspace_is_public["Stop publishing the one room identifier that was never meant to be guessable"]
|
|
115
121
|
clients_that_cannot_post["Decide what a client that cannot hold a secret or issue arbitrary HTTP gets"]
|
|
@@ -144,6 +150,8 @@ graph TD
|
|
|
144
150
|
a_lobby_derived_from_the_key -.- one_resolved_context_across_surfaces
|
|
145
151
|
a_lobby_derived_from_the_key -.- selective_wake_for_the_listener
|
|
146
152
|
abuse_control_after_authorization -.- ci_workspace_is_public
|
|
153
|
+
automatic_session_checkpoints -.- cross_key_rendezvous
|
|
154
|
+
automatic_session_checkpoints -.- selective_wake_for_the_listener
|
|
147
155
|
board_ttl_ceiling -.- ttl_clamped_silently
|
|
148
156
|
ci_workspace_is_public -.- init_writes_rooms_file
|
|
149
157
|
ci_workspace_is_public -.- read_only_rooms
|
|
@@ -282,6 +290,95 @@ graph TD
|
|
|
282
290
|
|
|
283
291
|
</details>
|
|
284
292
|
|
|
293
|
+
### `automatic-session-checkpoints`
|
|
294
|
+
|
|
295
|
+
- **title:** A session that ends anywhere is collectable everywhere, without anyone running a command
|
|
296
|
+
- **status:** ready
|
|
297
|
+
- **arc:** setup-and-first-run
|
|
298
|
+
- **priority:** next
|
|
299
|
+
- **related to** (not a dependency — both are startable):
|
|
300
|
+
- `cross-key-rendezvous` — Shares the addressing problem from the other end. That one asks how a stranger finds you; this one asks which of several checkpoints is the one you meant. Both resolve to "the tool decides, the agent does not choose", so settle the rule in one place rather than twice.
|
|
301
|
+
- `selective-wake-for-the-listener` — Both are about a session being reachable across a gap the agent is not awake for. That one wakes a parked session when a message lands; this one makes sure the session's own context survives the gap at all. Read that one first for the hook and background-process conventions this must follow.
|
|
302
|
+
- **refs:**
|
|
303
|
+
- `src/switchboard/handoff.py`
|
|
304
|
+
- `src/switchboard/claude_session.py`
|
|
305
|
+
- `docs/claude-code.md`
|
|
306
|
+
- `https://github.com/gald33/switchboard/pull/229`
|
|
307
|
+
|
|
308
|
+
<details><summary>evidence</summary>
|
|
309
|
+
|
|
310
|
+
> **Inferred from a design conversation on 2026-09-05, after the first real
|
|
311
|
+
> handoff the night before.** Nothing is broken; the mechanism works. What this
|
|
312
|
+
> item records is what the working mechanism still asks of a human, and one
|
|
313
|
+
> fragility that only shows up when something goes wrong.
|
|
314
|
+
>
|
|
315
|
+
> **The question that started it: how does Cursor move sessions between cloud
|
|
316
|
+
> and local so smoothly, and can we?** Reading Cursor's own store on a machine
|
|
317
|
+
> that runs it answers the first half. Its `conversations` table tags each row
|
|
318
|
+
> with a `source` — 496 `local` against 47 `cloud-cache` on the machine
|
|
319
|
+
> inspected — the cloud ones carry a server-side `scope` hash the local ones
|
|
320
|
+
> lack, the table has `root_fingerprint` and `cache_fingerprint` columns, and a
|
|
321
|
+
> `conversation_search_reconciliation` table holds a sync `cursor`. That is a
|
|
322
|
+
> client cache over a server-authoritative store. Cursor does not move a
|
|
323
|
+
> session between cloud and local; both ends are clients of one record its
|
|
324
|
+
> servers hold, so there is nothing to move.
|
|
325
|
+
>
|
|
326
|
+
> **That is exactly the trade Switchboard must not make.** Copying it means the
|
|
327
|
+
> hub storing transcripts it currently cannot read, which is the single
|
|
328
|
+
> property the whole design exists to protect (`docs/model.md`). The local-first
|
|
329
|
+
> choice is what let a session cross from a cloud container to a laptop on
|
|
330
|
+
> 2026-09-04 between two agents sharing no account, sealed with a key the hub
|
|
331
|
+
> never saw. A cache-and-sync design cannot do that, because the server has to
|
|
332
|
+
> hold the conversation in order to synchronise it.
|
|
333
|
+
>
|
|
334
|
+
> **So the achievable version of "seamless" is not shared storage — it is that
|
|
335
|
+
> nobody types a command.** A Cursor user does not experience syncing; they
|
|
336
|
+
> experience "it was there". That experience needs the handoff to happen at the
|
|
337
|
+
> boundaries automatically, and every part is already built:
|
|
338
|
+
>
|
|
339
|
+
> - `session publish` with no recipient is already a **checkpoint** — the
|
|
340
|
+
> capsule goes up sealed, addressed to nobody, collectable by anyone holding
|
|
341
|
+
> the room's key, which in practice is your own other machines
|
|
342
|
+
> (`handoff.py`).
|
|
343
|
+
> - `init` already installs `SessionStart` and `Stop` hooks
|
|
344
|
+
> (`cli.py`, `_session_start_cmd` / `_stop_cmd`).
|
|
345
|
+
> - `session receive` already takes `--wait`.
|
|
346
|
+
>
|
|
347
|
+
> Wiring them is the work: checkpoint on `Stop` and on going idle, collect on
|
|
348
|
+
> `SessionStart`.
|
|
349
|
+
>
|
|
350
|
+
> **And it closes a fragility, which is the stronger half of the case.** A
|
|
351
|
+
> handoff is explicit today, so a session that dies unexpectedly leaves nothing
|
|
352
|
+
> behind — the 2026-09-04 transfer survived only because the cloud agent chose
|
|
353
|
+
> to publish before it finished. Cursor survives that case because the server
|
|
354
|
+
> already holds the conversation. Periodic self-checkpointing is how a
|
|
355
|
+
> local-first design gets the same durability without giving anything away: the
|
|
356
|
+
> last checkpoint is always collectable and expires on its own, so nothing
|
|
357
|
+
> accumulates on the hub.
|
|
358
|
+
>
|
|
359
|
+
> **The open question, and the reason this is an item rather than a patch.**
|
|
360
|
+
> Addressing. When several sessions have checkpointed, a starting session must
|
|
361
|
+
> not collect all of them, and "the newest" is not obviously right either.
|
|
362
|
+
> `--from` and sender verification are the raw material; the rule is not
|
|
363
|
+
> decided. Most likely: the most recent checkpoint for **this repo** from **this
|
|
364
|
+
> account**, with everything else needing to be asked for by id. Settle it
|
|
365
|
+
> together with `cross-key-rendezvous`, which has the same shape of question.
|
|
366
|
+
>
|
|
367
|
+
> **What this must not promise, and the docs should say so rather than let
|
|
368
|
+
> somebody find out.** Two live sessions cannot edit one conversation: without
|
|
369
|
+
> an authority to reconcile against, a handoff is a baton and not a mirror.
|
|
370
|
+
> Continuity is only as good as the checkpoint interval. And a transcript is
|
|
371
|
+
> large — the first real one was 19.8 MB across 175 files — so checkpointing
|
|
372
|
+
> every turn is not free, which argues for idle and end-of-session rather than
|
|
373
|
+
> continuous.
|
|
374
|
+
>
|
|
375
|
+
> **Done when** a session that ends on one machine is collectable on another
|
|
376
|
+
> with nobody running `handoff` or `receive`, when a session killed without
|
|
377
|
+
> warning still leaves a collectable checkpoint, and when the addressing rule is
|
|
378
|
+
> written down and tested rather than left to whoever calls `receive` first.
|
|
379
|
+
|
|
380
|
+
</details>
|
|
381
|
+
|
|
285
382
|
### `board-ttl-ceiling`
|
|
286
383
|
|
|
287
384
|
- **title:** Decide whether a board value has earned seven times a lease's lifetime
|
|
@@ -484,6 +581,7 @@ graph TD
|
|
|
484
581
|
- **priority:** next
|
|
485
582
|
- **related to** (not a dependency — both are startable):
|
|
486
583
|
- `a-lobby-derived-from-the-key` — That one made the lobby unguessable by deriving it from the key, deliberately rejecting a well-known room. This is the cost of that decision, observed: holders of different keys have no lobby in common. Read it first; the fix here keeps its rejection and works around it, rather than reopening it.
|
|
584
|
+
- `automatic-session-checkpoints` — Shares the addressing problem from the other end. That one asks how a stranger finds you; this one asks which of several checkpoints is the one you meant. Both resolve to "the tool decides, the agent does not choose", so settle the rule in one place rather than twice.
|
|
487
585
|
- `known-rooms-address-book` — That one gives a project a room strangers can find. This is what an agent does with rooms once it knows them: a published meeting room, an invite it was handed, a side room it minted, its repo rooms — one list, swept when searching, chosen from when parking. Do that one first; this consumes what it publishes.
|
|
488
586
|
- `selective-wake-for-the-listener` — The other half of the same evening: a turn-based session that cannot park a listener returns on shared slots instead, which worked. Meeting failed one layer up — there was no shared room for the slots to be in.
|
|
489
587
|
- **refs:**
|
|
@@ -1744,6 +1842,7 @@ graph TD
|
|
|
1744
1842
|
- **arc:** setup-and-first-run
|
|
1745
1843
|
- **related to** (not a dependency — both are startable):
|
|
1746
1844
|
- `a-lobby-derived-from-the-key` — What makes a lobby worth having rather than merely tidy: an agent parked with `listen` is visible on a roster, so a lobby is a place peers can actually be found waiting rather than a room that is empty whenever nobody is mid-turn.
|
|
1845
|
+
- `automatic-session-checkpoints` — Both are about a session being reachable across a gap the agent is not awake for. That one wakes a parked session when a message lands; this one makes sure the session's own context survives the gap at all. Read that one first for the hook and background-process conventions this must follow.
|
|
1747
1846
|
- `cross-key-rendezvous` — The other half of the same evening: a turn-based session that cannot park a listener returns on shared slots instead, which worked. Meeting failed one layer up — there was no shared room for the slots to be in.
|
|
1748
1847
|
- `every-silent-failure-looks-like-a-quiet-room` — Cause 8 below is a defect in what that item builds on — a parked listener watches the inbox of the id it started under, and a `git checkout` re-derives that id without telling either side. The fix belongs in `listen`, not here.
|
|
1749
1848
|
- `known-rooms-address-book` — Same primitive, other axis. That one is about which *messages* wake a parked listener; this is about which *rooms* it is parked in, and why it need not be all of them.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
id: automatic-session-checkpoints
|
|
2
|
+
title: A session that ends anywhere is collectable everywhere, without anyone running a command
|
|
3
|
+
status: ready
|
|
4
|
+
arc: setup-and-first-run
|
|
5
|
+
priority: next
|
|
6
|
+
blocked_on: []
|
|
7
|
+
related_to:
|
|
8
|
+
- key: selective-wake-for-the-listener
|
|
9
|
+
note: 'Both are about a session being reachable across a gap the agent is not awake
|
|
10
|
+
for. That one wakes a parked session when a message lands; this one makes sure the
|
|
11
|
+
session''s own context survives the gap at all. Read that one first for the hook
|
|
12
|
+
and background-process conventions this must follow.'
|
|
13
|
+
- key: cross-key-rendezvous
|
|
14
|
+
note: 'Shares the addressing problem from the other end. That one asks how a stranger
|
|
15
|
+
finds you; this one asks which of several checkpoints is the one you meant. Both
|
|
16
|
+
resolve to "the tool decides, the agent does not choose", so settle the rule in
|
|
17
|
+
one place rather than twice.'
|
|
18
|
+
refs:
|
|
19
|
+
- 'src/switchboard/handoff.py'
|
|
20
|
+
- 'src/switchboard/claude_session.py'
|
|
21
|
+
- 'docs/claude-code.md'
|
|
22
|
+
- 'https://github.com/gald33/switchboard/pull/229'
|
|
23
|
+
evidence: |
|
|
24
|
+
**Inferred from a design conversation on 2026-09-05, after the first real
|
|
25
|
+
handoff the night before.** Nothing is broken; the mechanism works. What this
|
|
26
|
+
item records is what the working mechanism still asks of a human, and one
|
|
27
|
+
fragility that only shows up when something goes wrong.
|
|
28
|
+
|
|
29
|
+
**The question that started it: how does Cursor move sessions between cloud
|
|
30
|
+
and local so smoothly, and can we?** Reading Cursor's own store on a machine
|
|
31
|
+
that runs it answers the first half. Its `conversations` table tags each row
|
|
32
|
+
with a `source` — 496 `local` against 47 `cloud-cache` on the machine
|
|
33
|
+
inspected — the cloud ones carry a server-side `scope` hash the local ones
|
|
34
|
+
lack, the table has `root_fingerprint` and `cache_fingerprint` columns, and a
|
|
35
|
+
`conversation_search_reconciliation` table holds a sync `cursor`. That is a
|
|
36
|
+
client cache over a server-authoritative store. Cursor does not move a
|
|
37
|
+
session between cloud and local; both ends are clients of one record its
|
|
38
|
+
servers hold, so there is nothing to move.
|
|
39
|
+
|
|
40
|
+
**That is exactly the trade Switchboard must not make.** Copying it means the
|
|
41
|
+
hub storing transcripts it currently cannot read, which is the single
|
|
42
|
+
property the whole design exists to protect (`docs/model.md`). The local-first
|
|
43
|
+
choice is what let a session cross from a cloud container to a laptop on
|
|
44
|
+
2026-09-04 between two agents sharing no account, sealed with a key the hub
|
|
45
|
+
never saw. A cache-and-sync design cannot do that, because the server has to
|
|
46
|
+
hold the conversation in order to synchronise it.
|
|
47
|
+
|
|
48
|
+
**So the achievable version of "seamless" is not shared storage — it is that
|
|
49
|
+
nobody types a command.** A Cursor user does not experience syncing; they
|
|
50
|
+
experience "it was there". That experience needs the handoff to happen at the
|
|
51
|
+
boundaries automatically, and every part is already built:
|
|
52
|
+
|
|
53
|
+
- `session publish` with no recipient is already a **checkpoint** — the
|
|
54
|
+
capsule goes up sealed, addressed to nobody, collectable by anyone holding
|
|
55
|
+
the room's key, which in practice is your own other machines
|
|
56
|
+
(`handoff.py`).
|
|
57
|
+
- `init` already installs `SessionStart` and `Stop` hooks
|
|
58
|
+
(`cli.py`, `_session_start_cmd` / `_stop_cmd`).
|
|
59
|
+
- `session receive` already takes `--wait`.
|
|
60
|
+
|
|
61
|
+
Wiring them is the work: checkpoint on `Stop` and on going idle, collect on
|
|
62
|
+
`SessionStart`.
|
|
63
|
+
|
|
64
|
+
**And it closes a fragility, which is the stronger half of the case.** A
|
|
65
|
+
handoff is explicit today, so a session that dies unexpectedly leaves nothing
|
|
66
|
+
behind — the 2026-09-04 transfer survived only because the cloud agent chose
|
|
67
|
+
to publish before it finished. Cursor survives that case because the server
|
|
68
|
+
already holds the conversation. Periodic self-checkpointing is how a
|
|
69
|
+
local-first design gets the same durability without giving anything away: the
|
|
70
|
+
last checkpoint is always collectable and expires on its own, so nothing
|
|
71
|
+
accumulates on the hub.
|
|
72
|
+
|
|
73
|
+
**The open question, and the reason this is an item rather than a patch.**
|
|
74
|
+
Addressing. When several sessions have checkpointed, a starting session must
|
|
75
|
+
not collect all of them, and "the newest" is not obviously right either.
|
|
76
|
+
`--from` and sender verification are the raw material; the rule is not
|
|
77
|
+
decided. Most likely: the most recent checkpoint for **this repo** from **this
|
|
78
|
+
account**, with everything else needing to be asked for by id. Settle it
|
|
79
|
+
together with `cross-key-rendezvous`, which has the same shape of question.
|
|
80
|
+
|
|
81
|
+
**What this must not promise, and the docs should say so rather than let
|
|
82
|
+
somebody find out.** Two live sessions cannot edit one conversation: without
|
|
83
|
+
an authority to reconcile against, a handoff is a baton and not a mirror.
|
|
84
|
+
Continuity is only as good as the checkpoint interval. And a transcript is
|
|
85
|
+
large — the first real one was 19.8 MB across 175 files — so checkpointing
|
|
86
|
+
every turn is not free, which argues for idle and end-of-session rather than
|
|
87
|
+
continuous.
|
|
88
|
+
|
|
89
|
+
**Done when** a session that ends on one machine is collectable on another
|
|
90
|
+
with nobody running `handoff` or `receive`, when a session killed without
|
|
91
|
+
warning still leaves a collectable checkpoint, and when the addressing rule is
|
|
92
|
+
written down and tested rather than left to whoever calls `receive` first.
|
|
@@ -224,6 +224,109 @@ def transcript_metadata(path: Path) -> dict[str, Any]:
|
|
|
224
224
|
return meta
|
|
225
225
|
|
|
226
226
|
|
|
227
|
+
#: Wrappers the harness puts in `user` records that no human typed: tool
|
|
228
|
+
#: results, hook output, notifications, slash-command echoes. A brief that
|
|
229
|
+
#: counted these as instructions would report hundreds of turns for a session
|
|
230
|
+
#: whose human said twenty things.
|
|
231
|
+
_NOT_TYPED = (
|
|
232
|
+
"<task-notification", "<system-reminder", "<local-command", "<command-name",
|
|
233
|
+
"<command-message", "<command-args", "<user-prompt-submit-hook",
|
|
234
|
+
# Not a wrapper but not an instruction either: the harness writes this
|
|
235
|
+
# where a human pressed escape, and it says nothing about the work.
|
|
236
|
+
"[Request interrupted",
|
|
237
|
+
)
|
|
238
|
+
#: How much of one instruction a brief keeps. Long enough to recognise what was
|
|
239
|
+
#: asked, short enough that the whole brief stays pasteable.
|
|
240
|
+
BRIEF_CHARS = 240
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _record_text(record: dict[str, Any]) -> str:
|
|
244
|
+
"""The prose of a record, ignoring tool calls and results."""
|
|
245
|
+
content = (record.get("message") or {}).get("content")
|
|
246
|
+
if isinstance(content, str):
|
|
247
|
+
return content
|
|
248
|
+
if not isinstance(content, list):
|
|
249
|
+
return ""
|
|
250
|
+
return "\n".join(
|
|
251
|
+
part.get("text", "") for part in content
|
|
252
|
+
if isinstance(part, dict) and part.get("type") == "text"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def brief(path: Path, *, limit: int = 0) -> dict[str, Any]:
|
|
257
|
+
"""What a session was *about*, small enough to paste into another one.
|
|
258
|
+
|
|
259
|
+
A transcript is the wrong thing to hand a fresh session even where the
|
|
260
|
+
machinery allows it: this one is 4.5 MB, which no context window holds, and
|
|
261
|
+
replaying it would cost more than the work it describes. What actually
|
|
262
|
+
carries is much smaller — what the human asked for, which files were
|
|
263
|
+
touched, how the session worked. Read off the record rather than
|
|
264
|
+
summarised by a model, so it states what happened instead of what a
|
|
265
|
+
retelling remembers.
|
|
266
|
+
|
|
267
|
+
The filtering is the substance. Claude Code writes tool results, hook
|
|
268
|
+
output and notifications as ``user`` records too, so a naive count reports
|
|
269
|
+
hundreds of "instructions" for a session whose human typed twenty. Only
|
|
270
|
+
prose that no wrapper claims is kept.
|
|
271
|
+
|
|
272
|
+
``limit`` caps the instructions returned, newest first after the opening
|
|
273
|
+
one — a long session's shape is its first ask and its last few, not its
|
|
274
|
+
middle.
|
|
275
|
+
"""
|
|
276
|
+
records = 0
|
|
277
|
+
instructions: list[str] = []
|
|
278
|
+
files: dict[str, int] = {}
|
|
279
|
+
tools: dict[str, int] = {}
|
|
280
|
+
with path.open("rb") as fh:
|
|
281
|
+
for raw in fh:
|
|
282
|
+
records += 1
|
|
283
|
+
try:
|
|
284
|
+
record = json.loads(raw)
|
|
285
|
+
except ValueError:
|
|
286
|
+
continue
|
|
287
|
+
if not isinstance(record, dict):
|
|
288
|
+
continue
|
|
289
|
+
kind = record.get("type")
|
|
290
|
+
if kind == "user":
|
|
291
|
+
text = " ".join(_record_text(record).split())
|
|
292
|
+
if text and not text.startswith(_NOT_TYPED):
|
|
293
|
+
text = text[:BRIEF_CHARS]
|
|
294
|
+
# A repeated instruction is usually the same ask re-sent
|
|
295
|
+
# after an interruption, not a second thing wanted.
|
|
296
|
+
if not instructions or instructions[-1] != text:
|
|
297
|
+
instructions.append(text)
|
|
298
|
+
elif kind == "assistant":
|
|
299
|
+
content = (record.get("message") or {}).get("content")
|
|
300
|
+
if not isinstance(content, list):
|
|
301
|
+
continue
|
|
302
|
+
for part in content:
|
|
303
|
+
if not isinstance(part, dict) or part.get("type") != "tool_use":
|
|
304
|
+
continue
|
|
305
|
+
name = str(part.get("name") or "?")
|
|
306
|
+
tools[name] = tools.get(name, 0) + 1
|
|
307
|
+
target = (part.get("input") or {}).get("file_path")
|
|
308
|
+
if isinstance(target, str) and target:
|
|
309
|
+
files[target] = files.get(target, 0) + 1
|
|
310
|
+
kept = instructions
|
|
311
|
+
if limit and len(instructions) > limit:
|
|
312
|
+
# The opening ask plus the most recent ones: a session's shape is what
|
|
313
|
+
# it was for and where it got to, not the middle.
|
|
314
|
+
kept = instructions[:1] + instructions[-(limit - 1):]
|
|
315
|
+
meta = transcript_metadata(path)
|
|
316
|
+
return {
|
|
317
|
+
"records": records,
|
|
318
|
+
"cwd": meta["cwd"],
|
|
319
|
+
"git_branch": meta["git_branch"],
|
|
320
|
+
"version": meta["version"],
|
|
321
|
+
"user_messages": meta["user_messages"],
|
|
322
|
+
"assistant_messages": meta["assistant_messages"],
|
|
323
|
+
"instructions": kept,
|
|
324
|
+
"instructions_total": len(instructions),
|
|
325
|
+
"files": sorted(files, key=lambda f: -files[f]),
|
|
326
|
+
"tools": dict(sorted(tools.items(), key=lambda kv: -kv[1])),
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
227
330
|
# -------------------------------------------------------------- package ---
|
|
228
331
|
|
|
229
332
|
def _sha256(data: bytes) -> str:
|
|
@@ -3150,6 +3150,8 @@ def cmd_session(args: argparse.Namespace) -> int:
|
|
|
3150
3150
|
return _session_resume(args)
|
|
3151
3151
|
if action in ("register", "unregister"):
|
|
3152
3152
|
return _session_register(args)
|
|
3153
|
+
if action == "brief":
|
|
3154
|
+
return _session_brief(args)
|
|
3153
3155
|
if action in ("handoff", "publish"):
|
|
3154
3156
|
return _session_publish(args, fmt)
|
|
3155
3157
|
return _session_receive(args, fmt)
|
|
@@ -3260,6 +3262,64 @@ def _session_register(args: argparse.Namespace) -> int:
|
|
|
3260
3262
|
return EXIT_OK if report.get("registered") else EXIT_ERROR
|
|
3261
3263
|
|
|
3262
3264
|
|
|
3265
|
+
def _session_brief(args: argparse.Namespace) -> int:
|
|
3266
|
+
"""What a session was about, small enough to paste into another one.
|
|
3267
|
+
|
|
3268
|
+
The answer to a question `receive` cannot address: a cloud session in
|
|
3269
|
+
somebody's browser cannot be handed a transcript — no endpoint accepts one
|
|
3270
|
+
— and could not hold this one if it could, since 4.5 MB does not fit in a
|
|
3271
|
+
context window. What travels is the shape: what was asked, what was
|
|
3272
|
+
touched, how it worked. Read off the record rather than retold by a model.
|
|
3273
|
+
"""
|
|
3274
|
+
session_id = claude_session.valid_session_id(args.session_id)
|
|
3275
|
+
cfg = claude_session.config_dir(getattr(args, "config_dir", None))
|
|
3276
|
+
found = claude_session.find_transcripts(cfg, session_id)
|
|
3277
|
+
if not found:
|
|
3278
|
+
print(f"error: no transcript for {session_id} under {cfg / 'projects'}",
|
|
3279
|
+
file=sys.stderr)
|
|
3280
|
+
return EXIT_ERROR
|
|
3281
|
+
if len(found) > 1 and not args.cwd:
|
|
3282
|
+
where = ", ".join(str(p.parent.name) for p in found)
|
|
3283
|
+
print(f"error: {session_id} is under more than one project key ({where}); "
|
|
3284
|
+
f"name the directory with --cwd", file=sys.stderr)
|
|
3285
|
+
return EXIT_ERROR
|
|
3286
|
+
path = found[0]
|
|
3287
|
+
if args.cwd:
|
|
3288
|
+
key = claude_session.project_key(args.cwd)
|
|
3289
|
+
path = next((p for p in found if p.parent.name == key), found[0])
|
|
3290
|
+
report = claude_session.brief(path, limit=args.limit)
|
|
3291
|
+
if args.json:
|
|
3292
|
+
_print_json({"session_id": session_id, **report})
|
|
3293
|
+
return EXIT_OK
|
|
3294
|
+
fmt = Fmt(_use_color(sys.stdout))
|
|
3295
|
+
print(fmt.bold(f"session {session_id}"))
|
|
3296
|
+
print(f" {report['records']} records · {report['user_messages']} user / "
|
|
3297
|
+
f"{report['assistant_messages']} assistant turns · "
|
|
3298
|
+
f"claude {report['version'] or '?'}")
|
|
3299
|
+
if report["cwd"]:
|
|
3300
|
+
branch = f" on {report['git_branch']}" if report["git_branch"] else ""
|
|
3301
|
+
print(f" worked in {report['cwd']}{branch}")
|
|
3302
|
+
if report["tools"]:
|
|
3303
|
+
top = ", ".join(f"{name} {count}" for name, count in
|
|
3304
|
+
list(report["tools"].items())[:5])
|
|
3305
|
+
print(f" tools: {top}")
|
|
3306
|
+
if report["files"]:
|
|
3307
|
+
shown = ", ".join(Path(f).name for f in report["files"][:8])
|
|
3308
|
+
more = f" (+{len(report['files']) - 8})" if len(report["files"]) > 8 else ""
|
|
3309
|
+
print(f" files: {shown}{more}")
|
|
3310
|
+
total = report["instructions_total"]
|
|
3311
|
+
kept = len(report["instructions"])
|
|
3312
|
+
heading = "what was asked" + (f" ({kept} of {total})" if kept < total else
|
|
3313
|
+
f" ({total})")
|
|
3314
|
+
print(f"\n{fmt.bold(heading)}")
|
|
3315
|
+
for line in report["instructions"]:
|
|
3316
|
+
print(f" - {line}")
|
|
3317
|
+
if not report["instructions"]:
|
|
3318
|
+
print(fmt.dim(" (nothing a human typed — every user record is tool "
|
|
3319
|
+
"output or a notification)"))
|
|
3320
|
+
return EXIT_OK
|
|
3321
|
+
|
|
3322
|
+
|
|
3263
3323
|
def _session_import(args: argparse.Namespace) -> int:
|
|
3264
3324
|
capsule = claude_session.read_capsule(args.capsule_file)
|
|
3265
3325
|
result = claude_session.install(capsule, cwd=args.cwd, force=args.force)
|
|
@@ -6624,6 +6684,26 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
6624
6684
|
s.add_argument("--any-sender", action="store_true",
|
|
6625
6685
|
help="with --resume: start sessions from any roster-verified sender "
|
|
6626
6686
|
"(never one nobody vouched for)")
|
|
6687
|
+
s = ssub.add_parser(
|
|
6688
|
+
"brief",
|
|
6689
|
+
help="what a session was about, small enough to paste into another one",
|
|
6690
|
+
description="A transcript is the wrong thing to hand a fresh session even "
|
|
6691
|
+
"where the machinery allows it: a large one does not fit in a "
|
|
6692
|
+
"context window, and replaying it costs more than the work it "
|
|
6693
|
+
"describes. This prints what actually carries — what the human "
|
|
6694
|
+
"asked for, which files were touched, how the session worked — "
|
|
6695
|
+
"read off the record rather than retold by a model. Use it to "
|
|
6696
|
+
"start a session somewhere a capsule cannot reach, a cloud "
|
|
6697
|
+
"session in a browser being the case that prompted it.",
|
|
6698
|
+
)
|
|
6699
|
+
s.add_argument("session_id", metavar="session-id")
|
|
6700
|
+
s.add_argument("--limit", type=int, default=0,
|
|
6701
|
+
help="keep at most this many instructions: the first, then the "
|
|
6702
|
+
"most recent (default: all)")
|
|
6703
|
+
s.add_argument("--cwd", help="which project key, when the id is under more than one")
|
|
6704
|
+
s.add_argument("--config-dir", help=argparse.SUPPRESS)
|
|
6705
|
+
s.set_defaults(func=cmd_session, session_action="brief")
|
|
6706
|
+
|
|
6627
6707
|
s = ssub.add_parser(
|
|
6628
6708
|
"register",
|
|
6629
6709
|
help="give the desktop app a row for a session installed here",
|