-
Notifications
You must be signed in to change notification settings - Fork 6
[Epic] Inter-Agent Task Handoff #164
Description
Overview
Inter-Agent Task Handoff adds an async task delegation system to copilot-bridge that allows Agent A to fire-and-forget a long-running task to Agent B and receive results via callback, without blocking Agent A's session or requiring a human intermediary. The current ask_agent tool is synchronous and blocks for up to 300 seconds, making it unsuitable for long-running tasks, parallel multi-agent workflows, or autonomous pipeline chains. This feature adds two new tools -- delegate_task and check_task -- backed by a new delegated_tasks SQLite table, an asynchronous execution loop, and a dual-path result delivery system (channel callback + task store polling).
Spec Reference
Phases and Tasks
Phase 0 - Pre-Phase Spike
- [T000] Spike: trace
withWorkspaceEnv()mutex scope to determine effective concurrency ceiling and setmaxConcurrentDelegationsdefault accordingly
Phase 1 - Foundation + delegate_task Skeleton
- [T001] Define all TypeScript interfaces and union types in
src/types/task-delegator.ts(TaskStatus,DelegatedTask,NewDelegatedTask,DelegateTaskInput/Output/Error,CheckTaskInput/Output,DelegationAllowEntry) - [T002] Add
delegated_tasksDDL migration withCREATE TABLE IF NOT EXISTSguard and 5 named indexes insrc/state/store.ts - [T003] Add 7 new
interAgentdelegation config fields with startup-time validation insrc/config/config.ts(maxConcurrentDelegations,maxDelegationTimeout,maxDelegationTimeoutCeiling,maxTasksPerChain,deduplicationWindowSecs,requeueOnRestart,delegationAllow) - [T004] Implement 8 store functions in
src/state/store.ts:insertDelegatedTask,updateTaskStatus,getTask,listTasks,countRunningTasks,countChainTasks(queries bothdelegated_tasksANDagent_calls),findDuplicateTask,getTasksByStatus - [T005] Extend
src/core/inter-agent.tswithdelegationDepthonInterAgentContextand addcanDelegateTo/canReceiveDelegationFromexported functions - [T006] Implement
TaskDelegatorclass insrc/core/task-delegator.tswith 7-step synchronous validation andhandleDelegateTask()returning{ taskId, status: "queued" }within 2s (no async execution yet) - [T007] Register
delegate_tasktool insrc/core/session-manager.ts(add toBRIDGE_CUSTOM_TOOLS, wire handler toTaskDelegator) - [T008] Write Phase 1 unit tests in
test/unit/task-delegator.test.tscovering all 7 rejection error codes, deduplication,countChainTasksdual-table query, and zero regressions in existingask_agenttests
Phase 2 - Async Executor + check_task
- [T009] Implement
start(),tick(), andexecuteTask()insrc/core/task-delegator.ts-- 2s poll loop, priority-ordered dispatch, detached async execution viaexecuteEphemeralCall(), exponential backoff retry - [T010] Implement
recoverStaleTasks()insrc/core/task-delegator.ts-- on startup, transitionsrunningtasks totimed_outor re-queues perrequeueOnRestartconfig, completes within 5s - [T011] Wire
TaskDelegatorin bridge startup: callrecoverStaleTasks()before accepting connections, callstart()after full initialization - [T012] Implement
handleCheckTask()insrc/core/task-delegator.ts-- single-task mode (full untruncatedresult) and list mode (up to 10, result excluded), access-scoped to calling bot (admin sees all) - [T013] Register
check_tasktool insrc/core/session-manager.ts(add toBRIDGE_CUSTOM_TOOLS, wire handler) - [T014] Write Phase 2 unit + integration tests:
tick()lifecycle, full/failure result paths, retry + exhaustion,recoverStaleTasks()timing,check_taskall lifecycle states, admin visibility, end-to-end poll-to-completion, no regression onask_agent/schedule
Phase 3 - Callback Delivery + Safety Rails
- [T015] Implement
formatCallbackMessage()insrc/core/task-delegator.ts-- completion template (taskId[0:8], targetBot, duration, 500-char result summary,check_taskhint) and failure template (status, attempt/max, retry countdown) - [T016] Implement
deliverCallback()insrc/core/task-delegator.tsand wire intoexecuteTask()success/error paths -- resolvescallbackChannel ?? callerChannel, posts via existing channel adapter, silent failure with WARN log - [T017] Integrate
delegate_taskcalls into the existing loop detector insrc/core/loop-detector.ts-- reuse existing tool-name + args-hash pattern; reject at CRITICAL threshold withCONCURRENCY_LIMIT_EXCEEDED - [T018] Add
INFO-level lifecycle transition logs andagent_callsaudit rows on task completion insrc/core/task-delegator.ts - [T019] Write Phase 3 integration tests covering all 12 named acceptance criteria (AC-001 through AC-012) plus manual smoke test (3 parallel delegations, callbacks,
check_taskfull result)
Polish
- [T020] Add README /
docs/documentation fordelegate_taskandcheck_task: parameter tables, output schema, all 7 error codes, fire-and-forget + poll-loop code examples, safety limits table, T000 mutex finding note - [T021] Verify TypeScript strict-mode compilation across all new and modified files; fix implicit
any, missing return types, unchecked null dereferences - [T022] Run final backward-compatibility regression: full
ask_agentandscheduleintegration test suites against the Phase 1-3 build; confirm no table schema or tool resolution regressions
Acceptance Criteria
- AC-001 - Fire-and-Forget Semantics:
delegate_taskreturns within 2s with a valid UUIDtaskIdandstatus: "queued"; Agent A's session remains interactive during the full execution window. - AC-002 - Task Execution and Completion: on
executeEphemeralCall()success,status = "completed",resultis full untruncated text,completed_atis set, and a completion notification is posted to the callback channel. - AC-003 - Status Polling:
check_task({ taskId })returnsid,status,callerBot,targetBot,createdAt, and (if completed) the fullresult. - AC-004 - Concurrency Limit: when
maxConcurrentDelegations = 2and 2 tasks are running, a third call returnsCONCURRENCY_LIMIT_EXCEEDEDand creates no DB row. - AC-005 - Chain Budget: when
maxTasksPerChain = 5and achain_idhas 5 tasks (acrossask_agent+delegate_task), a 6th call returnsCHAIN_BUDGET_EXCEEDED. - AC-006 - Timeout Capping:
timeout: 7200withmaxDelegationTimeoutCeiling: 3600results intimeout_ms = 3600000. - AC-007 - Allowlist Enforcement: unauthorized caller returns
DELEGATION_NOT_PERMITTEDwithout creating a DB row. - AC-008 - Deduplication: identical task resubmitted within the deduplication window returns the existing
taskIdand currentstatus; no new row created. - AC-009 - Retry on Failure: failed task with
maxAttempts: 3re-queues with exponential backoff and posts a retry-scheduled failure notification. - AC-010 - Startup Recovery:
status = "running"tasks at bridge startup are transitioned to"timed_out"within 5s whenrequeueOnRestart = false. - AC-011 - Backward Compatibility:
ask_agent,schedule, and all existing bridge tools are unaffected after deployment. - AC-012 - Callback Delivery: completed task with
callbackChannelset results in a message posted to that channel containing task ID, target bot, duration, result summary (<= 500 chars), and acheck_taskusage hint.
Notes
Key Design Decisions
- SQLite-only persistence: No external message broker (Redis, NATS, etc.).
delegated_taskstable in the existing SQLite state DB. Transactions used for all state transitions to prevent partial writes. - Reuse
executeEphemeralCall(): Task execution reuses the existing ephemeral session engine unchanged. Permission inheritance viabuildEphemeralPermissionHandler()boundsgrantToolsto the caller's own permissions -- no new privilege escalation vector. delegationAllowoverlayscanCall: Admins can permit sync Q&A (ask_agent) while restricting async delegation (delegate_task) separately. IfdelegationAllowis not configured for a bot pair, falls back to the existingcanCall/canBeCalledBycheck.countChainTasksspans both tables: Chain budget enforcement queries bothdelegated_tasksANDagent_callsbychain_idto prevent budget bypass when mixingask_agentanddelegate_taskin the same chain.- Dual-path result access: Channel callback (pushed on completion) and
check_taskpolling (structured query) are always both active. Callback delivery failure is silent -- result remains available via polling.
Risks
withWorkspaceEnv()mutex scope (Risk 1 / T000): If the mutex inexecuteEphemeralCall()wraps the entire call duration (not just env setup), effective concurrency per bot is 1 regardless ofmaxConcurrentDelegations. The spike (T000) must measure this before Phase 2 ships. Default is tentatively 3 (narrow mutex assumed).- SQLite write contention (Risk 3): At
maxConcurrentDelegations = 10, concurrentBEGIN IMMEDIATEtransactions on task state transitions could cause brief lock contention. Mitigated by bounded concurrency ceiling and existing SQLite WAL mode if enabled. - Callback channel availability (Risk 4): If
callbackChanneldoes not exist or the bot has no access to it, delivery fails silently. Result remains accessible viacheck_task. A WARN is logged. - Nested async chain depth semantics (Open Question 4): Async delegation consuming a depth slot identically to
ask_agentis recommended but the interaction with non-linear async chains needs review during T005/T006 implementation.
Out of Scope (v1)
External message brokers, task cancellation (cancel_task), task DAGs, streaming results, cross-bridge delegation, session checkpointing, human-in-the-loop approval, admin UI, and delegated_tasks encryption at rest.