Initial commit
This commit is contained in:
16
.claude-plugin/plugin.json
Normal file
16
.claude-plugin/plugin.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
212
.claude/skills/managing-agent-lifecycles/SKILL.md
Normal file
212
.claude/skills/managing-agent-lifecycles/SKILL.md
Normal file
@@ -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
|
||||
270
.claude/skills/managing-agent-lifecycles/api-reference.md
Normal file
270
.claude/skills/managing-agent-lifecycles/api-reference.md
Normal file
@@ -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<string, unknown>,
|
||||
});
|
||||
```
|
||||
|
||||
**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<string, unknown>,
|
||||
});
|
||||
```
|
||||
|
||||
**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<string, unknown>;
|
||||
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<string, unknown>;
|
||||
}
|
||||
```
|
||||
|
||||
### WorkflowConfig
|
||||
|
||||
```typescript
|
||||
interface WorkflowConfig {
|
||||
lifespan: 'workflow';
|
||||
workflowId: string;
|
||||
name: string;
|
||||
workflowType?: string;
|
||||
model?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
```
|
||||
|
||||
### 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"
|
||||
}
|
||||
```
|
||||
372
.claude/skills/managing-agent-lifecycles/examples.md
Normal file
372
.claude/skills/managing-agent-lifecycles/examples.md
Normal file
@@ -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
|
||||
356
.claude/skills/managing-agent-lifecycles/patterns.md
Normal file
356
.claude/skills/managing-agent-lifecycles/patterns.md
Normal file
@@ -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 |
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -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.
|
||||
57
plugin.lock.json
Normal file
57
plugin.lock.json
Normal file
@@ -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": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user