
Session Handoff
- 67 installs
- 36 repo stars
- Updated July 14, 2026
- oimiragieo/agent-studio
Helps with ai & agent building tasks.
About
session-handoff is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- session-handoff
- AI & Agent Building
- AI-coding skill
Session Handoff by the numbers
- 67 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #5,906 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oimiragieo/agent-studio --skill session-handoffAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 67 |
|---|---|
| repo stars | ★ 36 |
| Last updated | July 14, 2026 |
| Repository | oimiragieo/agent-studio ↗ |
What it does
Helps with ai & agent building tasks.
Files
<identity> Session Handoff Specialist - Executes the Phase 7 context handoff loop, transferring session continuity natively across processes. </identity>
<capabilities>
- Writing the session handoff log securely directly to disk via atomic locks.
- Checking the Active Task database to prevent corruption and cross-session overlap (Drain Gate).
- Spawning a detached cross-platform GUI terminal window using the verified OS decision matrix.
</capabilities>
<instructions>
When to Use
Invoke this skill:
- When context reaches maximum capacity (>150k limit)
- When the user explicitly asks to restart, shift-change, or prep for continuation.
- Before ending a long work session
How to Execute (MANDATORY)
To trigger the session handoff, you MUST execute the internal skill executable via the Bash tool. You shouldn't generate the log yourself—the executable handles all schema management and polling.
node .claude/skills/session-handoff/session-handoff.cjsDrain-Complete Gate
If the executable fails and prints [session-handoff] ABORT: Cannot handoff session while tasks are active., you did not follow the drain rule! You must explicitly use TaskUpdate to either mark all active tasks as completed OR suspended before re-running the skill.
Required Setup (Context Preservation)
Before running the skill, ensure that .claude/context/memory/active_context.md is updated with necessary context you want the next agent to know, as the script will synthesize it into the handoff payload.
</instructions>
Invoke the session-handoff skill and follow it exactly as presented to you
'use strict';
/**
* Post-execute hook for session-handoff
* Auto-generated by enterprise-bundle-scaffolder
*
* Records metrics after skill execution.
*/
function postExecute(_context) {
// Record execution metrics
return { ok: true, skill: 'session-handoff' };
}
module.exports = { postExecute };
'use strict';
/**
* Pre-execute hook for session-handoff
* Auto-generated by enterprise-bundle-scaffolder
*
* Validates inputs before skill execution.
*/
function preExecute(context) {
// Validate skill invocation context
if (!context || typeof context !== 'object') {
return { allow: true, message: 'session-handoff: no context to validate' };
}
return { allow: true };
}
module.exports = { preExecute };
session-handoff Research Requirements
Generated: 2026-02-28
Skill Description
Prepare context for new conversations when session is lost or ending. Creates handoff documents that capture current state, progress, and next steps for seamless continuation.
Research Areas
- Current best practices for session-handoff
- Industry standards and tooling
- Integration patterns
Source References
- To be populated by skill-updater research phase
session-handoff Rules
Purpose
Prepare context for new conversations when session is lost or ending. Creates handoff documents that capture current state, progress, and next steps for seamless continuation.
Best Practices
- Create handoff before long sessions end
- Include concrete next steps, not vague descriptions
- Reference specific files and line numbers
- Capture decisions made and rationale
- Store handoffs in persistent memory location
Integration Points
See SKILL.md for complete documentation.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "session-handoffInput",
"description": "Input schema for Prepare context for new conversations when session is lost or ending. Creates handoff documents that capture current state, progress, and next steps for seamless continuation.",
"type": "object",
"additionalProperties": true,
"properties": {
"target": {
"type": "string",
"description": "Target file or path for the skill to operate on"
},
"options": {
"type": "object",
"description": "Additional options for skill execution",
"additionalProperties": true
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "session-handoffOutput",
"type": "object",
"additionalProperties": true,
"properties": {
"ok": {
"type": "boolean"
},
"summary": {
"type": "string"
}
}
}
#!/usr/bin/env node
'use strict';
/**
* session-handoff - Enterprise Skill Script
* Auto-generated by enterprise-bundle-scaffolder
*/
const fs = require('fs');
const path = require('path');
// Parse arguments
const args = process.argv.slice(2);
const options = {};
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--')) {
const key = args[i].slice(2);
const value = args[i + 1] && !args[i + 1].startsWith('--') ? args[++i] : true;
options[key] = value;
}
}
if (options.help) {
console.log(`
session-handoff - Enterprise Skill
Usage:
node main.cjs --check <file> Check a file against guidelines
node main.cjs --list List all guidelines
node main.cjs --help Show this help
Description:
Prepare context for new conversations when session is lost or ending. Creates handoff documents that capture current state, progress, and next steps for seamless continuation.
`);
process.exit(0);
}
if (options.list) {
console.log('Guidelines for session-handoff:');
console.log('See SKILL.md for full guidelines');
process.exit(0);
}
console.log('session-handoff skill loaded. Use with Claude for code review.');
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const { getOrCreateSessionId } = require('../../lib/context/session-id-manager.cjs');
// Path resolution
const projectRoot = process.cwd();
const runtimeDir = path.join(projectRoot, '.claude/context/runtime');
const memoryDir = path.join(projectRoot, '.claude/context/memory');
const tasksFile = path.join(runtimeDir, 'tasks.json');
console.log('[session-handoff] Initiating programmatic session handoff...');
const args = process.argv.slice(2);
const autoSuspend = args.includes('--auto-suspend');
// MT-B: Drain-complete gate via tasks database reading
let tasks = [];
if (fs.existsSync(tasksFile)) {
try {
tasks = JSON.parse(fs.readFileSync(tasksFile, 'utf8'));
} catch (e) {
console.error(`[session-handoff] Failed to read tasks database: ${e.message}`);
process.exit(1);
}
}
const activeTasks = tasks.filter(t => t.status === 'in_progress' || t.status === 'blocked');
if (activeTasks.length > 0) {
if (autoSuspend) {
console.log(
`[session-handoff] --auto-suspend flag detected. Suspending ${activeTasks.length} active tasks...`
);
let modified = false;
tasks.forEach(t => {
if (t.status === 'in_progress' || t.status === 'blocked') {
t.status = 'suspended';
t.metrics = t.metrics || {};
t.metrics.suspended_at = new Date().toISOString();
t.metrics.suspend_reason = 'Auto-suspended during session handoff via --auto-suspend flag';
modified = true;
}
});
if (modified) {
try {
fs.writeFileSync(tasksFile, JSON.stringify(tasks, null, 2), 'utf8');
console.log(`[session-handoff] Tasks successfully suspended.`);
} catch (e) {
console.error(
`[session-handoff] Failed to write tasks database during suspend: ${e.message}`
);
process.exit(1);
}
}
} else {
console.error(`\n[session-handoff] ABORT: Cannot handoff session while tasks are active.`);
console.error(`Active tasks found:`);
activeTasks.forEach(t => console.error(` - [${t.id}] ${t.description} (${t.status})`));
console.error(
`\nPlease instruct the model to finish or formally suspend these tasks before handing off, or pass the --auto-suspend flag.`
);
process.exit(1);
}
}
// Ensure runtime dir exists
if (!fs.existsSync(runtimeDir)) {
fs.mkdirSync(runtimeDir, { recursive: true });
}
const sessionId = getOrCreateSessionId(runtimeDir);
// countTokens() — reads token count from budget-tracker.json (mock-friendly)
function countTokens() {
try {
const budgetPath = path.join(runtimeDir, 'budget-tracker.json');
if (!fs.existsSync(budgetPath)) return 0;
const raw = fs.readFileSync(budgetPath, 'utf8');
const data = JSON.parse(raw);
if (!data || typeof data !== 'object') return 0;
const entry = data[sessionId];
if (entry && typeof entry.totalTokens === 'number') {
return entry.totalTokens;
}
return 0;
} catch (_e) {
return 0;
}
}
// Read context summary from active_context if available
let contextSummary = 'Context transferred via session-handoff skill.';
const activeContextPath = path.join(memoryDir, 'active_context.md');
if (fs.existsSync(activeContextPath)) {
contextSummary = fs.readFileSync(activeContextPath, 'utf8').substring(0, 100000);
}
// Build structured resumeInstructions from available context
function buildResumeInstructions() {
// Extract objective from active_context.md (first heading or first line)
let objective = 'Resume previous session work.';
if (fs.existsSync(activeContextPath)) {
const lines = fs.readFileSync(activeContextPath, 'utf8').split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith('# ') || trimmed.startsWith('## ')) {
objective = trimmed.replace(/^#+\s+/, '');
break;
} else if (trimmed.length > 10 && !trimmed.startsWith('<!--')) {
objective = trimmed;
break;
}
}
}
// Build openTasks from tasks database
const openTasks = tasks
.filter(t => t.status === 'pending' || t.status === 'in_progress' || t.status === 'suspended')
.map(t => `${t.id}: ${t.description || t.subject || '(no description)'}`);
// Key files — gather recently referenced files from active_context
const keyFiles = [];
if (fs.existsSync(activeContextPath)) {
const content = fs.readFileSync(activeContextPath, 'utf8');
const fileMatches = content.match(/\.claude\/[^\s"'\n,)]+\.(cjs|md|json|ts|js)/g) || [];
const unique = [...new Set(fileMatches)].slice(0, 10);
keyFiles.push(...unique);
}
// Recent decisions from decisions.md
const recentDecisions = [];
const decisionsPath = path.join(memoryDir, 'decisions.md');
if (fs.existsSync(decisionsPath)) {
const lines = fs.readFileSync(decisionsPath, 'utf8').split('\n');
const adrLines = lines.filter(l => l.startsWith('## ADR') || l.startsWith('### ADR')).slice(-5);
recentDecisions.push(...adrLines.map(l => l.replace(/^#+\s+/, '')));
}
// Risks from issues.md or defaults
const risks = [];
const issuesPath = path.join(memoryDir, 'issues.md');
if (fs.existsSync(issuesPath)) {
const lines = fs.readFileSync(issuesPath, 'utf8').split('\n');
const issueLines = lines.filter(l => l.startsWith('- ') || l.startsWith('* ')).slice(-5);
risks.push(...issueLines.map(l => l.replace(/^[-*]\s+/, '')));
}
// Resume prompt — be explicit about executing ALL work, not just discovering it
const resumePrompt = `You are resuming an in-progress session. Read .claude/context/memory/active_context.md FIRST and execute ALL tasks listed under NEXT ACTION (IMMEDIATE). Do NOT stop after one task — complete the FULL pipeline. Use TaskList() to track progress. Spawn specialist agents for each wave. Do NOT just clean up stale tasks and stop.`;
return {
objective,
nextStep:
openTasks.length > 0
? `Continue with: ${openTasks[0]}`
: 'Run TaskList() to discover pending work',
openTasks,
keyFiles,
recentDecisions,
risks,
resumePrompt,
};
}
// pendingMemoryWrites from learnings.md
function extractPendingMemoryWrites() {
const writes = [];
const learningsPath = path.join(memoryDir, 'learnings.md');
if (!fs.existsSync(learningsPath)) return writes;
try {
const content = fs.readFileSync(learningsPath, 'utf8');
const lines = content.split('\n');
const bulletLines = lines.filter(l => l.startsWith('- ') || l.startsWith('* ')).slice(-10);
writes.push(...bulletLines.map(l => l.replace(/^[-*]\s+/, '')));
} catch (_e) {
// ignore
}
return writes;
}
// countTokens() pre-validation
const tokenCount = countTokens();
process.stderr.write(`[session-handoff] Token count from budget-tracker: ${tokenCount}\n`);
const resumeInstructions = buildResumeInstructions();
const pendingMemoryWrites = extractPendingMemoryWrites();
// Generate session name for --name flag: shift-YYYY-MM-DD-HH
function buildSessionName() {
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
return `shift-${yyyy}-${mm}-${dd}-${hh}`;
}
const sessionName = buildSessionName();
// Build the M7.1 Log with structured resumeInstructions
const handoverData = {
schemaVersion: '2.0.0',
generation: 1,
sessionId: sessionId,
status: 'READY',
tokenCount: tokenCount,
resumeInstructions: resumeInstructions,
fallbackInstruction: 'Run TaskList() to discover pending work, check active_context.md',
contextSummary: contextSummary,
pendingMemoryWrites: pendingMemoryWrites,
timestamp: new Date().toISOString(),
};
const tmpPath = path.join(runtimeDir, `shift-change-log.tmp-${Date.now()}.json`);
const finalPath = path.join(runtimeDir, 'shift-change-log.json');
try {
// Write atomic
fs.writeFileSync(tmpPath, JSON.stringify(handoverData, null, 2), 'utf8');
fs.renameSync(tmpPath, finalPath);
console.log(`[session-handoff] Wrote READY handover log atomically.`);
} catch (e) {
console.error(`[session-handoff] Failed to write handover log: ${e.message}`);
process.exit(1);
}
// M7.1 Spawn directly
console.log(`[session-handoff] Spawning new terminal session (name: ${sessionName})...`);
const spawnScript = path.join(projectRoot, 'scripts/spawn-new-session.cjs');
if (!fs.existsSync(spawnScript)) {
console.error(`[session-handoff] Fatal: Cannot find spawn script at ${spawnScript}`);
process.exit(1);
}
try {
// Execute the spawn script synchronously with --name flag and --skip-drain
execFileSync('node', [spawnScript, '--skip-drain', '--name', sessionName], { stdio: 'inherit' });
console.log(`[session-handoff] Handoff execution completed successfully.`);
} catch (e) {
console.error(`[session-handoff] Spawn script failure: ${e.message}`);
process.exit(1);
}
session-handoff Implementation Template
Goal
- Define target outcome and acceptance criteria.
TDD
1. Red 2. Green 3. Refactor
Verification
- lint
- format
- targeted tests