May 10, 2026

Background ACP/Subagent Completions Surface Generic Task-Status Messages Instead of Parent-Agent Replies in Group Channels

When background ACP or subagent tasks complete in group/channel contexts, the system delivers raw lifecycle markers (e.g., 'Background task done') directly to the channel instead of routing completion context to the parent agent for user-facing summaries.

πŸ” Symptoms

Primary Manifestation

When a delegated or background ACP/subagent task completes in a group or channel context, the visible channel output contains only a raw lifecycle marker without conversational context from the parent agent:

Background task done: synthesized response from task-abc123
Background task failed: task-xyz789 failed with exit code 1
Background task timed out: task-def456 exceeded 30000ms limit

Secondary Manifestations

  • The user receives completion telemetry rather than an actionable summary in the conversational thread.
  • No follow-up message from the parent agent appears after the lifecycle marker, leaving the interaction hanging.
  • In multi-agent orchestration scenarios, child agent output may be prematurely exposed to the channel rather than being absorbed by the parent agent for synthesis.

Environment Context

This behavior manifests specifically when:

  • The originating conversation occurs within a GROUP or CHANNEL session type
  • The task was delegated via ACP protocol or subagent invocation
  • The task completion path traverses src/tasks/task-registry.ts

Observed Console Behavior

If debug logging is enabled, you may observe entries indicating the completion delivery bypass:

[TaskRegistry] Terminal delivery to origin: group:channel-123
[TaskRegistry] Completion marker emitted without parent-agent handoff
[TaskRegistry] Skipping conversational synthesis for task: task-abc123

🧠 Root Cause

Architectural Analysis

The issue stems from a divergence in the terminal task delivery path within src/tasks/task-registry.ts. The code contains a conditional branch that, when the requester’s origin is identified as a group/channel context, delivers the completion event directly to that channel without invoking the parent-agent conversational handoff pipeline.

Failure Sequence

The following sequence illustrates the problematic code path:

  1. Background task completes: The delegated ACP or subagent task finishes execution and emits a completion event.
  2. TaskRegistry receives completion signal: The TaskRegistry class processes the completion via its terminal delivery handler.
  3. Origin detection fails to flag group context: The completion handler detects the requester's origin as a group/channel identifier but does not set the requiresConversationalHandoff flag.
  4. Direct lifecycle marker emission: The code bypasses the parent-agent synthesis path and emits a raw Background task done/failed/timed out message directly to the channel.
  5. Parent agent excluded: The parent agent session never receives the completion context, so no user-facing summary is generated.

Code-Level Root Cause

In src/tasks/task-registry.ts, the terminal delivery logic likely contains a condition similar to:

// Problematic code path (pseudo-representation)
if (requesterOrigin.type === 'group' || requesterOrigin.type === 'channel') {
    // Direct emission without handoff
    emitLifecycleMarker(requesterOrigin, taskCompletion);
    return; // Early return bypasses parent-agent pipeline
}

This early return prevents the completion context from reaching the parent agent’s synthesis handler, which would otherwise generate the user-facing summary.

Architectural Inconsistency

The system’s design intent (as documented in the workflow specification) requires:

Child/Background Output β†’ Internal
    ↓
Parent Session Receives Completion Event
    ↓
Parent Agent Synthesizes Summary β†’ User-Facing Response

However, the group/channel delivery path violates step 2, directly exposing the raw marker to users without the synthesis step.

πŸ› οΈ Step-by-Step Fix

Prerequisites

  • Access to the src/tasks/task-registry.ts source file
  • Understanding of the parent-agent communication protocol in your deployment
  • Access to test environment with group/channel context capability

Fix Procedure

Step 1: Locate the Terminal Delivery Handler

Open src/tasks/task-registry.ts and identify the method handling terminal task delivery. The method signature will resemble:

async deliverTerminalCompletion(
    taskId: string,
    completion: TaskCompletion,
    requesterOrigin: SessionOrigin
): Promise<void>

Step 2: Modify the Group/Channel Origin Handling

Replace the direct emission logic with a handoff-aware flow. The fix ensures that group/channel completions still route through the parent-agent pipeline:

// BEFORE (problematic)
if (requesterOrigin.type === 'group' || requesterOrigin.type === 'channel') {
    // Direct emission without handoff
    emitLifecycleMarker(requesterOrigin, taskCompletion);
    return; // Bypasses parent-agent pipeline
}

