swain-session
Session
Manages session identity, preferences, and context continuity across agent sessions. This skill is agent-agnostic — it relies on AGENTS.md for auto-invocation.
Auto-run behavior
This skill is invoked automatically at session start (see AGENTS.md). When auto-invoked:
- Restore tab name — run the tab-naming script
- Load preferences — read session.json and apply any stored preferences
- Show context bookmark — if a previous session left a context note, display it
When invoked manually, the user can change preferences or bookmark context.
Session purpose text
When the operator launches with free text (e.g., swain new bug about timestamps), the launcher exports SWAIN_PURPOSE and — for runtimes that accept an initial prompt — also passes it inline as /swain-session Session purpose: new bug about timestamps.
The launcher is responsible for choosing the checkout that will own that bookmark:
- If the operator starts from the main checkout, the launcher opens a new worktree first and only then passes the session purpose.
- If the operator starts inside a linked worktree that already has a bookmark, the launcher should steer them to resume/finish that worktree or open a different worktree before reusing the purpose text.
The greeting script (swain-session-greeting.sh) reads $SWAIN_PURPOSE and writes the bookmark deterministically (SPEC-297). The greeting JSON exposes the captured text as the purpose field.
When the greeting JSON's purpose field is non-null:
- Display it to the operator:
**Session purpose:** <text>.
Do not re-parse the initial prompt or call swain-bookmark.sh yourself — the greeting already did both. The inline prompt text is for display context only; the env var is the source of truth.
Preflight
Before any step, run the preflight script to gather all session state in a single pass. This replaces the old subprocess chain (greeting → bootstrap → tab-name) with one read-only script.
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
PREFLIGHT_SCRIPT="$(find "$REPO_ROOT" -path '*/swain-session/scripts/swain-session-preflight.sh' -print -quit 2>/dev/null)"
PREFLIGHT_JSON=$( bash "$PREFLIGHT_SCRIPT" --repo-root "$REPO_ROOT" 2>/dev/null )
echo "$PREFLIGHT_JSON"
Store PREFLIGHT_JSON for use in all steps below. Every decision references a field from this JSON — do not run additional check commands unless performing a mutation.
Step 1 — Fast Greeting (SPEC-194)
Run the greeting script. It calls the preflight internally and applies lightweight mutations (tab naming, lock cleanup, .agents dir creation). It does not invoke specgraph, GitHub API, or the full status dashboard.
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-session-greeting.sh" --json
The greeting emits structured JSON:
{
"greeting": true,
"branch": "trunk",
"dirty": false,
"isolated": false,
"bookmark": "Left off implementing the bootstrap script",
"focus": "VISION-001",
"tab": "project @ branch",
"warnings": []
}
The preflight JSON also includes previous session state under prev_session, which eliminates the need for a separate swain-session-state.sh resume call:
{
"prev_session": {
"exists": true,
"status": "closed",
"session_id": "session-20260404-215204-9576",
"focus_lane": "INITIATIVE-002",
"phase": "closed",
"start_time": "2026-04-05T01:52:04Z",
"end_time": "2026-04-06T04:10:09Z",
"decisions_made": 0,
"walkaway": "Reviewed and fixed SPIKE-058"
}
}
After receiving the greeting JSON:
-
Present the greeting to the operator — branch, dirty state, bookmark (if any), focus lane (if any), and warnings.
-
If
prev_session.existsis true, display the previous session context (from the preflight JSON) so the operator can decide whether to continue or start fresh. -
If
isolatedisfalseand the operator has not started work yet, do not create a worktree now — worktree creation is handled by bin/swain pre-launch (SPEC-245). If a worktree name is needed for reference, generate it:bash "$REPO_ROOT/.agents/bin/swain-worktree-name.sh" "context"Then re-run the greeting with
--pathto refresh tab name and context:bash "$REPO_ROOT/.agents/bin/swain-session-greeting.sh" --path "$(pwd)" --json -
If
bookmarkis not null, display it:Resuming session — Last time: {bookmark}
-
The session is now ready for work. The full status dashboard is available on-demand (see Status Dashboard).
If $TMUX is NOT set (detected by absence of tab in the JSON), check whether tmux is installed:
- tmux not installed: Offer to install it (
brew install tmux). - tmux installed but not in a session: Show:
[note] Not in a tmux session — session tab and pane features unavailable
The operator can say "exit worktree" or "back to main" at any time — this ends the session. bin/swain handles worktree cleanup after the runtime exits (SPEC-245).
Worktree / branch changes (agent-agnostic)
When an agent enters a worktree or switches branches, re-run the bootstrap with --path to update the tab name:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-session-bootstrap.sh" --path "$NEW_WORKDIR" --skip-worktree --auto
This is agent-agnostic — works in Claude Code, opencode, gemini cli, codex, copilot, or any agent that reads AGENTS.md and can run bash commands.
Session.json schema
{
"lastBranch": "trunk",
"lastContext": "Working on swain-session skill",
"preferences": {
"verbosity": "concise"
},
"bookmark": {
"note": "Left off implementing the bootstrap script",
"files": ["SKILL.md"],
"timestamp": "2026-03-10T14:32:00Z"
}
}
Migration: If .agents/session.json does not exist but the old global location (~/.claude/projects/<project-path-slug>/memory/session.json) does, the bootstrap script copies it automatically.
README Reconciliation Checkpoint (SPEC-209)
After the greeting and before work begins, compare README.md against the artifact tree. This runs once per session, at focus lane selection time.
Trigger
When a focus lane is set (either restored from a previous session or newly selected), and README.md exists:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
[ -f "$REPO_ROOT/README.md" ] && echo "has_readme" || echo "no_readme"
If no README exists, skip reconciliation silently — swain-doctor will flag the missing README.
Process
- Read README.md and extract claims — any statement about what the project does, who it's for, how it works, what it supports, or what behavior it exhibits. Read the entire README as prose; no markers or section conventions required.
- Compare claims against current Active Visions, Designs, Journeys, and Persona artifacts. Look for:
- Stale promises — README claims a feature or behavior that an artifact explicitly dropped or superseded.
- Missing coverage — an artifact describes a capability the README doesn't mention.
- Contradictions — README and artifact disagree on behavior, audience, or scope.
- For each mismatch, surface a specific question to the operator:
"README says '{claim}' but {artifact-id} {describes the conflict}. Which is right?"
Reconciliation direction
Bidirectional. Drift does not assume artifacts are right:
- A new Vision may mean the README needs updating.
- The README may be right and the Vision needs reshaping.
- A promise may have been intentionally dropped and needs removing from both.
Deferral tracking
The operator can defer any mismatch. Deferrals are tracked in .agents/session.json under a readme_deferrals key:
{
"readme_deferrals": [
{
"claim": "real-time sync",
"conflict_artifact": "VISION-003",
"deferred_at": "2026-03-31T14:00:00Z"
}
]
}
Deferred items are raised again at the next session start. When the operator resolves a deferral (updates README or artifact), remove it from the list.
Silent pass
If no drift is detected, the reconciliation check passes silently — no output to the operator.
Session Lifecycle (SPEC-119)
swain-session owns a bounded session lifecycle: start → work → close → resume. Session state is tracked in .agents/session-state.json via the swain-session-state.sh script.
Session start
After bootstrap completes and the worktree is ready, initialize the session lifecycle:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-session-state.sh" init --focus "<FOCUS-ID>" --session-roadmap "$(pwd)/SESSION-ROADMAP.md" --repo-root "$REPO_ROOT"
This:
- Creates
.agents/session-state.jsonwith focus lane, decision budget (default 5), and start time - Generates
SESSION-ROADMAP.mdviachart.sh session --focus <ID>
The focus lane defaults to the previous session's lane (from bootstrap JSON session.focus). Confirm with the operator or accept their redirect.
Custom decision budget: --budget 7
During work — recording decisions
When the operator or agent makes a decision (approves a spec, chooses an approach, sets direction), record it:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-session-state.sh" record-decision --note "Approved SPEC-119 implementation approach"
Session close
When the operator says "done", "wrap up", "close session", or the decision budget is reached, execute this close sequence. Critical: swain-retro must run while the session is still active so it can read session state. Do not close the session before running retro.
Step 1 — Generate session digest and progress logs
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-session-digest.sh" --session-id "$(jq -r .session_id "$REPO_ROOT/.agents/session-state.json")" --output "$REPO_ROOT/.agents/session-log.jsonl"
bash "$REPO_ROOT/.agents/bin/swain-progress-log.sh" --digest "$REPO_ROOT/.agents/session-log.jsonl"
This appends a JSONL digest entry and updates each touched EPIC/Initiative's progress.md and ## Progress section.
Step 2 — Run retro (session still active)
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
SWAIN_RETRO_SKILL="$REPO_ROOT/.claude/skills/swain-retro/SKILL.md"
Skill("$SWAIN_RETRO_SKILL", "Session close — session is closing. Run /swain-retro to capture session learnings before the session state is cleared.")
Important: Retro reads session.json and session-state.json while they are still populated. Do not call session-state.sh close before this step.
Step 3 — Close the session
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-session-state.sh" close --walkaway "Completed SPEC-119 tests and state management" --session-roadmap "$(pwd)/SESSION-ROADMAP.md"
This sets session phase to closed with end time and appends the walk-away signal to SESSION-ROADMAP.md.
Step 4 — Run session teardown
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
SWAIN_TEARDOWN_SKILL="$REPO_ROOT/.claude/skills/swain-teardown/SKILL.md"
Skill("$SWAIN_TEARDOWN_SKILL", "Session teardown — --session-chain flag passed from swain-session close handler.")
This runs orphan worktree checks, git dirty-state check, ticket sync prompt, and writes a handoff summary. The --session-chain flag tells teardown to skip the redundant session-active check since the handler already confirmed session state.
Step 5 — Commit SESSION-ROADMAP.md
Finally, commit SESSION-ROADMAP.md to git.
Session resume
On the next session start, read prev_session from the preflight JSON (see Preflight). This includes the previous session's focus lane, walkaway note, decision count, and staleness status — no separate script call needed.
If you need to call session-state.sh directly (e.g., from a script that doesn't have the preflight JSON):
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-session-state.sh" resume
Display the previous session context so the operator can decide whether to continue or start fresh.
Session state schema
{
"session_id": "session-20260328-220634-4ad1",
"focus_lane": "INITIATIVE-019",
"phase": "active",
"start_time": "2026-03-28T22:06:34Z",
"last_activity_time": "2026-03-28T22:06:34Z",
"end_time": null,
"decision_budget": 5,
"decisions_made": 0,
"decisions": [],
"walkaway": null
}
Manual invocation commands
When invoked explicitly by the user, support these operations:
Set tab name
User says something like "set tab name to X" or "rename tab":
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-tab-name.sh" "Custom Name"
Bookmark context
User says "remember where I am" or "bookmark this":
- Infer what they're working on from conversation context, or use the note they provided — do not prompt the user
- Write to session.json
bookmarkfield with note, relevant files, and timestamp - If a bookmark already exists, overwrite it silently without asking for confirmation —
swain-bookmark.shhandles atomic writes
Clear bookmark
User says "clear bookmark" or "fresh start":
- Remove the
bookmarkfield from session.json
Show session info
User says "session info" or "what's my session":
- Display current tab name, branch, preferences, bookmark status
- If the bookmark note contains an artifact ID (e.g.,
SPEC-052,EPIC-018), show the Vision ancestry breadcrumb for strategic context. Runbash "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents/bin/chart.sh" scope <ID> 2>/dev/null | head -5to get the parent chain. Display as:Context: Swain > Operator Situational Awareness > Vision-Rooted Chart Hierarchy
Set preference
User says "set preference X to Y":
- Update
preferencesin session.json
Post-operation bookmark (auto-update protocol)
Other swain skills update the session bookmark after operations. Read references/bookmark-protocol.md for the protocol, invocation patterns, and examples.
Focus Lane
The operator can set a focus lane to scope recommendations within a single vision or initiative. This is a steering mechanism — it doesn't hide other work, but frames recommendations around the operator's current focus.
Setting focus: When the operator says "focus on security" or "I'm working on VISION-001", resolve the name to an artifact ID and invoke the focus script.
Name-to-ID resolution: If the operator uses a name instead of an ID (e.g., "security" instead of "VISION-001"), search Vision and Initiative artifact titles for the best match using swain chart:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/chart.sh" --ids --flat 2>/dev/null | grep -i "<name>"
If exactly one match, use it. If multiple matches, ask the operator to clarify. If no match, tell the operator no Vision or Initiative matches that name and offer to create one.
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-focus.sh" set <RESOLVED-ID>
Clearing focus:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-focus.sh" clear
Checking focus:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-focus.sh"
Display the focus artifact as a context line by calling artifact-context.sh on the focus ID. Fall back to the bare ID if the utility is unavailable.
Focus lane is stored in .agents/session.json under the focus_lane key. It persists across status checks within a session. The status dashboard reads it to filter recommendations and show peripheral awareness for non-focus visions.
Status Dashboard (SPEC-122)
swain-session now owns the project status dashboard. When the operator says "status", "what's next", "dashboard", "overview", "where are we", "what should I work on", or "show me priorities", run the status script:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
STATUS_SCRIPT="$REPO_ROOT/.agents/bin/swain-status.sh"
[ -f "$STATUS_SCRIPT" ] && bash "$STATUS_SCRIPT" --refresh || echo "status dashboard script not found"
For compact mode (MOTD): bash "$STATUS_SCRIPT" --compact
After the script runs, present a structured agent summary following references/agent-summary-template.md.
Cache
Status writes to .agents/status-cache.json with 120-second TTL. Use --refresh to bypass, --json for raw output.
Recommendation
Read .priority.recommendations[0] from the JSON cache. When a focus lane is set, recommendations scope to that vision/initiative.
Context-rich display
When presenting artifacts to the operator (recommendations, focus lane, decisions needed), use the artifact-context utility instead of bare IDs:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
CONTEXT=$(bash "$REPO_ROOT/.agents/bin/artifact-context.sh" <ARTIFACT-ID> 2>/dev/null)
If the utility is available and returns output, use the context line. If unavailable or empty, fall back to <ID> — <title> (current behavior).
Display format: title ID — scope. progress.
Mode Inference
- Both specs in review AND strategic decisions pending → ask operator
- Specs awaiting review → detail mode
- Focus lane + pending decisions → vision mode
- Nothing actionable → vision mode (master plan mirror)
Decisions Needed (roadmap integration)
Uses chart.sh roadmap --json for Eisenhower classification. Show top 5 items from "Do First" and "Schedule" quadrants that need operator decisions.
Settings
This skill reads from swain.settings.json (project root) and ~/.config/swain/settings.json (user override). User settings take precedence.
Relevant settings:
terminal.tabNameFormat— format string for tab names. Supports{project}and{branch}placeholders. Default:{project} @ {branch}
Error handling
- If jq is not available, warn the user and skip JSON operations. Tab naming still works without jq.
- If git is not available, use the directory name as the project name and skip branch detection.
- Never fail hard — session management is a convenience, not a gate.
More from cristoslc/swain
swain-do
Task tracking and implementation execution for swain projects. Invoke whenever a SPEC needs an implementation plan, the user asks what to work on next, wants to check or update task status, claim or close tasks, manage dependencies, abandon work, bookmark context, or record a decision. Also invoked by swain-design after creating a SPEC that's ready for implementation. Tracks SPECs and SPIKEs — not EPICs, VISIONs, or JOURNEYs directly (those get decomposed into SPECs first). Triggers also on: 'bookmark', 'remember where I am', 'record decision'.
124swain-update
Update swain skills to the latest version. Use when the user says 'update swain', 'upgrade swain', 'pull latest swain', 'reinstall swain', 'refresh skills', or wants to update their swain skills installation. Uses npx to pull the latest swain release from GitHub, with a git-clone fallback, then invokes swain-doctor to reconcile governance and validate project health.
121swain-release
Cut a release — detect versioning context, generate a changelog from conventional commits, bump versions, create a git tag, and optionally squash-merge to a release branch. Use when the user says "release", "cut a release", "tag a release", "bump the version", "create a changelog", "ship a version", "publish", or any variation of shipping/publishing a version. This skill is intentionally generic and works across any repo — it infers context from git history and project structure rather than assuming a specific setup. Supports the trunk+release branch model (ADR-013) when a `release` branch exists.
121swain-design
Create, validate, and transition documentation artifacts (Vision, Initiative, Epic, Spec, Spike, ADR, Persona, Runbook, Design, Journey) through lifecycle phases. Handles spec writing, feature planning, epic creation, initiative creation, ADR drafting, research spikes, persona definition, runbook creation, design capture, architecture docs, phase transitions, implementation planning, cross-reference validation, and audits. Also invoke to update frontmatter fields, re-parent an artifact under a different epic or initiative, or set priority on a Vision or Initiative. Chains into swain-do for implementation tracking on SPEC; decomposes EPIC/VISION/INITIATIVE/JOURNEY into children first.
121swain
Meta-router for swain skills. Invoke when the user explicitly asks swain to do something — not merely when they mention the project by name. Routes to the matching swain-* sub-skill — only load the one that matches. If the user's intent matches multiple rows, pick the most specific match. Sub-skills that are not installed will gracefully no-op.
118swain-search
Trove collection and normalization for swain-design artifacts. Collects sources from the web, local files, and media (video/audio), normalizes them to markdown, and caches them in reusable troves. Use when researching a topic for a spike, ADR, vision, or any artifact that needs structured research. Also use to refresh stale troves or extend existing ones with new sources. Triggers on: 'research X', 'gather sources for', 'compile research on', 'search for sources about', 'refresh the trove', 'find existing research on X', or when swain-design needs research inputs for a spike or ADR.
112