원클릭으로
live-agent-lifecycle
Live agent registration, workspace isolation, termination, and eviction workflow.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Live agent registration, workspace isolation, termination, and eviction workflow.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Iterative multi-round codebase audit with diminishing-returns detection. Run 5-20+ rounds, each focusing on one specific area. Built from 19 rounds of dogfooding pi-crew on itself.
Pi TUI crew widget data sources, display priority, and rendering performance.
Multi-phase orchestration for planners and executors.
Spawn 3 adversarial subagents (Skeptic, Pragmatist, Critic) to evaluate a decision, architecture choice, or plan. Anti-anchoring: each role receives ONLY the question, not conversation history. Aggregates votes into consensus recommendation with dissent tracking. Use when facing critical decisions, architecture choices, security tradeoffs, or plan reviews where single-perspective analysis is insufficient.
Background worker, heartbeat, stale-run, crash-recovery, and deadletter workflow. Use when debugging stuck/dead workers or changing async run reliability.
Child Pi worker spawning, lifecycle callbacks, and failure modes.
| name | live-agent-lifecycle |
| description | Live agent registration, workspace isolation, termination, and eviction workflow. |
| origin | pi-crew |
| triggers | ["register agent","terminate agent","evict stale","ghost agent","workspace isolation"] |
Live agents are real-time, in-memory worker sessions managed by LiveAgentManager (src/runtime/live-agent-manager.ts). They are distinct from CrewAgentRecord files on disk — live agents provide real-time activity (tool names, response text, turn count) while agent records are durable snapshots.
LiveAgentHandle is the core data structure:
interface LiveAgentHandle {
agentId: string; // unique per run
taskId: string; // maps to task
runId: string; // run this agent belongs to
workspaceId: string; // manifest.cwd — workspace boundary
role?: string;
agent?: string;
modelName?: string;
session: LiveSessionHandle; // steer/prompt/abort/dispose
status: CrewAgentRecord["status"];
pendingSteers: string[];
pendingFollowUps: string[];
pendingMessages: IrcMessage[];
activity: LiveAgentActivity; // real-time tracking
createdAt: string;
updatedAt: string;
}
The in-memory liveAgents Map stores all active handles. It is never persisted — on Pi restart, the Map is empty and agents are re-created from agent records.
registerLiveAgent(input, eventLogFn?, eventsPath?) is called when a live session worker starts. It:
liveAgents Maplive_agent.registered event to events.jsonlKey caller sites:
live-session-runtime.ts — when a live session agent startslive-executor.ts — when spawning a live taskworkspaceId: string field is the workspace boundary. Set to manifest.cwd at registration time.
Why it matters: When Pi has multiple workspace folders open, agents from workspace A must not be visible or controllable from workspace B. Every handle carries its origin workspace.
Enforcement in api.ts:
listActiveLiveAgentsByWorkspace(workspaceId) — filters by workspacelive.workspaceId !== manifest.cwd → reject with errorlistLiveAgentsByWorkspace(manifest.cwd) so each workspace only sees its own agentsEnforcement in live-session-runtime.ts:
workspaceId from TeamContext.workspaceIdLiveAgentActivity provides real-time data without reading disk:
interface LiveAgentActivity {
activeTools: Map<string, string>; // toolName → description
toolUses: number; // total invocations
turnCount: number;
maxTurns?: number;
responseText: string; // last 200 chars
compactionCount: number;
startedAtMs: number;
completedAtMs: number; // 0 = still running
modelName?: string;
}
Tracking functions (called from live-executor):
trackLiveAgentToolStart(agentId, toolName) — adds tool to activeToolstrackLiveAgentToolEnd(agentId, toolName) — removes tool from activeToolstrackLiveAgentTurnEnd(agentId, compaction?) — increments turn, clears toolstrackLiveAgentResponseText(agentId, text) — stores last 200 charsmarkLiveAgentCompleted(agentId) — sets completedAtMsterminateLiveAgent(agentIdOrTaskId, status?, eventLogFn?, eventsPath?) is the canonical termination path:
live_agent.terminated event to events.jsonlsession.abort() to stop the childsession.dispose() to clean upliveAgents MapTermination call sites (4 total):
| Location | When |
|---|---|
team-runner.ts (run complete) | All agents terminated when run succeeds or fails |
team-runner.ts (task complete) | Per-task termination when terminateOnTaskComplete=true |
background-runner.ts (catch) | Termination in finally block after background run |
cancel.ts | Termination when user cancels a run |
respond.ts | Termination when responding to waiting tasks |
crash-recovery.ts (purgeStale) | Termination when cleaning up orphaned runs |
crash-recovery.ts (reconcile) | Termination when reconciling stale runs |
terminateLiveAgentsForRun(runId, status?, eventLogFn?, eventsPath?) terminates all agents for a run in parallel.
Stale handles are handles whose status is terminal (not running/queued/waiting) and older than 10 minutes. evictStaleLiveAgentHandles(now?) removes them:
const STALE_HANDLE_MS = 10 * 60 * 1000;
// Only evict terminal-status handles
if (handle.status !== "running" && handle.status !== "queued" && handle.status !== "waiting") {
const age = now - new Date(handle.updatedAt).getTime();
if (age > STALE_HANDLE_MS) {
liveAgents.delete(agentId);
safeDisposeLiveSession(handle);
}
}
Triggered on every widget refresh in crew-widget.ts:
evictStaleLiveAgentHandles(); // called at start of activeWidgetRuns()
This prevents the Map from growing indefinitely with completed agents.
On task completion, upsertCrewAgent(manifest, recordFromTask(manifest, task, "live-session")) is called to persist the final status to disk (agents.json, agents/<id>/status.json). This ensures the widget sees the correct status even after the live agent handle is evicted.
The sync chain:
task.completed → upsertCrewAgent → agents.json updated
→ live_agent.terminated event logged
→ (later) evictStaleLiveAgentHandles → handle removed from Map
Before terminating or evicting live agents, verify:
If ANY answer is NO → Stop. Verify lifecycle state before mutation.
terminateLiveAgent is not called, the handle stays in the Map forever with status "running". Use finally blocks or crash-recovery to ensure termination.evictStaleLiveAgentHandles, completed agents accumulate in the Map. This is mitigated by calling eviction on every widget refresh.workspaceId differs from the current workspace. Always check live.workspaceId === manifest.cwd.src/runtime/live-agent-manager.ts — register, terminate, evict, workspaceIdsrc/runtime/live-session-runtime.ts — 4 lifecycle gaps fixedsrc/runtime/team-runner.ts — terminate on success/failsrc/runtime/background-runner.ts — terminate on catchsrc/runtime/crash-recovery.ts — terminate in purge+reconcilesrc/extension/team-tool/api.ts — workspaceId filtersrc/extension/team-tool/cancel.ts — terminate on cancelsrc/extension/team-tool/respond.ts — terminate on respondsrc/ui/crew-widget.ts — evictStaleLiveAgentHandles on refreshcd pi-crew
npx tsc --noEmit
node --experimental-strip-types --test test/unit/live-agent-manager.test.ts test/unit/live-session-runtime.test.ts test/unit/live-agent-control.test.ts
npm test