The src/background/ module manages long-running AI agent tasks that execute asynchronously in isolated sessions. It enables fire-and-forget task execution, allowing users to continue working while background tasks complete independently. The module handles task lifecycle management, session creation, completion detection, and optional tmux pane integration for visual task tracking.
Represents a background task with complete lifecycle tracking:
bg_<random>)pending | starting | running | completed | failed | cancelled)Configuration for launching new background tasks:
Two-phase task launch:
pending statusmaxConcurrentStarts limit (default: 10)session.status events instead of pollingtasks Map: Task ID → BackgroundTasktasksBySessionId Map: Session ID → Task IDcompletionResolvers Map stores pending wait promiseswaitForCompletion() returns promise that resolves on task completioncancelled before removing from queuestartTask() after incrementing activeStartsMain orchestrator for background task lifecycle:
State:
tasks: Map of all tracked taskstasksBySessionId: Session ID to task ID mappingclient: OpenCode client APIdirectory: Working directory for taskstmuxEnabled: Whether tmux integration is activeconfig: Plugin configurationbackgroundConfig: Background task configurationstartQueue: Queue of tasks waiting to startactiveStarts: Count of currently starting tasksmaxConcurrentStarts: Concurrency limitcompletionResolvers: Map of waiting promisesKey Methods:
launch(opts): Create and queue a new background task (sync)handleSessionStatus(event): Process session.status eventsgetResult(taskId): Retrieve current task statewaitForCompletion(taskId, timeout): Wait for task completioncancel(taskId?): Cancel one or all taskscleanup(): Clean up all tasksManages tmux pane lifecycle for background sessions:
State:
client: OpenCode client APItmuxConfig: Tmux configurationserverUrl: OpenCode server URLsessions: Map of tracked sessionspollInterval: Polling timerenabled: Whether tmux integration is activeKey Methods:
onSessionCreated(event): Spawn tmux pane for child sessionsonSessionStatus(event): Close pane when session becomes idlepollSessions(): Fallback polling for status updatescloseSession(sessionId): Close pane and remove trackingcleanup(): Close all panes and stop pollingsessionId: OpenCode session IDpaneId: Tmux pane identifierparentId: Parent session IDtitle: Session titlecreatedAt: Creation timestamplastSeenAt: Last seen timestampmissingSince: When session went missing (optional)type: Event type (session.created, session.status)properties: Event properties containing session infoUser calls launch()
↓
Create BackgroundTask with status='pending'
↓
Store in tasks Map
↓
Enqueue in startQueue
↓
processQueue() checks concurrency limit
↓
startTask() executes (async)
↓
├─ Set status='starting'
├─ Increment activeStarts
├─ Check for cancellation (race condition)
├─ Create OpenCode session
├─ Store sessionId in tasksBySessionId
├─ Set status='running'
├─ Wait 500ms (if tmux enabled)
├─ Send prompt to session
└─ Decrement activeStarts and processQueue()
session.status event received
↓
handleSessionStatus() checks event type
↓
Lookup taskId from tasksBySessionId
↓
Verify task is running
↓
Check if status.type === 'idle'
↓
extractAndCompleteTask()
↓
├─ Fetch session messages
├─ Filter assistant messages
├─ Extract text/reasoning parts
├─ Join extracted content
└─ completeTask()
↓
├─ Set status='completed'
├─ Set result or error
├─ Delete from tasksBySessionId
├─ Send notification to parent session
├─ Resolve completionResolvers
└─ Log completion
User calls cancel(taskId?)
↓
Find task(s) with pending/starting/running status
↓
For each task:
↓
├─ Delete from completionResolvers
├─ Check if in startQueue (before marking cancelled)
├─ Set status='cancelled' (prevents race with startTask)
├─ Remove from startQueue if pending
└─ completeTask() with 'cancelled' status
session.created event received
↓
onSessionCreated() checks enabled and parentID
↓
Skip if already tracking
↓
spawnTmuxPane() with session info
↓
├─ Create pane with title
├─ Connect to OpenCode server
└─ Return paneId
↓
Store in sessions Map
↓
Start polling (if not already running)
session.status event received (idle)
↓
onSessionStatus() checks enabled
↓
closeSession()
↓
├─ closeTmuxPane()
├─ Delete from sessions Map
└─ Stop polling if no sessions left
pollSessions() runs on interval
↓
Fetch all session statuses
↓
For each tracked session:
↓
├─ Check if idle → close
├─ Update lastSeenAt if found
├─ Set missingSince if not found
├─ Check missingTooLong → close
└─ Check timeout → close
@opencode-ai/plugin: PluginInput type, client API../config: BackgroundTaskConfig, PluginConfig, TmuxConfig, POLL_INTERVAL_BACKGROUND_MS../utils: applyAgentVariant, resolveAgentVariant, log, tmux utilitiessrc/index.ts)src/skills/background-task.ts)Plugin Initialization
Event Handling
session.status for completion detectionsession.created and session.statusSkill Integration
launch() to create tasksgetResult() and waitForCompletion() to retrieve resultscancel() to cancel tasksCleanup
cleanup() methodsmaxConcurrentStarts: Maximum concurrent task starts (default: 10)enabled: Whether tmux integration is active../config/schema)failedfailedAll operations are logged with context:
Logs use the format [component-name] message with structured metadata.