# Background Task Management in Craft-Agents: A Technical Deep Dive

> Explore background task management in Craft-Agents. Learn how its pipeline detects flags, tracks tasks by agent IDs, and groups child activities for a superior UI experience. Discover the technical approach.

- Repository: [Craft Docs/craft-agents-oss](https://github.com/lukilabs/craft-agents-oss)
- Tags: deep-dive
- Published: 2026-04-18

---

**Craft-Agents treats background execution as a first-class concept, using a pipeline that detects background flags, tracks tasks via agent IDs, and groups child activities under parent tasks for clean UI rendering.**

Background task management in the Craft-Agents SDK enables long-running operations to execute asynchronously while the UI maintains real-time status updates. When a **Task** or Bash **Shell** is invoked with `run_in_background: true`, the system extracts identifiers, emits tracking events, and aggregates metrics upon completion. This article examines the implementation across [`packages/shared/src/agent/tool-matching.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/tool-matching.ts) and [`packages/ui/src/components/chat/turn-utils.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/ui/src/components/chat/turn-utils.ts).

## Detecting Background Task Events

The detection logic resides in [`packages/shared/src/agent/tool-matching.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/tool-matching.ts), specifically within the `detectBackgroundEvents` function. When a tool result arrives, the code inspects the payload for the `run_in_background` flag and extracts the `agentId` from the result text.

### Identifying Task Background Events

For parent tasks, the system checks three conditions: the tool name indicates a parent task, the input contains `run_in_background: true`, and the result string is present and error-free. A regex captures the `agentId`, triggering a `task_backgrounded` event:

```typescript
// packages/shared/src/agent/tool-matching.ts
if (isParentTaskTool(entry.name) && entry.input?.run_in_background === true
    && !isError && resultStr) {
  const agentIdMatch = resultStr.match(/agentId:\s*([a-zA-Z0-9_-]+)/);
  if (agentIdMatch?.[1]) {
    events.push({
      type: 'task_backgrounded',
      toolUseId,
      taskId: agentIdMatch[1],
      turnId,
      ...(typeof intentValue === 'string' && { intent: intentValue }),
    });
  }
}

```

### Handling Shell Background Events

A similar branch manages Bash shells, looking for `shell_id` or `backgroundTaskId` in the result payload to emit `shell_backgrounded` events. This ensures both task agents and shell processes receive consistent background treatment.

## Tracking Task Output and Metrics

Once a background task completes, it returns a **TaskOutput** tool result containing the original `task_id` (matching the `agentId`) and execution metrics. The UI layer in [`packages/ui/src/components/chat/turn-utils.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/ui/src/components/chat/turn-utils.ts) manages this mapping.

### Mapping Agent IDs to Task Results

The system builds a lookup table to associate completed TaskOutput activities with their respective agent IDs:

```typescript
// packages/ui/src/components/chat/turn-utils.ts
const taskOutputByAgentId = new Map<string, TaskOutputData>();
for (const activity of activities) {
  if (activity.toolName === 'TaskOutput' && activity.status === 'completed') {
    const taskId = activity.toolInput?.task_id as string | undefined;
    if (taskId) {
      const data = extractTaskOutputData(activity);
      if (data) taskOutputByAgentId.set(taskId, data);
    }
  }
}

```

The `extractTaskOutputData` function parses the JSON payload to extract `duration_ms` and token usage statistics.

### Linking Parent Tasks to Background Jobs

To correlate parent tasks with their background execution data, the code scans activities for completed or backgrounded parent tasks and extracts the `agentId` from the content string:

```typescript
// packages/ui/src/components/chat/turn-utils.ts
for (const activity of activities) {
  if (isParentTaskTool(activity.toolName ?? '') &&
      (activity.status === 'completed' || activity.status === 'backgrounded') &&
      activity.content) {
    const agentIdMatch = activity.content.match(/agentId:\s*([a-zA-Z0-9_-]+)/);
    const capturedAgentId = agentIdMatch?.[1];
    if (capturedAgentId && activity.toolUseId) {
      taskToAgentId.set(activity.toolUseId, capturedAgentId);
    }
  }
}

```

This creates a bridge between `toolUseId` and `agentId`, enabling the UI to retrieve metrics from `taskOutputByAgentId` when rendering the parent task.

## Grouping and UI Presentation

The final phase transforms the flat activity list into a hierarchical structure suitable for rendering, hiding implementation details like raw `TaskOutput` entries while surfacing their data.

### Activity Grouping Logic

The `groupActivitiesByParent` function categorizes activities into standalone items or grouped task structures:

```typescript
// packages/ui/src/components/chat/turn-utils.ts
if (isParentTaskTool(activity.toolName ?? '')) {
  const children = activity.toolUseId
    ? (childrenByParent.get(activity.toolUseId) || [])
    : [];

  let taskOutputData: TaskOutputData | undefined;
  if (activity.toolUseId) {
    const agentId = taskToAgentId.get(activity.toolUseId);
    if (agentId) taskOutputData = taskOutputByAgentId.get(agentId);
  }

  result.push({
    type: 'group',
    parent: activity,
    children: children.sort((a, b) => a.timestamp - b.timestamp),
    taskOutputData,
  });
}

```

Orphan activities without parents are added directly to the result array. `TaskOutput` activities are intentionally skipped in the main loop because their data is already incorporated into the parent group.

### Rendering Background Status

UI components consume the grouped structure to display:
- A "running in background" badge when `status === 'backgrounded'`
- Expandable task groups showing child activities
- Aggregated metrics (`durationMs`, `input_tokens`, `output_tokens`) extracted from `taskOutputData`

The styling leverages the `bg-background` class defined in [`packages/shared/src/config/theme.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/config/theme.ts), ensuring consistent visual treatment across components like `OverlayErrorBanner`.

## Complete Code Example

The following example demonstrates the full lifecycle of a background task invocation:

```typescript
// 1️⃣ Invoke a Task with background flag
await agent.runTool('Task', {
  _intent: 'Search repository',
  run_in_background: true,
  query: 'background task detection',
});

// 2️⃣ The SDK returns something like:
//    "Task completed successfully\n\nagentId: abc123"

// 3️⃣ `detectBackgroundEvents` creates a `task_backgrounded` event.
//    UI marks the turn as "backgrounded".

// 4️⃣ Later a `TaskOutput` tool arrives:
//    {
//      toolName: 'TaskOutput',
//      toolInput: { task_id: 'abc123' },
//      content: '{"duration_ms":1200,"usage":{"input_tokens":30,"output_tokens":45}}'
//    }

// 5️⃣ `groupActivitiesByParent` links `abc123` → parent Task,
//    attaches the duration/tokens, and hides the raw `TaskOutput` activity.
//    The UI now shows "Task (1.2 s, 75 tokens)".

```

## Summary

- **Detection**: The `detectBackgroundEvents` function in [`packages/shared/src/agent/tool-matching.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/tool-matching.ts) identifies background execution by checking `run_in_background: true` and extracting `agentId` or `shell_id` from results.
- **Tracking**: [`packages/ui/src/components/chat/turn-utils.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/ui/src/components/chat/turn-utils.ts) maintains two mappings: `taskOutputByAgentId` (metrics storage) and `taskToAgentId` (parent-to-background linkage).
- **Grouping**: The `groupActivitiesByParent` function creates hierarchical structures that hide raw `TaskOutput` entries while surfacing their metrics in the parent task UI.
- **Rendering**: UI components consume grouped data to display background status badges and aggregated statistics (duration, token usage) without exposing implementation details.

## Frequently Asked Questions

### How does Craft-Agents detect when a task runs in the background?

The SDK checks tool results in [`packages/shared/src/agent/tool-matching.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/tool-matching.ts) using the `detectBackgroundEvents` function. When a tool result contains `run_in_background: true` in its input and the result text includes an `agentId` (extracted via regex), the system emits a `task_backgrounded` event. This event signals the UI to mark the turn as running asynchronously.

### Where are background task metrics stored after completion?

Completed background tasks return a `TaskOutput` tool result containing `duration_ms` and token usage statistics. The UI layer in [`packages/ui/src/components/chat/turn-utils.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/ui/src/components/chat/turn-utils.ts) parses these results into `taskOutputByAgentId`, a Map that stores metrics keyed by the `task_id` (matching the original `agentId`). This allows the system to attach final metrics to the parent task once it completes.

### How does the UI handle background task visualization?

The UI uses the `groupActivitiesByParent` function in [`packages/ui/src/components/chat/turn-utils.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/ui/src/components/chat/turn-utils.ts) to transform flat activity lists into hierarchical groups. When rendering, components check for `status === 'backgrounded'` to display loading indicators. Once complete, the UI extracts metrics from the grouped `taskOutputData` and displays aggregated duration and token counts alongside the parent task, while hiding the raw `TaskOutput` activity from the user view.