// AFTER (fixed)
if (requesterOrigin.type === 'group' || requesterOrigin.type === 'channel') {
    // Route through parent-agent for conversational synthesis
    const parentAgentContext = await this.extractParentAgentContext(taskId);
    
    if (parentAgentContext && parentAgentContext.sessionId) {
        // Handoff to parent agent for user-facing summary
        await this.deliverToParentAgent(
            parentAgentContext.sessionId,
            taskCompletion,
            requesterOrigin
        );
        // Emit telemetry marker as secondary, not primary output
        await this.emitSecondaryTelemetry(requesterOrigin, taskCompletion);
    } else {
        // Fallback: no parent agent available, emit lifecycle marker
        await this.emitLifecycleMarker(requesterOrigin, taskCompletion);
    }
    return;
}

Step 3: Implement the Parent Agent Delivery Method

Add the deliverToParentAgent method if it does not exist:

private async deliverToParentAgent(
    parentSessionId: string,
    completion: TaskCompletion,
    originalOrigin: SessionOrigin
): Promise<void> {
    const parentSession = await this.sessionManager.getSession(parentSessionId);
    
    if (!parentSession) {
        this.logger.warn(`Parent session ${parentSessionId} not found for task completion`);
        return;
    }

    // Deliver completion context to parent agent for synthesis
    await parentSession.deliverCompletionEvent({
        taskId: completion.taskId,
        result: completion.result,
        status: completion.status,
        origin: originalOrigin,
        timestamp: Date.now()
    });

    this.logger.info(`Completion context delivered to parent agent session: ${parentSessionId}`);
}

Step 4: Implement Secondary Telemetry Emission

Add the emitSecondaryTelemetry method to emit lifecycle markers as non-blocking telemetry:

private async emitSecondaryTelemetry(
    origin: SessionOrigin,
    completion: TaskCompletion
): Promise<void> {
    // Emit as structured telemetry, not as conversational message
    await this.telemetryChannel.publish({
        type: 'task_completion_telemetry',
        origin: origin,
        taskId: completion.taskId,
        status: completion.status,
        summary: this.summarizeCompletion(completion),
        timestamp: Date.now()
    });
    
    this.logger.debug(`Secondary telemetry emitted for task: ${completion.taskId}`);
}

Step 5: Verify Handoff Flagging

Ensure the parent-agent context extraction correctly identifies when a handoff is required. Update extractParentAgentContext if necessary:

private async extractParentAgentContext(
    taskId: string
): Promise<ParentAgentContext | null> {
    const taskRecord = await this.taskStore.getTask(taskId);
    
    if (!taskRecord) {
        return null;
    }

    // Always attempt to resolve parent context for delegated tasks
    return {
        sessionId: taskRecord.parentAgentSessionId,
        requiresSynthesis: taskRecord.delegationType !== 'direct',
        originTaskId: taskId
    };
}

πŸ§ͺ Verification

Pre-Fix Baseline

Before applying the fix, capture the baseline behavior:

# In a group/channel context, invoke a background ACP task
/agent delegate --task "analyze logs" --background --channel group:engineering

# Observe output - should show only:
# Background task done: analyze logs completed
# (No parent-agent summary follows)

Post-Fix Verification Steps

Step 1: Unit Test Validation

Run unit tests for the affected code path:

npm test -- --grep "TaskRegistry.deliverTerminalCompletion"
# Expected: All tests pass with new handoff logic coverage

Step 2: Integration Test - Group Channel Completion

# Create a test scenario in a group channel
/agent delegate --task "generate report" --background --channel group:test-room

# Expected behavior after fix:
# 1. No raw "Background task done" message as primary output
# 2. Parent agent synthesizes and sends: "I've completed the report generation. 
#    Here's the summary: ..."
# 3. Telemetry marker appears in debug logs (not in conversational thread)

Step 3: Verify Parent Agent Receives Context

Enable debug logging and observe the delivery sequence:

# Enable debug logging
export LOG_LEVEL=debug

# Execute background task
/agent delegate --task "process data" --background --channel group:verification-room

# Check logs for expected sequence:
# [TaskRegistry] Extracting parent agent context for task: task-xyz
# [TaskRegistry] Parent agent session resolved: session-abc
# [TaskRegistry] Completion context delivered to parent agent session: session-abc
# [TaskRegistry] Secondary telemetry emitted for task: task-xyz
# [ParentAgent] Received completion event, synthesizing summary...

Step 4: Verify Telemetry Persistence

Ensure lifecycle telemetry is still captured for monitoring:

# Check telemetry channel receives completion data
curl -X POST http://telemetry-api/internal/events \
  -d '{"type":"task_completion_telemetry","taskId":"task-xyz"}'

