| name | sessions |
| description | Understand the Synapse Claude Code plugin hooks, session lifecycle, and multi-agent parallel execution. Covers what each hook does, where plugin state lives, and how to run multiple experiments in parallel via Task sub-agents. |
| license | AGPL-3.0 |
| metadata | {"author":"Vincentwei1021","version":"0.6.1","category":"research","mcp_server":"synapse"} |
Sessions Skill
Use this skill when the task is about how the Synapse plugin itself behaves inside Claude Code: which hooks fire, what they inject into context, how sub-agent sessions are managed, and how to dispatch multiple experiments in parallel to Task sub-agents.
Prompt Boundary
Stay inside this skill when the work is about:
- understanding why Synapse context appeared in the session (which hook injected it)
- debugging a sub-agent that is not behaving as expected (session, heartbeat, or teardown issue)
- deciding how a main agent should dispatch parallel experiment work across sub-agents
- deciding when to call
synapse_create_session / synapse_close_session directly instead of relying on hooks
Hand off to:
- experiments for the actual work inside one experiment
- autonomy for the CC-client autonomous loop
Core Rule
For sub-agents spawned through the Task tool, sessions are fully automatic. Do not manually call synapse_create_session or synapse_close_session for a sub-agent unless you are debugging the lifecycle itself. The SubagentStart and SubagentStop hooks already do it.
Plugin Hooks At A Glance
The plugin ships ten hooks wired up in hooks/hooks.json. Each reads/writes local state under .synapse/ in the project and, when needed, calls Synapse MCP tools via the bundled synapse-api.sh wrapper.
| Hook script | Claude Code event | What it does | State touched | MCP calls |
|---|
on-session-start.sh | SessionStart (startup | resume | compact) | Calls synapse_checkin, caches owner / roles / project UUID into state.json, scans for pre-assigned sub-agent session files, and builds the rich additionalContext block that orients the agent on assignments, projects, and workflow. | state.json, reads sessions/*.json | synapse_checkin, optional synapse_session_heartbeat |
on-user-prompt.sh | UserPromptSubmit | Fast local-only check on every user turn. Scans .synapse/sessions/ and injects a brief reminder that sub-agent sessions are auto-managed. No network calls (stays under 100 ms). | reads sessions/ | none |
on-pre-enter-plan.sh | PreToolUse (EnterPlanMode) | Injects planning-mode guidance: prefer the current Experiment pipeline (draft → pending_review → pending_start → in_progress → completed), plan one independent run per experiment card, do not plan to create sessions manually. | none | none |
on-pre-exit-plan.sh | PreToolUse (ExitPlanMode) | Reminds the agent to verify that the plan is expressed as Experiment records before executing. | none | none |
on-pre-spawn-agent.sh | PreToolUse (Task) | Before a sub-agent is spawned, atomically writes .synapse/pending/{name} with the agent name and type so the SubagentStart hook can claim it. Skips read-only sub-agent types. | writes pending/{name} | none |
on-subagent-start.sh | SubagentStart | Atomically claims the pending file via mv, then either reuses an active session, reopens a closed one, or creates a new one via synapse_list_sessions / synapse_reopen_session / synapse_create_session. Writes sessions/{name}.json, stores the mapping in state.json, and injects the session UUID plus the execution workflow directly into the sub-agent's context. | state.json, sessions/{name}.json, claimed/{agent_id} | synapse_list_sessions, synapse_create_session, synapse_reopen_session, synapse_session_heartbeat |
on-teammate-idle.sh | TeammateIdle | When a teammate sub-agent idles between turns, sends a heartbeat so its Synapse session does not auto-time-out after 1 hour. Output suppressed — this fires too often to notify the user. | reads state.json | synapse_session_heartbeat |
on-subagent-stop.sh | SubagentStop | Looks up the session UUID, calls synapse_close_session, and cleans up state.json, sessions/{name}.json, and claimed/{agent_id}. | deletes state entries, sessions/{name}.json, claimed/{agent_id} | synapse_close_session |
on-task-completed.sh | TaskCompleted | Scans the completed task description/subject for a synapse:experiment:<uuid> marker. If found, injects a reminder to finalize the experiment with synapse_submit_experiment_results (or report progress if still running). | reads task metadata only | none |
on-session-end.sh | SessionEnd | Safety-checked cleanup. Removes .synapse/ only when all sub-agent sessions are closed and state.json has no meaningful content left. | deletes .synapse/ if safe | none |
Local State Layout
The plugin keeps per-project state under .synapse/ in the project working directory:
state.json — flock-guarded key/value store. Holds owner info cached from synapse_checkin, agent roles, the primary project UUID, and the session_{…} / agent_for_session_{…} / name_for_agent_{…} mappings used by the start/stop hooks.
pending/{name} — written by on-pre-spawn-agent.sh, atomically claimed by on-subagent-start.sh via mv.
claimed/{agent_id} — marker file showing which pending entry was claimed.
sessions/{name}.json — per-sub-agent session metadata: sessionUuid, agentId, agentName, agentType, sessionAction, createdAt. Other hooks (idle, stop, user-prompt) read from here to locate the session.
on-session-end.sh removes .synapse/ only when it is safe; otherwise state is preserved across sessions so resumed work reconnects to the same Synapse sessions.
MCP Connection Session vs Synapse Agent Session
These are two different things — do not confuse them.
- MCP connection session — the HTTP-streamable session on
/api/mcp. Identified by the mcp-session-id header, auto-renewed on every request, expires after 30 minutes of inactivity. The plugin handles this transparently.
- Synapse agent session — a durable record in Synapse of which agent is working on what (the green / yellow / grey indicators on the Settings page). Created/closed by the plugin hooks above, or explicitly by
synapse_create_session / synapse_close_session.
Session Status Lifecycle
active ——(no heartbeat 1h)——> inactive ——(heartbeat)——> active
\ \
\—— close ——> closed ——(reopen)——> active
| Status | Meaning |
|---|
active | Agent is working. Green indicator. |
inactive | No heartbeat in over an hour. Yellow indicator. |
closed | Session ended. Grey indicator. Can be reopened. |
Session Tools
| Tool | Purpose |
|---|
synapse_list_sessions | List sessions for the current agent. |
synapse_get_session | Read one session's details. |
synapse_create_session | Create a named session — usually only needed for direct (non-sub-agent) work. |
synapse_close_session | Close a session. |
synapse_reopen_session | Reopen a closed session instead of creating a duplicate with the same name. |
synapse_session_heartbeat | Keep a session active. Hooks already send heartbeats automatically. |
synapse_session_checkin_experiment | Bind a session to a current Experiment. Usually handled by synapse_start_experiment({ sessionUuid }). |
synapse_session_checkout_experiment | Unbind a session from a current Experiment. Usually handled by synapse_submit_experiment_results({ sessionUuid }). |
Running Multiple Experiments In Parallel
When the main agent needs to execute several pending_start experiments concurrently, it should orchestrate, not execute. The plugin does the heavy lifting for session bookkeeping; the main agent only needs to spawn sub-agents and monitor.
Main agent: dispatch
# 1. Refresh and list what needs to run
synapse_checkin()
synapse_get_assigned_experiments({ researchProjectUuid, statuses: ["pending_start"] })
# 2. Inspect each candidate
synapse_get_experiment({ experimentUuid })
# 3. For each experiment, spawn a Task sub-agent with the experiment UUID in the prompt.
# The SubagentStart hook auto-creates/reuses a Synapse session and injects the
# execution workflow. The main agent does not need to call synapse_create_session.
Task({
subagent_type: "general-purpose",
name: "training-worker-<short>",
prompt: "Your Synapse experiment UUID: <experiment-uuid>. Run the experiment end to end."
})
Sub-agent: execute
Each sub-agent follows the full execution checklist in experiments: synapse_start_experiment({ sessionUuid }) → synapse_list_compute_nodes / synapse_reserve_gpus → synapse_get_node_access_bundle → run remotely (tmux + unbuffered) → synapse_report_experiment_progress at milestones → synapse_submit_experiment_results({ sessionUuid }) → optional synapse_save_experiment_report.
Main agent: monitor
The main agent does not block on any one sub-agent. After dispatching all sub-agents, it schedules a single Claude Code CronCreate heartbeat that polls Synapse on a cadence — see "Monitoring Long Runs With CronCreate" in the experiments skill for the full pattern. The heartbeat prompt should look like:
CronCreate({
cron: "*/10 * * * *",
prompt: "Check Synapse for project <projectUuid>: synapse_get_assigned_experiments({ researchProjectUuid: '<projectUuid>', statuses: ['in_progress', 'completed'] }). For each still-in-progress experiment, synapse_get_experiment + synapse_report_experiment_progress with the latest tmux/log line. Once all have completed, CronDelete this job.",
recurring: true,
durable: true,
})
Within each fire of the heartbeat, the iteration looks like:
synapse_get_assigned_experiments({
researchProjectUuid,
statuses: ["in_progress", "completed"]
})
# For any experiment still in_progress, read its latest state
synapse_get_experiment({ experimentUuid })
# Once all have completed, synthesize. If this is the assigned autonomous-loop
# agent, propose follow-ups; otherwise use synapse_create_experiment for
# user-directed terminal work.
synapse_propose_experiment({ researchProjectUuid, title, description })
durable: true keeps the heartbeat alive across CC restarts so cards do not go stale just because the user closed their laptop. Always CronDelete once every experiment is completed and synthesis has been written.
Sequential multi-experiment sub-agent
A single sub-agent can also handle multiple experiments in order when dependencies matter:
Task({
subagent_type: "general-purpose",
name: "sequential-worker",
prompt: """
Synapse experiments, in order (each depends on the previous):
1. <experiment-uuid-1> — baseline evaluation
2. <experiment-uuid-2> — ablation built on #1's results
For each: start_experiment → run → report_progress → submit_results.
"""
})
Project-Level MCP For Sub-Agents
Sub-agents only inherit Synapse MCP access if the MCP server is configured at the project level. Put the config in .mcp.json at the project root (the plugin bundle ships the template at public/synapse-plugin/.mcp.json). User-level-only MCP configs will not reach sub-agents.
Troubleshooting
| Symptom | Likely cause / fix |
|---|
| Sub-agent cannot see Synapse tools | MCP is user-level only. Move .mcp.json to the project root. |
Sub-agent session shown as inactive | Heartbeat has not fired for 1 h. Usually the sub-agent crashed; respawn with the same name — SubagentStart will reopen the existing session instead of making a duplicate. |
| Duplicate sessions appear with similar names | A previous sub-agent stopped without the SubagentStop hook firing (hard crash). Close stale sessions with synapse_close_session, then respawn. |
| Main agent did not receive checkin context | SessionStart hook failed (check SYNAPSE_URL, SYNAPSE_API_KEY). Call synapse_checkin() manually to recover. |
Experiment stuck in in_progress | The sub-agent died before synapse_submit_experiment_results. Either resume by respawning the sub-agent with the same experiment UUID, or the main agent calls synapse_report_experiment_progress and then synapse_submit_experiment_results({ outcome: "failure", experimentResults: { error: "..." } }) to close it out. |
Reference