commit fcf1961dfab0ceb568c4219d982743b5144b1820 Author: Zhongwei Li Date: Sat Nov 29 18:45:35 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..fdc5667 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,16 @@ +{ + "name": "claude-agent-lifecycle", + "description": "Persistent agent lifecycle management for Claude Code. Provides 6 agent lifespans (ephemeral, turn, context, session, workflow, project) with automatic disposal via hooks.", + "version": "1.0.0", + "author": { + "name": "Hendrik Geldenhuys", + "url": "https://github.com/hgeldenhuys" + }, + "skills": [ + "./.claude/skills/" + ], + "hooks": { + "Stop": "hooks/lifecycle-manager.ts", + "SessionEnd": "hooks/lifecycle-manager.ts" + } +} \ No newline at end of file diff --git a/.claude/skills/managing-agent-lifecycles/SKILL.md b/.claude/skills/managing-agent-lifecycles/SKILL.md new file mode 100644 index 0000000..dffc73b --- /dev/null +++ b/.claude/skills/managing-agent-lifecycles/SKILL.md @@ -0,0 +1,212 @@ +--- +name: managing-agent-lifecycles +description: Implements persistent agent lifecycle management with 6 lifespans (ephemeral, turn, context, session, workflow, project). Use when creating agents that survive across turns, managing workflow-scoped execution, or needing automatic cleanup at lifecycle boundaries. +--- + +# Managing Agent Lifecycles + +Persistent agent lifecycle management for Claude Code projects. Creates agents with defined lifespans and automatic disposal. + +## When to Use + +- Creating agents that persist beyond a single call +- Managing workflow-scoped agents (story execution, feature development) +- Automatic agent cleanup at lifecycle boundaries (Stop, SessionEnd) +- Building systems with multiple cooperating agents + +## Quick Start + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; + +const registry = new AgentRegistry(); + +// Create session-scoped agent (survives until session ends) +const { agent, isNew } = await registry.create({ + lifespan: 'session', + name: 'my-advisor', + model: 'haiku', +}); + +// Resume later in same session +const advisor = await registry.resume('my-advisor'); +``` + +## The 6 Lifespans + +| Lifespan | Auto-Disposal | Use Case | +|----------|---------------|----------| +| `ephemeral` | Immediately | Fire-and-forget tasks | +| `turn` | Stop event | Per-response helpers | +| `context` | Context reset | Multi-turn conversations | +| `session` | SessionEnd | Knowledge advisors | +| `workflow` | Manual/complete | Story/feature execution | +| `project` | Manual only | Singleton services | + +### Choosing a Lifespan + +``` +How long should the agent live? +├── Just this one call? → ephemeral +├── Until response completes? → turn +├── Until context resets? → context +├── Until session ends? → session +├── Until work unit completes? → workflow +└── Forever (manual disposal)? → project +``` + +## Implementation Checklist + +When implementing agent lifecycle management: + +- [ ] Install package: `bun add claude-agent-lifecycle` +- [ ] Choose appropriate lifespan for each agent type +- [ ] Configure hooks for automatic disposal (Stop, SessionEnd) +- [ ] Use descriptive agent names (e.g., `backend-dev` not `agent1`) +- [ ] Store role and capabilities in metadata +- [ ] Test with debug mode enabled + +## Common Patterns + +### Pattern 1: Session-Scoped Advisor + +For agents persisting throughout a Claude Code session: + +```typescript +const { agent, isNew } = await registry.create({ + lifespan: 'session', + name: 'shadow-advisor', + model: 'haiku', + metadata: { role: 'knowledge-retrieval' }, +}); +``` + +### Pattern 2: Workflow-Scoped Execution + +For agents tied to a unit of work: + +```typescript +// Start executor for story +await registry.startWorkflow({ + lifespan: 'workflow', + workflowId: 'FEAT-001', + name: 'executor', +}); + +// Add specialists +await registry.startWorkflow({ + lifespan: 'workflow', + workflowId: 'FEAT-001', + name: 'backend-dev', +}); + +// Complete when done (disposes all workflow agents) +await registry.completeWorkflow('FEAT-001'); +``` + +### Pattern 3: Turn-Scoped Helper + +For short-lived per-response agents: + +```typescript +const { agent } = await registry.create({ + lifespan: 'turn', + name: 'code-analyzer', +}); +// Automatically disposed when Stop hook fires +``` + +See `patterns.md` for detailed implementations. + +## Hook Integration + +Hooks automatically dispose agents at lifecycle boundaries. + +### hooks/hooks.json + +```json +{ + "Stop": [{ + "hooks": [{ + "type": "command", + "command": "bun \"${CLAUDE_PLUGIN_ROOT}/hooks/lifecycle-manager.ts\"" + }] + }], + "SessionEnd": [{ + "hooks": [{ + "type": "command", + "command": "bun \"${CLAUDE_PLUGIN_ROOT}/hooks/lifecycle-manager.ts\"" + }] + }] +} +``` + +### hooks/lifecycle-manager.ts + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; +import { getHookEvent } from 'claude-hooks-sdk'; + +const event = getHookEvent(); +const registry = new AgentRegistry(); + +if (event.type === 'Stop') { + await registry.disposeByLifespan('turn'); +} + +if (event.type === 'SessionEnd') { + await registry.disposeByScope(event.session.sessionId); +} +``` + +## Storage Structure + +``` +.agent/agents/ +├── session/{session-id}/{agent-name}.json +├── workflow/{workflow-id}/{agent-name}.json +└── project/{agent-name}.json +``` + +## Debug Mode + +Enable for troubleshooting: + +```bash +AGENT_LIFECYCLE_DEBUG=true claude +``` + +Output: +``` +[agent-lifecycle] Created: shadow-advisor (session) +[agent-lifecycle] Stop: Disposed 1 turn-scoped agents +``` + +## API Summary + +| Method | Purpose | +|--------|---------| +| `create(config)` | Create or resume agent | +| `resume(name)` | Resume by name | +| `dispose(id)` | Dispose specific agent | +| `startWorkflow(config)` | Start workflow agent | +| `completeWorkflow(id)` | Complete and dispose workflow | + +See `api-reference.md` for complete API documentation. + +## Installation + +```bash +# As plugin (recommended) +/plugin marketplace add hgeldenhuys/claude-agent-lifecycle +/plugin install claude-agent-lifecycle + +# As package +bun add claude-agent-lifecycle +``` + +## Related Files + +- `api-reference.md` - Complete API documentation +- `patterns.md` - Detailed pattern implementations +- `examples.md` - Real-world usage examples diff --git a/.claude/skills/managing-agent-lifecycles/api-reference.md b/.claude/skills/managing-agent-lifecycles/api-reference.md new file mode 100644 index 0000000..b3c5288 --- /dev/null +++ b/.claude/skills/managing-agent-lifecycles/api-reference.md @@ -0,0 +1,270 @@ +# API Reference + +Complete API documentation for claude-agent-lifecycle. + +## Table of Contents + +1. [AgentRegistry](#agentregistry) +2. [Methods](#methods) +3. [Types](#types) +4. [Storage Backends](#storage-backends) + +--- + +## AgentRegistry + +Main class for managing agent lifecycles. + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; + +const registry = new AgentRegistry(options?: RegistryOptions); +``` + +### RegistryOptions + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `storagePath` | `string` | `.agent/agents` | Base path for file storage | +| `debug` | `boolean` | `false` | Enable debug logging | + +--- + +## Methods + +### create(config) + +Creates a new agent or resumes an existing one. + +```typescript +const { agent, isNew } = await registry.create({ + lifespan: 'session', + name: 'my-agent', + sessionId?: string, // Auto-detected if not provided + model?: string, // 'haiku', 'sonnet', 'opus' + metadata?: Record, +}); +``` + +**Returns**: `{ agent: Agent, isNew: boolean }` + +**Behavior**: +- If agent with same name exists in scope, resumes it +- If not, creates new agent +- `isNew` indicates whether agent was created or resumed + +### resume(name, scope?) + +Resumes an existing agent by name. + +```typescript +const agent = await registry.resume('my-agent'); +const agent = await registry.resume('my-agent', 'session-123'); +``` + +**Returns**: `Agent | null` + +**Throws**: If agent not found and no scope provided + +### dispose(agentId) + +Disposes a specific agent by ID. + +```typescript +await registry.dispose('agent-uuid-here'); +``` + +**Returns**: `void` + +### disposeByLifespan(lifespan) + +Disposes all agents of a specific lifespan type. + +```typescript +const count = await registry.disposeByLifespan('turn'); +``` + +**Returns**: `number` - Count of disposed agents + +### disposeByScope(scope) + +Disposes all agents in a specific scope. + +```typescript +const count = await registry.disposeByScope('session-123'); +``` + +**Returns**: `number` - Count of disposed agents + +### list(filter?) + +Lists agents matching optional filter criteria. + +```typescript +// List all agents +const all = await registry.list(); + +// Filter by lifespan +const sessionAgents = await registry.list({ lifespan: 'session' }); + +// Filter by scope +const workflowAgents = await registry.list({ scope: 'FEAT-001' }); +``` + +**Returns**: `Agent[]` + +### startWorkflow(config) + +Starts a workflow-scoped agent. + +```typescript +const agent = await registry.startWorkflow({ + lifespan: 'workflow', + workflowId: 'FEAT-001', + name: 'executor', + workflowType?: string, // e.g., 'loom-story' + model?: string, + metadata?: Record, +}); +``` + +**Returns**: `Agent` + +### completeWorkflow(workflowId) + +Completes a workflow and disposes all its agents. + +```typescript +const disposed = await registry.completeWorkflow('FEAT-001'); +``` + +**Returns**: `number` - Count of disposed agents + +### getWorkflowAgents(workflowId) + +Gets all agents for a specific workflow. + +```typescript +const agents = await registry.getWorkflowAgents('FEAT-001'); +``` + +**Returns**: `Agent[]` + +--- + +## Types + +### Agent + +```typescript +interface Agent { + agentId: string; // Unique identifier (UUID) + name: string; // Human-readable name + lifespan: Lifespan; // One of 6 lifespans + scope: string; // Scope identifier + model?: string; // Model preference + turnCount: number; // Interaction count + metadata?: Record; + createdAt: string; // ISO timestamp + lastActiveAt: string; // ISO timestamp +} +``` + +### Lifespan + +```typescript +type Lifespan = + | 'ephemeral' // Single use + | 'turn' // Until Stop event + | 'context' // Until context reset + | 'session' // Until SessionEnd event + | 'workflow' // Until workflow completes + | 'project'; // Until manually disposed +``` + +### CreateConfig + +```typescript +interface CreateConfig { + lifespan: Lifespan; + name: string; + sessionId?: string; + model?: string; + metadata?: Record; +} +``` + +### WorkflowConfig + +```typescript +interface WorkflowConfig { + lifespan: 'workflow'; + workflowId: string; + name: string; + workflowType?: string; + model?: string; + metadata?: Record; +} +``` + +### ListFilter + +```typescript +interface ListFilter { + lifespan?: Lifespan; + scope?: string; + name?: string; +} +``` + +--- + +## Storage Backends + +### MemoryStorage + +Used for ephemeral, turn, and context lifespans. + +- Data stored in memory +- Lost on process restart +- Fast access, no I/O + +### FileStorage + +Used for session, workflow, and project lifespans. + +- Data persisted to JSON files +- Survives process restarts +- Structure: + +``` +.agent/agents/ +├── lifecycle.log # Debug logs (when enabled) +├── session/ +│ └── {session-id}/ +│ └── {agent-name}.json +├── workflow/ +│ └── {workflow-id}/ +│ └── {agent-name}.json +└── project/ + └── {agent-name}.json +``` + +### Agent JSON Format + +```json +{ + "agentId": "550e8400-e29b-41d4-a716-446655440000", + "name": "shadow-advisor", + "lifespan": "session", + "scope": "abc-123", + "model": "haiku", + "turnCount": 5, + "metadata": { + "role": "knowledge-retrieval", + "preloadedKnowledge": ["patterns", "pain-points"] + }, + "createdAt": "2025-01-15T10:30:00Z", + "lastActiveAt": "2025-01-15T11:45:00Z" +} +``` diff --git a/.claude/skills/managing-agent-lifecycles/examples.md b/.claude/skills/managing-agent-lifecycles/examples.md new file mode 100644 index 0000000..0de1d99 --- /dev/null +++ b/.claude/skills/managing-agent-lifecycles/examples.md @@ -0,0 +1,372 @@ +# Real-World Examples + +Practical examples of agent lifecycle management in production systems. + +## Table of Contents + +1. [Shadow Advisor (claude-weave)](#example-1-shadow-advisor) +2. [Story Execution (claude-loom)](#example-2-story-execution) +3. [Custom Hook Handler](#example-3-custom-hook-handler) +4. [Debug Logging Setup](#example-4-debug-logging-setup) + +--- + +## Example 1: Shadow Advisor + +From claude-weave: A session-scoped knowledge retrieval agent. + +### agents/shadow-advisor-lifecycle.ts + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; +import { getSessionId } from 'claude-hooks-sdk'; + +const SHADOW_ADVISOR_CONFIG = { + lifespan: 'session' as const, + name: 'shadow-advisor', + model: 'haiku', + metadata: { + role: 'knowledge-retrieval', + preloadedKnowledge: [ + 'qualia', // Pain points, solutions + 'epistemology', // Patterns, validations + 'praxeology', // WoW patterns + ], + capabilities: [ + 'query-11d-knowledge', + 'synthesize-across-dimensions', + 'recommend-patterns', + ], + }, +}; + +export async function getShadowAdvisor() { + const registry = new AgentRegistry(); + const sessionId = getSessionId(); + + const { agent, isNew } = await registry.create({ + ...SHADOW_ADVISOR_CONFIG, + sessionId, + }); + + return { agent, isNew }; +} + +export async function resumeShadowAdvisor() { + const registry = new AgentRegistry(); + return await registry.resume('shadow-advisor'); +} + +export async function disposeShadowAdvisor() { + const registry = new AgentRegistry(); + const agent = await registry.resume('shadow-advisor'); + if (agent) { + await registry.dispose(agent.agentId); + return true; + } + return false; +} +``` + +### Usage in Weave Hooks + +```typescript +// hooks/weave-hooks.ts +import { getHookEvent } from 'claude-hooks-sdk'; +import { getShadowAdvisor } from '../agents/shadow-advisor-lifecycle'; + +async function handleSessionStart() { + const { agent, isNew } = await getShadowAdvisor(); + + if (isNew) { + console.log('Shadow Advisor created for session'); + // Load 11D knowledge into agent context... + } else { + console.log(`Shadow Advisor resumed (turn ${agent.turnCount})`); + } +} +``` + +--- + +## Example 2: Story Execution + +From claude-loom: Workflow-scoped agents for story execution. + +### agents/story-lifecycle.ts + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; + +interface StoryConfig { + storyId: string; + title: string; + acceptanceCriteria: string[]; +} + +export class StoryLifecycle { + private registry: AgentRegistry; + private storyId: string; + + constructor(storyId: string) { + this.registry = new AgentRegistry(); + this.storyId = storyId; + } + + async start(config: StoryConfig) { + // Create main executor + const executor = await this.registry.startWorkflow({ + lifespan: 'workflow', + workflowId: this.storyId, + name: 'story-executor', + workflowType: 'loom-story', + model: 'sonnet', + metadata: { + title: config.title, + acceptanceCriteria: config.acceptanceCriteria, + status: 'in-progress', + startedAt: new Date().toISOString(), + }, + }); + + return executor; + } + + async addSpecialist( + role: 'backend-dev' | 'frontend-dev' | 'backend-qa' | 'frontend-qa' + ) { + const modelMap = { + 'backend-dev': 'sonnet', + 'frontend-dev': 'sonnet', + 'backend-qa': 'haiku', + 'frontend-qa': 'haiku', + }; + + return await this.registry.startWorkflow({ + lifespan: 'workflow', + workflowId: this.storyId, + name: role, + model: modelMap[role], + metadata: { + role, + assignedAt: new Date().toISOString(), + }, + }); + } + + async getActiveAgents() { + return await this.registry.getWorkflowAgents(this.storyId); + } + + async complete(outcome: 'success' | 'failed' | 'cancelled') { + const agents = await this.getActiveAgents(); + + // Log completion stats + console.log(`Story ${this.storyId} ${outcome}`); + console.log(`Agents used: ${agents.length}`); + for (const agent of agents) { + console.log(` - ${agent.name}: ${agent.turnCount} turns`); + } + + // Dispose all workflow agents + return await this.registry.completeWorkflow(this.storyId); + } +} + +// Usage +async function executeStory() { + const story = new StoryLifecycle('FEAT-001'); + + await story.start({ + storyId: 'FEAT-001', + title: 'Add user authentication', + acceptanceCriteria: [ + 'Users can sign up with email', + 'Users can log in', + 'Sessions persist across page reloads', + ], + }); + + await story.addSpecialist('backend-dev'); + await story.addSpecialist('backend-qa'); + + // ... execute story tasks ... + + await story.complete('success'); +} +``` + +--- + +## Example 3: Custom Hook Handler + +Complete hook handler managing multiple agent types. + +### hooks/lifecycle-manager.ts + +```typescript +#!/usr/bin/env bun +import { AgentRegistry } from 'claude-agent-lifecycle'; +import { getHookEvent, getSessionId } from 'claude-hooks-sdk'; + +const DEBUG = process.env.AGENT_LIFECYCLE_DEBUG === 'true' + || process.argv.includes('--debug'); + +function log(message: string) { + if (DEBUG) { + console.log(`[agent-lifecycle] ${message}`); + } +} + +async function main() { + const event = getHookEvent(); + const registry = new AgentRegistry({ debug: DEBUG }); + + switch (event.type) { + case 'SessionStart': { + const sessionId = getSessionId(); + log(`SessionStart: ${sessionId} (source: ${event.source})`); + + // List active agents + const agents = await registry.list(); + if (agents.length > 0) { + log(`Active agents: ${agents.map(a => `${a.name}(${a.lifespan})`).join(', ')}`); + } + break; + } + + case 'Stop': { + // Dispose turn-scoped agents + const turnCount = await registry.disposeByLifespan('turn'); + if (turnCount > 0) { + log(`Stop: Disposed ${turnCount} turn-scoped agents`); + } + break; + } + + case 'SessionEnd': { + const sessionId = event.session?.sessionId; + if (sessionId) { + // Dispose session-scoped agents + const sessionCount = await registry.disposeByScope(sessionId); + log(`SessionEnd: Disposed ${sessionCount} agents for session ${sessionId}`); + } + break; + } + } +} + +main().catch(error => { + console.error('[agent-lifecycle] Error:', error.message); + process.exit(1); +}); +``` + +--- + +## Example 4: Debug Logging Setup + +Setting up comprehensive debug logging. + +### Enable Debug Mode + +```bash +# Option 1: Environment variable +export AGENT_LIFECYCLE_DEBUG=true +claude + +# Option 2: Per-session +AGENT_LIFECYCLE_DEBUG=true claude + +# Option 3: In hook command +# hooks/hooks.json +{ + "Stop": [{ + "hooks": [{ + "type": "command", + "command": "bun hooks/lifecycle-manager.ts --debug" + }] + }] +} +``` + +### Custom Debug Logger + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; +import * as fs from 'fs'; + +class DebugRegistry extends AgentRegistry { + private logPath: string; + + constructor(logPath = '.agent/agents/debug.log') { + super({ debug: true }); + this.logPath = logPath; + } + + private writeLog(entry: object) { + const line = JSON.stringify({ + timestamp: new Date().toISOString(), + ...entry, + }); + fs.appendFileSync(this.logPath, line + '\n'); + } + + async create(config: any) { + const result = await super.create(config); + this.writeLog({ + event: result.isNew ? 'agent:created' : 'agent:resumed', + agentId: result.agent.agentId, + name: result.agent.name, + lifespan: result.agent.lifespan, + }); + return result; + } + + async dispose(agentId: string) { + this.writeLog({ + event: 'agent:disposed', + agentId, + }); + return super.dispose(agentId); + } + + async completeWorkflow(workflowId: string) { + const agents = await this.getWorkflowAgents(workflowId); + this.writeLog({ + event: 'workflow:completed', + workflowId, + agentCount: agents.length, + agents: agents.map(a => a.name), + }); + return super.completeWorkflow(workflowId); + } +} + +// Usage +const registry = new DebugRegistry(); +``` + +### Debug Log Output + +```jsonl +{"timestamp":"2025-01-15T10:30:00Z","event":"agent:created","agentId":"abc-123","name":"shadow-advisor","lifespan":"session"} +{"timestamp":"2025-01-15T10:30:05Z","event":"agent:created","agentId":"def-456","name":"backend-dev","lifespan":"workflow"} +{"timestamp":"2025-01-15T10:45:00Z","event":"workflow:completed","workflowId":"FEAT-001","agentCount":3,"agents":["executor","backend-dev","backend-qa"]} +{"timestamp":"2025-01-15T11:00:00Z","event":"agent:disposed","agentId":"abc-123"} +``` + +--- + +## Integration Checklist + +When integrating agent lifecycle into your project: + +- [ ] Install: `bun add claude-agent-lifecycle` +- [ ] Create hooks directory with `hooks.json` +- [ ] Implement `lifecycle-manager.ts` hook handler +- [ ] Define agent configurations (lifespan, name, metadata) +- [ ] Add debug logging for development +- [ ] Test all lifecycle events (SessionStart, Stop, SessionEnd) +- [ ] Verify automatic disposal at boundaries +- [ ] Document agent roles and lifespans diff --git a/.claude/skills/managing-agent-lifecycles/patterns.md b/.claude/skills/managing-agent-lifecycles/patterns.md new file mode 100644 index 0000000..e8f4dd4 --- /dev/null +++ b/.claude/skills/managing-agent-lifecycles/patterns.md @@ -0,0 +1,356 @@ +# Agent Lifecycle Patterns + +Detailed implementations of common agent lifecycle patterns. + +## Table of Contents + +1. [Session-Scoped Advisor](#pattern-1-session-scoped-advisor) +2. [Workflow-Scoped Execution](#pattern-2-workflow-scoped-execution) +3. [Turn-Scoped Helper](#pattern-3-turn-scoped-helper) +4. [Project Singleton](#pattern-4-project-singleton) +5. [Multi-Agent Orchestration](#pattern-5-multi-agent-orchestration) + +--- + +## Pattern 1: Session-Scoped Advisor + +Agents that persist throughout a Claude Code session, ideal for knowledge retrieval and advisory roles. + +### Use Cases + +- Knowledge advisors (Shadow Advisor, Librarian) +- Context-aware assistants +- Session-wide configuration managers + +### Implementation + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; + +export async function getSessionAdvisor(role: string) { + const registry = new AgentRegistry(); + + const { agent, isNew } = await registry.create({ + lifespan: 'session', + name: `${role}-advisor`, + model: 'haiku', + metadata: { + role, + capabilities: ['knowledge-retrieval', 'pattern-matching'], + preloadedAt: new Date().toISOString(), + }, + }); + + if (isNew) { + // First creation - load initial context + console.log(`Created new ${role} advisor`); + // Initialize with domain knowledge... + } else { + // Resumed - increment interaction count + console.log(`Resumed ${role} advisor (turn ${agent.turnCount})`); + } + + return agent; +} + +// Usage +const shadow = await getSessionAdvisor('shadow'); +const librarian = await getSessionAdvisor('librarian'); +``` + +### Hook Integration + +Automatically disposed at SessionEnd: + +```typescript +// hooks/session-cleanup.ts +import { AgentRegistry } from 'claude-agent-lifecycle'; +import { getHookEvent } from 'claude-hooks-sdk'; + +const event = getHookEvent(); + +if (event.type === 'SessionEnd') { + const registry = new AgentRegistry(); + const count = await registry.disposeByScope(event.session.sessionId); + console.log(`Disposed ${count} session agents`); +} +``` + +--- + +## Pattern 2: Workflow-Scoped Execution + +Agents tied to a bounded unit of work (story, feature, task). Multiple specialists collaborate on a shared workflow. + +### Use Cases + +- Story execution (Loom) +- Feature development +- Multi-step task orchestration + +### Implementation + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; + +export class WorkflowOrchestrator { + private registry: AgentRegistry; + private workflowId: string; + + constructor(workflowId: string) { + this.registry = new AgentRegistry(); + this.workflowId = workflowId; + } + + async start() { + // Create main executor + return await this.registry.startWorkflow({ + lifespan: 'workflow', + workflowId: this.workflowId, + name: 'executor', + workflowType: 'story-execution', + model: 'sonnet', + metadata: { + startedAt: new Date().toISOString(), + status: 'in-progress', + }, + }); + } + + async addSpecialist(role: string) { + const validRoles = [ + 'backend-dev', + 'frontend-dev', + 'backend-qa', + 'frontend-qa', + 'cli-dev', + ]; + + if (!validRoles.includes(role)) { + throw new Error(`Invalid role: ${role}`); + } + + return await this.registry.startWorkflow({ + lifespan: 'workflow', + workflowId: this.workflowId, + name: role, + model: role.includes('qa') ? 'haiku' : 'sonnet', + metadata: { role, assignedAt: new Date().toISOString() }, + }); + } + + async getStatus() { + const agents = await this.registry.getWorkflowAgents(this.workflowId); + return { + workflowId: this.workflowId, + agentCount: agents.length, + agents: agents.map(a => ({ + name: a.name, + turnCount: a.turnCount, + lastActive: a.lastActiveAt, + })), + }; + } + + async complete() { + const count = await this.registry.completeWorkflow(this.workflowId); + console.log(`Completed workflow ${this.workflowId}, disposed ${count} agents`); + return count; + } +} + +// Usage +const orchestrator = new WorkflowOrchestrator('FEAT-001'); +await orchestrator.start(); +await orchestrator.addSpecialist('backend-dev'); +await orchestrator.addSpecialist('backend-qa'); + +// ... execute work ... + +await orchestrator.complete(); +``` + +--- + +## Pattern 3: Turn-Scoped Helper + +Short-lived agents that assist with a single response cycle. Automatically disposed when Stop hook fires. + +### Use Cases + +- Code analysis helpers +- One-off data processors +- Temporary computation agents + +### Implementation + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; + +export async function createTurnHelper(task: string) { + const registry = new AgentRegistry(); + + const { agent } = await registry.create({ + lifespan: 'turn', + name: `helper-${task}`, + model: 'haiku', + metadata: { + task, + createdFor: 'single-response', + }, + }); + + return agent; +} + +// Usage - agent disposed automatically at Stop +const analyzer = await createTurnHelper('code-analysis'); +const formatter = await createTurnHelper('output-formatting'); +``` + +### Hook Integration + +```typescript +// hooks/turn-cleanup.ts +import { AgentRegistry } from 'claude-agent-lifecycle'; +import { getHookEvent } from 'claude-hooks-sdk'; + +const event = getHookEvent(); + +if (event.type === 'Stop') { + const registry = new AgentRegistry(); + const count = await registry.disposeByLifespan('turn'); + if (count > 0) { + console.log(`Disposed ${count} turn-scoped agents`); + } +} +``` + +--- + +## Pattern 4: Project Singleton + +Agents that persist indefinitely, shared across all sessions. Requires manual disposal. + +### Use Cases + +- Project configuration managers +- Shared knowledge bases +- Cross-session state holders + +### Implementation + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; + +export async function getProjectSingleton(name: string) { + const registry = new AgentRegistry(); + + const { agent, isNew } = await registry.create({ + lifespan: 'project', + name, + metadata: { + singleton: true, + initVersion: '1.0.0', + }, + }); + + if (isNew) { + console.log(`Created project singleton: ${name}`); + } else { + console.log(`Using existing singleton: ${name} (${agent.turnCount} turns)`); + } + + return agent; +} + +// Manual disposal when needed +export async function disposeProjectSingleton(name: string) { + const registry = new AgentRegistry(); + const agent = await registry.resume(name); + if (agent) { + await registry.dispose(agent.agentId); + console.log(`Disposed project singleton: ${name}`); + } +} +``` + +--- + +## Pattern 5: Multi-Agent Orchestration + +Coordinating multiple agents across different lifespans. + +### Implementation + +```typescript +import { AgentRegistry } from 'claude-agent-lifecycle'; + +export class MultiAgentSystem { + private registry: AgentRegistry; + + constructor() { + this.registry = new AgentRegistry({ debug: true }); + } + + async initialize(sessionId: string, workflowId: string) { + // Session-scoped: knowledge advisors + await this.registry.create({ + lifespan: 'session', + name: 'shadow-advisor', + sessionId, + model: 'haiku', + }); + + await this.registry.create({ + lifespan: 'session', + name: 'librarian', + sessionId, + model: 'haiku', + }); + + // Workflow-scoped: execution agents + await this.registry.startWorkflow({ + lifespan: 'workflow', + workflowId, + name: 'executor', + model: 'sonnet', + }); + + // Turn-scoped: temporary helpers created as needed + // (created per-request, not at initialization) + } + + async getSystemStatus() { + const agents = await this.registry.list(); + + return { + total: agents.length, + byLifespan: { + session: agents.filter(a => a.lifespan === 'session').length, + workflow: agents.filter(a => a.lifespan === 'workflow').length, + turn: agents.filter(a => a.lifespan === 'turn').length, + project: agents.filter(a => a.lifespan === 'project').length, + }, + agents: agents.map(a => ({ + name: a.name, + lifespan: a.lifespan, + scope: a.scope, + turns: a.turnCount, + })), + }; + } +} +``` + +--- + +## Best Practices Summary + +| Pattern | Lifespan | Model | Disposal | +|---------|----------|-------|----------| +| Knowledge Advisor | session | haiku | SessionEnd hook | +| Workflow Executor | workflow | sonnet | completeWorkflow() | +| Workflow Specialist | workflow | sonnet/haiku | completeWorkflow() | +| Turn Helper | turn | haiku | Stop hook | +| Project Singleton | project | varies | Manual | diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ef2a66 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# claude-agent-lifecycle + +Persistent agent lifecycle management for Claude Code. Provides 6 agent lifespans (ephemeral, turn, context, session, workflow, project) with automatic disposal via hooks. diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..a332a2f --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,57 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:hgeldenhuys/claude-agent-lifecycle:", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "e52859c64f43f3adcef60fe99cd72a6608c55146", + "treeHash": "92549440c37c3cb2ce5cf5f7780b4a6f0b5e1b43720e6ada92266117de29d829", + "generatedAt": "2025-11-28T10:17:25.928386Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "claude-agent-lifecycle", + "description": "Persistent agent lifecycle management for Claude Code. Provides 6 agent lifespans (ephemeral, turn, context, session, workflow, project) with automatic disposal via hooks.", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "1e317d0ac04c5ae7eb5a95db05b52299b3764d7c3babf7c9c51d5221f3834794" + }, + { + "path": ".claude/skills/managing-agent-lifecycles/examples.md", + "sha256": "7e03cd6f07583e953e41e20bcc263bcb0c915a524a912ae5b4412753241d13ce" + }, + { + "path": ".claude/skills/managing-agent-lifecycles/patterns.md", + "sha256": "5480f50288cdcd684ae20a2b582b8cd8e7c0128e6be1e38740ec5f91bceabaca" + }, + { + "path": ".claude/skills/managing-agent-lifecycles/SKILL.md", + "sha256": "defd9e354a411cc475ef01bd31e048386f916490c2ec4c87cf482f13bbcd03c8" + }, + { + "path": ".claude/skills/managing-agent-lifecycles/api-reference.md", + "sha256": "2dc50f3cc8520c8ab7cb80d7d4ac5cf42d151c87c8cc0d8c494fd1720793f6db" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "fa8d24e52608648ac23cffabb26471380aa872f9909a7ae605de6458a663e97c" + } + ], + "dirSha256": "92549440c37c3cb2ce5cf5f7780b4a6f0b5e1b43720e6ada92266117de29d829" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file