# Expected: Telemetry record exists with complete task metadata

Step 5: Fallback Behavior Verification

Test that the fallback (direct lifecycle emission) still functions when no parent agent is available:

# Execute direct task (no parent agent) in group context
/agent execute --task "quick status" --channel group:no-parent-room

# Expected: Direct lifecycle marker emitted (fallback path)
# Background task done: quick status completed

Expected Outcomes

  • Primary channel output: Parent-agent synthesized user-facing summary (not raw lifecycle marker)
  • Log output: Confirmation of parent-agent handoff
  • Telemetry: Secondary lifecycle marker captured in telemetry system
  • Exit codes: All tests exit with 0

⚠️ Common Pitfalls

Pitfall 1: Missing Parent Session Resolution

Symptom: Parent-agent handoff fails silently; no summary appears.

Cause: The parentAgentSessionId is not persisted during task delegation.

Mitigation: Ensure task records include parentAgentSessionId at delegation time:

// During task delegation initialization
await taskStore.createTask({
    taskId: generatedTaskId,
    parentAgentSessionId: currentSession.id,
    delegationType: 'ACP' | 'subagent',
    // ...other fields
});

Pitfall 2: Circular Handoff in Nested Delegation

Symptom: Infinite loop or stack overflow when parent agents delegate to child agents recursively.

Cause: The handoff logic does not track delegation depth.

Mitigation: Implement delegation depth tracking:

if (completion.delegationDepth >= MAX_DELEGATION_DEPTH) {
    // Emit direct completion for deep nested tasks
    await this.emitLifecycleMarker(requesterOrigin, completion);
    return;
}

Pitfall 3: Channel Message Flooding

Symptom: Multiple completion messages appear in the channel after the fix.

Cause: Both the parent-agent summary and the telemetry marker are emitted to the channel.

Mitigation: Ensure telemetry emission publishes to a separate channel (not the conversational thread):

// Telemetry goes to telemetry system, not conversation channel
await this.telemetryChannel.publish({...}); // βœ… Correct

// NOT:
// await this.channel.sendMessage(origin, message); // ❌ Incorrect

Pitfall 4: Session Manager Availability in Tests

Symptom: Unit tests pass locally but fail in CI due to session manager mock not being injected.

Cause: Direct instantiation of SessionManager instead of dependency injection.

Mitigation: Use constructor injection:

constructor(
    private readonly sessionManager: SessionManagerInterface,
    private readonly taskStore: TaskStoreInterface,
    private readonly telemetryChannel: TelemetryChannelInterface
) {}

Pitfall 5: macOS/Darvin Path Separator in Test Fixtures

Symptom: Tests fail to locate fixture files on macOS.

Cause: Test fixtures use POSIX paths that fail on Darwin.

Mitigation: Use path.join() for all file path construction:

import * as path from 'path';
const fixturePath = path.join(__dirname, '..', 'fixtures', 'task-completion.json');

Pitfall 6: Docker Environment Session Isolation

Symptom: Parent-agent context is not resolved in Docker-based test environments.

Cause: Session state is not persisted across container restarts during tests.

Mitigation: Use in-memory session store with test isolation:

beforeEach(() => {
    testSessionStore.clear();
    container.mock(SessionManager, {
        getSession: (id) => testSessionStore.get(id)
    });
});

Adjacent Issues

  • Missing Parent-Agent Summary in Multi-Agent Delegation
    Similar root cause: Completion events bypass the synthesis pipeline. Manifests when nested agent delegation completes without user-facing output.
  • ACP Protocol Timeout Messages Not Synthesized
    The ACP protocol may emit Background task timed out messages directly without parent-agent awareness.
  • Group Channel Message Ordering with Background Tasks
    Lifecycle markers may appear out of order relative to other channel messages due to async completion handling.
  • E_TASK_REGISTRY_HANDSHAKE_FAILED: Parent-agent handoff handshake times out.
  • E_SESSION_NOT_FOUND: Parent agent session ID resolved but session no longer exists.
  • E_COMPLETION_TELEMETRY_FALLBACK: Fallback to direct emission triggered due to missing parent context.
  • E_DELEGATION_DEPTH_EXCEEDED: Nested delegation exceeded maximum depth threshold.
  • E_ORIGIN_TYPE_UNHANDLED: Unrecognized session origin type in terminal delivery path.

Historical Context

This issue may be adjacent to earlier UX improvements around delegated and background output visibility in group rooms. Prior fixes addressed configuration toggles for output visibility; this issue specifically targets the missing parent-agent conversational handoff that should precede any user-facing completion message.

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.