Files
gh-jezweb-claude-skills-ski…/references/thread-lifecycle.md
2025-11-30 08:25:15 +08:00

272 lines
5.8 KiB
Markdown

# Thread Lifecycle Management
Complete guide to managing threads effectively to avoid errors and optimize performance.
---
## Thread States
A thread progresses through these states based on run activity:
```
Idle (no active runs)
Active (run in progress)
Requires Action (function calling)
Completed / Failed / Cancelled
Idle (ready for next run)
```
---
## Common Patterns
### Pattern 1: One Thread Per User
**Use Case**: Chatbots, support assistants
```typescript
// In-memory cache (use database in production)
const userThreads = new Map<string, string>();
async function getOrCreateUserThread(userId: string): Promise<string> {
let threadId = userThreads.get(userId);
if (!threadId) {
const thread = await openai.beta.threads.create({
metadata: { user_id: userId },
});
threadId = thread.id;
userThreads.set(userId, threadId);
}
return threadId;
}
```
**Benefits**:
- Conversation continuity
- Automatic history management
- Simple architecture
**Drawbacks**:
- Long threads consume memory
- 100k message limit eventually
### Pattern 2: Session-Based Threads
**Use Case**: Temporary conversations
```typescript
async function createSessionThread(userId: string, sessionId: string) {
return await openai.beta.threads.create({
metadata: {
user_id: userId,
session_id: sessionId,
expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
},
});
}
```
**Benefits**:
- Clear boundaries
- Easy cleanup
- Fresh context
### Pattern 3: Topic-Based Threads
**Use Case**: Multi-topic conversations
```typescript
async function getTopicThread(userId: string, topic: string) {
const key = `${userId}:${topic}`;
// Separate threads for different topics
return await getOrCreateThread(key);
}
```
---
## Active Run Management
### Check for Active Runs
```typescript
async function hasActiveRun(threadId: string): Promise<boolean> {
const runs = await openai.beta.threads.runs.list(threadId, {
limit: 1,
order: 'desc',
});
const latestRun = runs.data[0];
return latestRun && ['queued', 'in_progress', 'cancelling'].includes(latestRun.status);
}
```
### Safe Run Creation
```typescript
async function createRunSafely(
threadId: string,
assistantId: string,
cancelIfActive = true
) {
// Check for active runs
const runs = await openai.beta.threads.runs.list(threadId, {
limit: 1,
order: 'desc',
});
const latestRun = runs.data[0];
if (latestRun && ['queued', 'in_progress'].includes(latestRun.status)) {
if (cancelIfActive) {
await openai.beta.threads.runs.cancel(threadId, latestRun.id);
// Wait for cancellation
while (latestRun.status !== 'cancelled') {
await new Promise(r => setTimeout(r, 500));
latestRun = await openai.beta.threads.runs.retrieve(threadId, latestRun.id);
}
} else {
throw new Error('Thread has active run');
}
}
return await openai.beta.threads.runs.create(threadId, {
assistant_id: assistantId,
});
}
```
---
## Cleanup Strategies
### Time-Based Cleanup
```typescript
async function cleanupOldThreads(maxAgeHours: number = 24) {
// Query your database for old threads
const oldThreads = await db.getThreadsOlderThan(maxAgeHours);
for (const threadId of oldThreads) {
await openai.beta.threads.del(threadId);
await db.deleteThreadRecord(threadId);
}
}
```
### Message Count-Based
```typescript
async function archiveIfTooLong(threadId: string, maxMessages: number = 1000) {
const messages = await openai.beta.threads.messages.list(threadId);
if (messages.data.length >= maxMessages) {
// Archive to database
await db.archiveThread(threadId, messages.data);
// Create new thread
return await openai.beta.threads.create({
metadata: { previous_thread: threadId },
});
}
return threadId;
}
```
### Safe Deletion
```typescript
async function safeDeleteThread(threadId: string) {
// Cancel all active runs first
const runs = await openai.beta.threads.runs.list(threadId);
for (const run of runs.data) {
if (['queued', 'in_progress'].includes(run.status)) {
await openai.beta.threads.runs.cancel(threadId, run.id);
}
}
// Wait a moment for cancellations
await new Promise(r => setTimeout(r, 1000));
// Delete thread
await openai.beta.threads.del(threadId);
}
```
---
## Error Handling
### Concurrent Run Prevention
```typescript
class ThreadManager {
private locks = new Map<string, Promise<any>>();
async executeRun(threadId: string, assistantId: string) {
// Wait if another run is in progress
if (this.locks.has(threadId)) {
await this.locks.get(threadId);
}
// Create lock
const runPromise = this._runAssistant(threadId, assistantId);
this.locks.set(threadId, runPromise);
try {
return await runPromise;
} finally {
this.locks.delete(threadId);
}
}
private async _runAssistant(threadId: string, assistantId: string) {
// Run logic here
}
}
```
---
## Best Practices
1. **Always check for active runs** before creating new ones
2. **Use metadata** to track ownership and expiration
3. **Implement cleanup** to manage costs
4. **Set reasonable limits** (message count, age)
5. **Handle errors gracefully** (active run conflicts)
6. **Archive old conversations** before deletion
7. **Use locks** for concurrent access
---
## Monitoring
```typescript
async function getThreadStats(threadId: string) {
const thread = await openai.beta.threads.retrieve(threadId);
const messages = await openai.beta.threads.messages.list(threadId);
const runs = await openai.beta.threads.runs.list(threadId);
return {
threadId: thread.id,
createdAt: new Date(thread.created_at * 1000),
messageCount: messages.data.length,
runCount: runs.data.length,
lastRun: runs.data[0],
metadata: thread.metadata,
};
}
```
---
**Last Updated**: 2025-10-25