diff --git a/src/agents/atlas/default-prompt-sections.ts b/src/agents/atlas/default-prompt-sections.ts
index 46e2634f7..24ba9f807 100644
--- a/src/agents/atlas/default-prompt-sections.ts
+++ b/src/agents/atlas/default-prompt-sections.ts
@@ -150,16 +150,16 @@ task(
### 3.5 Handle Failures (USE RESUME)
-**CRITICAL: When re-delegating, ALWAYS use \`session_id\` parameter.**
+**CRITICAL: When re-delegating, ALWAYS use \`task_id\` parameter.**
-Every \`task()\` output includes a session_id. STORE IT.
+Every \`task()\` output includes a task_id. STORE IT.
If task fails:
1. Identify what went wrong
2. **Resume the SAME session** - subagent has full context already:
\`\`\`typescript
task(
- session_id="ses_xyz789", // Session from failed task
+ task_id="ses_xyz789", // Task ID from failed task
load_skills=[...],
prompt="FAILED: {error}. Fix by: {specific instruction}"
)
@@ -167,7 +167,7 @@ If task fails:
3. Maximum 3 retry attempts with the SAME session
4. If blocked after 3 attempts: Document and continue to independent tasks
-**Why session_id is MANDATORY for failures:**
+**Why task_id is MANDATORY for failures:**
- Subagent already read all files, knows the context
- No repeated exploration = 70%+ token savings
- Subagent knows what approaches already failed
@@ -292,6 +292,6 @@ export const DEFAULT_ATLAS_CRITICAL_RULES = `
- Pass inherited wisdom to every subagent
- Parallelize independent tasks
- Verify with your own tools
-- **Store session_id from every delegation output**
-- **Use \`session_id="{session_id}"\` for retries, fixes, and follow-ups**
+- **Store task_id from every delegation output**
+- **Use \`task_id="{task_id}"\` for retries, fixes, and follow-ups**
`
diff --git a/src/agents/atlas/gemini-prompt-sections.ts b/src/agents/atlas/gemini-prompt-sections.ts
index 7a84e3d73..2ca4c2bc2 100644
--- a/src/agents/atlas/gemini-prompt-sections.ts
+++ b/src/agents/atlas/gemini-prompt-sections.ts
@@ -164,10 +164,10 @@ Count remaining **top-level task** checkboxes. Ignore nested verification/eviden
### 3.5 Handle Failures
-**CRITICAL: Use \`session_id\` for retries.**
+**CRITICAL: Use \`task_id\` for retries.**
\`\`\`typescript
-task(session_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}")
+task(task_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}")
\`\`\`
- Maximum 3 retries per task
diff --git a/src/agents/atlas/gpt-prompt-sections.ts b/src/agents/atlas/gpt-prompt-sections.ts
index 96977f777..1a9f39c26 100644
--- a/src/agents/atlas/gpt-prompt-sections.ts
+++ b/src/agents/atlas/gpt-prompt-sections.ts
@@ -169,10 +169,10 @@ Count remaining **top-level task** checkboxes. Ignore nested verification/eviden
### 3.5 Handle Failures
-**CRITICAL: Use \`session_id\` for retries.**
+**CRITICAL: Use \`task_id\` for retries.**
\`\`\`typescript
-task(session_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}")
+task(task_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}")
\`\`\`
- Maximum 3 retries per task
diff --git a/src/agents/dynamic-agent-core-sections.ts b/src/agents/dynamic-agent-core-sections.ts
index dc91fd480..416750a54 100644
--- a/src/agents/dynamic-agent-core-sections.ts
+++ b/src/agents/dynamic-agent-core-sections.ts
@@ -182,7 +182,7 @@ Multi-step task? **ALWAYS consult Plan Agent first.** Do NOT start implementatio
- Single-file fix or trivial change → proceed directly
- Anything else (2+ steps, unclear scope, architecture) → \`task(subagent_type="plan", ...)\` FIRST
-- Use \`session_id\` to resume the same Plan Agent - ask follow-up questions aggressively
+- Use \`task_id\` to resume the same Plan Agent - ask follow-up questions aggressively
- If ANY part of the task is ambiguous, ask Plan Agent before guessing
Plan Agent returns a structured work breakdown with parallel execution opportunities. Follow it.`
diff --git a/src/agents/hephaestus/gpt-5-3-codex.ts b/src/agents/hephaestus/gpt-5-3-codex.ts
index 2ca2964f7..28127bcc8 100644
--- a/src/agents/hephaestus/gpt-5-3-codex.ts
+++ b/src/agents/hephaestus/gpt-5-3-codex.ts
@@ -409,9 +409,9 @@ After delegation, ALWAYS verify: works as expected? follows codebase pattern? MU
Every \`task()\` output includes a session_id. **USE IT for follow-ups.**
-- **Task failed/incomplete** - \`session_id="{id}", prompt="Fix: {error}"\`
-- **Follow-up on result** - \`session_id="{id}", prompt="Also: {question}"\`
-- **Verification failed** - \`session_id="{id}", prompt="Failed: {error}. Fix."\`
+- **Task failed/incomplete** - \`task_id="{id}", prompt="Fix: {error}"\`
+- **Follow-up on result** - \`task_id="{id}", prompt="Also: {question}"\`
+- **Verification failed** - \`task_id="{id}", prompt="Failed: {error}. Fix."\`
${
oracleSection
diff --git a/src/agents/hephaestus/gpt-5-4.ts b/src/agents/hephaestus/gpt-5-4.ts
index a88b6ea0f..eec4e18b4 100644
--- a/src/agents/hephaestus/gpt-5-4.ts
+++ b/src/agents/hephaestus/gpt-5-4.ts
@@ -312,10 +312,10 @@ Every delegation prompt needs these 6 sections:
After delegation, verify by reading every file the subagent touched. Check: works as expected? follows codebase pattern? Do not trust self-reports.
-Every \`task()\` returns a session_id. Use it for all follow-ups:
-- Task failed/incomplete: \`session_id="{id}", prompt="Fix: {error}"\`
-- Follow-up on result: \`session_id="{id}", prompt="Also: {question}"\`
-- Verification failed: \`session_id="{id}", prompt="Failed: {error}. Fix."\`
+Every \`task()\` returns a task_id. Use it for all follow-ups:
+- Task failed/incomplete: \`task_id="{id}", prompt="Fix: {error}"\`
+- Follow-up on result: \`task_id="{id}", prompt="Also: {question}"\`
+- Verification failed: \`task_id="{id}", prompt="Failed: {error}. Fix."\`
This preserves full context, avoids repeated exploration, saves 70%+ tokens.
diff --git a/src/agents/hephaestus/gpt.ts b/src/agents/hephaestus/gpt.ts
index cf1a3ea91..712bb9536 100644
--- a/src/agents/hephaestus/gpt.ts
+++ b/src/agents/hephaestus/gpt.ts
@@ -277,11 +277,11 @@ After delegation, ALWAYS verify: works as expected? follows codebase pattern? MU
### Session Continuity
-Every \`task()\` output includes a session_id. **USE IT for follow-ups.**
+Every \`task()\` output includes a task_id. **USE IT for follow-ups.**
-- **Task failed/incomplete** - \`session_id="{id}", prompt="Fix: {error}"\`
-- **Follow-up on result** - \`session_id="{id}", prompt="Also: {question}"\`
-- **Verification failed** - \`session_id="{id}", prompt="Failed: {error}. Fix."\`
+- **Task failed/incomplete** - \`task_id="{id}", prompt="Fix: {error}"\`
+- **Follow-up on result** - \`task_id="{id}", prompt="Also: {question}"\`
+- **Verification failed** - \`task_id="{id}", prompt="Failed: {error}. Fix."\`
${
oracleSection
diff --git a/src/agents/sisyphus.ts b/src/agents/sisyphus.ts
index 55b6c1c21..81a863d54 100644
--- a/src/agents/sisyphus.ts
+++ b/src/agents/sisyphus.ts
@@ -317,15 +317,15 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING:
### Session Continuity (MANDATORY)
-Every \`task()\` output includes a session_id. **USE IT.**
+Every \`task()\` output includes a task_id. **USE IT.**
**ALWAYS continue when:**
-- Task failed/incomplete → \`session_id=\"{session_id}\", prompt=\"Fix: {specific error}\"\`
-- Follow-up question on result → \`session_id=\"{session_id}\", prompt=\"Also: {question}\"\`
-- Multi-turn with same agent → \`session_id=\"{session_id}\"\` - NEVER start fresh
-- Verification failed → \`session_id=\"{session_id}\", prompt=\"Failed verification: {error}. Fix.\"\`
+- Task failed/incomplete → \`task_id=\"{task_id}\", prompt=\"Fix: {specific error}\"\`
+- Follow-up question on result → \`task_id=\"{task_id}\", prompt=\"Also: {question}\"\`
+- Multi-turn with same agent → \`task_id=\"{task_id}\"\` - NEVER start fresh
+- Verification failed → \`task_id=\"{task_id}\", prompt=\"Failed verification: {error}. Fix.\"\`
-**Why session_id is CRITICAL:**
+**Why task_id is CRITICAL:**
- Subagent has FULL conversation context preserved
- No repeated file reads, exploration, or setup
- Saves 70%+ tokens on follow-ups
@@ -336,10 +336,10 @@ Every \`task()\` output includes a session_id. **USE IT.**
task(category="quick", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix the type error in auth.ts...")
// CORRECT: Resume preserves everything
-task(session_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42")
+task(task_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42")
\`\`\`
-**After EVERY delegation, STORE the session_id for potential continuation.**
+**After EVERY delegation, STORE the task_id for potential continuation.**
### Code Changes:
- Match existing patterns (if codebase is disciplined)
diff --git a/src/agents/sisyphus/default.ts b/src/agents/sisyphus/default.ts
index 14895b124..52e237f7c 100644
--- a/src/agents/sisyphus/default.ts
+++ b/src/agents/sisyphus/default.ts
@@ -389,15 +389,15 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING:
### Session Continuity (MANDATORY)
-Every \`task()\` output includes a session_id. **USE IT.**
+Every \`task()\` output includes a task_id. **USE IT.**
**ALWAYS continue when:**
-- Task failed/incomplete → \`session_id="{session_id}", prompt="Fix: {specific error}"\`
-- Follow-up question on result → \`session_id="{session_id}", prompt="Also: {question}"\`
-- Multi-turn with same agent → \`session_id="{session_id}"\` - NEVER start fresh
-- Verification failed → \`session_id="{session_id}", prompt="Failed verification: {error}. Fix."\`
+- Task failed/incomplete → \`task_id="{task_id}", prompt="Fix: {specific error}"\`
+- Follow-up question on result → \`task_id="{task_id}", prompt="Also: {question}"\`
+- Multi-turn with same agent → \`task_id="{task_id}"\` - NEVER start fresh
+- Verification failed → \`task_id="{task_id}", prompt="Failed verification: {error}. Fix."\`
-**Why session_id is CRITICAL:**
+**Why task_id is CRITICAL:**
- Subagent has FULL conversation context preserved
- No repeated file reads, exploration, or setup
- Saves 70%+ tokens on follow-ups
@@ -408,10 +408,10 @@ Every \`task()\` output includes a session_id. **USE IT.**
task(category="quick", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix the type error in auth.ts...")
// CORRECT: Resume preserves everything
-task(session_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42")
+task(task_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42")
\`\`\`
-**After EVERY delegation, STORE the session_id for potential continuation.**
+**After EVERY delegation, STORE the task_id for potential continuation.**
### Code Changes:
- Match existing patterns (if codebase is disciplined)
diff --git a/src/agents/sisyphus/gpt-5-4.ts b/src/agents/sisyphus/gpt-5-4.ts
index 9e8219015..4667e3466 100644
--- a/src/agents/sisyphus/gpt-5-4.ts
+++ b/src/agents/sisyphus/gpt-5-4.ts
@@ -387,10 +387,10 @@ Post-delegation: delegation never substitutes for verification. Always run \` {
// given
const link = {
sessionId: "ses_bg_123",
- taskId: "bg_123",
+ taskId: "ses_bg_123",
backgroundTaskId: "bg_123",
agent: "explore",
category: "quick",
@@ -29,7 +29,7 @@ describe("buildTaskMetadataBlock", () => {
// then
expect(block).toBe(
- "\nsession_id: ses_bg_123\ntask_id: bg_123\nbackground_task_id: bg_123\nsubagent: explore\ncategory: quick\n"
+ "\nsession_id: ses_bg_123\ntask_id: ses_bg_123\nbackground_task_id: bg_123\nsubagent: explore\ncategory: quick\n"
)
})
})
diff --git a/src/hooks/atlas/verification-reminders.ts b/src/hooks/atlas/verification-reminders.ts
index b00618ecd..9bde55b9d 100644
--- a/src/hooks/atlas/verification-reminders.ts
+++ b/src/hooks/atlas/verification-reminders.ts
@@ -29,7 +29,7 @@ Your completion will NOT be recorded until you complete ALL of the following:
If anything fails while closing this out, resume the same session immediately:
\`\`\`typescript
-task(session_id="${sessionId}", load_skills=[], prompt="fix: checkbox not recorded correctly")
+task(task_id="${sessionId}", load_skills=[], prompt="fix: checkbox not recorded correctly")
\`\`\`
**Your completion is NOT tracked until the checkbox is marked in the plan file.**
@@ -47,7 +47,7 @@ ${VERIFICATION_REMINDER}
**If ANY verification fails, use this immediately:**
\`\`\`
-task(session_id="${sessionId}", load_skills=[], prompt="fix: [describe the specific failure]")
+task(task_id="${sessionId}", load_skills=[], prompt="fix: [describe the specific failure]")
\`\`\`
${buildReuseHint(sessionId)}`
diff --git a/src/hooks/keyword-detector/ultrawork/default.ts b/src/hooks/keyword-detector/ultrawork/default.ts
index 37790d9ea..0c95c5f1e 100644
--- a/src/hooks/keyword-detector/ultrawork/default.ts
+++ b/src/hooks/keyword-detector/ultrawork/default.ts
@@ -115,15 +115,15 @@ task(subagent_type="plan", load_skills=[], run_in_background=false, prompt=" {
await afterHook(input, output)
expect(output.output).toContain("to continue:")
+ expect(output.output).toContain('task(task_id="ses_abc123"')
expect(output.output).toContain("ses_abc123")
})
@@ -74,6 +75,7 @@ describe("createTaskResumeInfoHook", () => {
await afterHook(input, output)
expect(output.output).toContain("run_in_background=false")
+ expect(output.output).toContain('task_id="ses_abc123"')
})
})
})
@@ -120,7 +122,7 @@ describe("createTaskResumeInfoHook", () => {
const output = {
title: "task",
output:
- 'Done.\nSession ID: ses_abc123\nto continue: task(session_id="ses_abc123", load_skills=[], prompt="...")',
+ 'Done.\nSession ID: ses_abc123\nto continue: task(task_id="ses_abc123", load_skills=[], run_in_background=false, prompt="...")',
metadata: {},
}
diff --git a/src/plugin/tool-execute-before.test.ts b/src/plugin/tool-execute-before.test.ts
index 80daa79a8..f1f3a30b5 100644
--- a/src/plugin/tool-execute-before.test.ts
+++ b/src/plugin/tool-execute-before.test.ts
@@ -161,6 +161,23 @@ describe("createToolExecuteBeforeHandler", () => {
expect(output.args.subagent_type).toBe("explore")
})
+ test("normalizes task_id into the canonical resume argument", async () => {
+ //#given
+ const ctx = createCtxWithSessionMessages([
+ { info: { role: "assistant", agent: "oracle" } },
+ ])
+ const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks })
+ const input = { tool: "task", sessionID: "ses_123", callID: "call_1" }
+ const output = { args: { task_id: "ses_resume_123", description: "Continue task", prompt: "fix it" } as Record }
+
+ //#when
+ await handler(input, output)
+
+ //#then
+ expect(output.args.task_id).toBe("ses_resume_123")
+ expect(output.args.subagent_type).toBe("oracle")
+ })
+
test("falls back to 'continue' when session has no agent info", async () => {
//#given
const ctx = createCtxWithSessionMessages([
diff --git a/src/plugin/tool-execute-before.ts b/src/plugin/tool-execute-before.ts
index 68b1545ea..dcd47f222 100644
--- a/src/plugin/tool-execute-before.ts
+++ b/src/plugin/tool-execute-before.ts
@@ -100,12 +100,21 @@ export function createToolExecuteBeforeHandler(args: {
const argsObject = output.args
const category = typeof argsObject.category === "string" ? argsObject.category : undefined
const subagentType = typeof argsObject.subagent_type === "string" ? argsObject.subagent_type : undefined
- const sessionId = typeof argsObject.session_id === "string" ? argsObject.session_id : undefined
+ const taskId =
+ typeof argsObject.task_id === "string"
+ ? argsObject.task_id
+ : typeof argsObject.session_id === "string"
+ ? argsObject.session_id
+ : undefined
+
+ if (taskId && typeof argsObject.task_id !== "string") {
+ argsObject.task_id = taskId
+ }
if (category) {
argsObject.subagent_type = "sisyphus-junior"
- } else if (!subagentType && sessionId) {
- const resolvedAgent = await resolveSessionAgent(ctx.client, sessionId)
+ } else if (!subagentType && taskId) {
+ const resolvedAgent = await resolveSessionAgent(ctx.client, taskId)
argsObject.subagent_type = resolvedAgent ?? "continue"
}
diff --git a/src/tools/background-task/create-background-cancel.ts b/src/tools/background-task/create-background-cancel.ts
index 49fc4e5bb..a61c280f8 100644
--- a/src/tools/background-task/create-background-cancel.ts
+++ b/src/tools/background-task/create-background-cancel.ts
@@ -59,7 +59,7 @@ export function createBackgroundCancel(manager: BackgroundManager, _client: Back
To continue a cancelled task, use:
\`\`\`
-task(session_id="", prompt="Continue: ")
+task(task_id="", prompt="Continue: ")
\`\`\`
Continuable sessions:
diff --git a/src/tools/delegate-task/background-continuation.ts b/src/tools/delegate-task/background-continuation.ts
index dd0850439..becf732b9 100644
--- a/src/tools/delegate-task/background-continuation.ts
+++ b/src/tools/delegate-task/background-continuation.ts
@@ -3,6 +3,8 @@ import type { ExecutorContext, ParentContext } from "./executor-types"
import { publishToolMetadata } from "../../features/tool-metadata-store"
import { formatDetailedError } from "./error-formatting"
import { getSessionTools } from "../../shared/session-tools-store"
+import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract"
+import { getTaskID } from "./task-id"
export async function executeBackgroundContinuation(
args: DelegateTaskArgs,
@@ -13,8 +15,13 @@ export async function executeBackgroundContinuation(
const { manager } = executorCtx
try {
+ const taskID = getTaskID(args)
+ if (!taskID) {
+ throw new Error("task_id is required to continue a background task")
+ }
+
const task = await manager.resume({
- sessionId: args.session_id!,
+ sessionId: taskID,
prompt: args.prompt,
parentSessionID: parentContext.sessionID,
parentMessageID: parentContext.messageID,
@@ -31,6 +38,8 @@ export async function executeBackgroundContinuation(
load_skills: args.load_skills,
description: args.description,
run_in_background: args.run_in_background,
+ taskId: task.sessionID,
+ backgroundTaskId: task.id,
sessionId: task.sessionID,
command: args.command,
model: task.model ? { providerID: task.model.providerID, modelID: task.model.modelID } : undefined,
@@ -50,14 +59,17 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
Do NOT call background_output now. Wait for notification first.
-
-session_id: ${task.sessionID}
-${task.agent ? `subagent: ${task.agent}\n` : ""}`
+${buildTaskMetadataBlock({
+ sessionId: task.sessionID,
+ taskId: task.sessionID,
+ backgroundTaskId: task.id,
+ agent: task.agent,
+ })}`
} catch (error) {
return formatDetailedError(error, {
operation: "Continue background task",
args,
- sessionID: args.session_id,
+ sessionID: getTaskID(args),
})
}
}
diff --git a/src/tools/delegate-task/background-task.test.ts b/src/tools/delegate-task/background-task.test.ts
index 84a7bc644..3837dbb6e 100644
--- a/src/tools/delegate-task/background-task.test.ts
+++ b/src/tools/delegate-task/background-task.test.ts
@@ -104,11 +104,14 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () =>
//#then - output and metadata should include canonical session linkage
expectFn(result).toContain("")
expectFn(result).toContain("session_id: ses_sub_123")
- expectFn(result).toContain("task_id: bg_resolved")
+ expectFn(result).toContain("task_id: ses_sub_123")
expectFn(result).toContain("background_task_id: bg_resolved")
+ expectFn(result).toContain("subagent: explore")
expectFn(result).toContain("Background Task ID: bg_resolved")
expectFn(metadataCalls).toHaveLength(1)
expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_sub_123")
+ expectFn(metadataCalls[0].metadata.taskId).toBe("ses_sub_123")
+ expectFn(metadataCalls[0].metadata.backgroundTaskId).toBe("bg_resolved")
})
testFn("captures late-resolved session id and emits synced metadata", async () => {
@@ -152,10 +155,12 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () =>
//#then - late session id still propagates to task metadata contract
expectFn(result).toContain("session_id: ses_late_123")
- expectFn(result).toContain("task_id: bg_late")
+ expectFn(result).toContain("task_id: ses_late_123")
expectFn(result).toContain("background_task_id: bg_late")
expectFn(metadataCalls).toHaveLength(1)
expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_late_123")
+ expectFn(metadataCalls[0].metadata.taskId).toBe("ses_late_123")
+ expectFn(metadataCalls[0].metadata.backgroundTaskId).toBe("bg_late")
})
testFn("passes question-deny session permission when launching delegate task", async () => {
diff --git a/src/tools/delegate-task/background-task.ts b/src/tools/delegate-task/background-task.ts
index 73d43ad02..17b3bd632 100644
--- a/src/tools/delegate-task/background-task.ts
+++ b/src/tools/delegate-task/background-task.ts
@@ -10,6 +10,7 @@ import { SessionCategoryRegistry } from "../../shared/session-category-registry"
import { QUESTION_DENIED_SESSION_PERMISSION } from "../../shared/question-denied-session-permission"
import { setSessionFallbackChain } from "../../hooks/model-fallback/hook"
import { stripAgentListSortPrefix } from "../../shared/agent-display-names"
+import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract"
function continueSessionSetup(args: {
taskID: string
@@ -125,6 +126,8 @@ export async function executeBackgroundTask(
description: args.description,
run_in_background: args.run_in_background,
command: args.command,
+ ...(sessionId ? { taskId: sessionId } : {}),
+ backgroundTaskId: task.id,
...(sessionId ? { sessionId } : {}),
...(categoryModel ? { model: { providerID: categoryModel.providerID, modelID: categoryModel.modelID } } : {}),
}
@@ -136,7 +139,13 @@ export async function executeBackgroundTask(
await publishToolMetadata(ctx, unstableMeta)
const taskMetadataBlock = sessionId
- ? `\n\n\nsession_id: ${sessionId}\ntask_id: ${task.id}\nbackground_task_id: ${task.id}\n`
+ ? `\n\n${buildTaskMetadataBlock({
+ sessionId,
+ taskId: sessionId,
+ backgroundTaskId: task.id,
+ agent: task.agent,
+ category: args.category,
+ })}`
: ""
return `Background task launched.
diff --git a/src/tools/delegate-task/error-formatting.ts b/src/tools/delegate-task/error-formatting.ts
index f2c24abc5..9b7b98ab2 100644
--- a/src/tools/delegate-task/error-formatting.ts
+++ b/src/tools/delegate-task/error-formatting.ts
@@ -1,4 +1,5 @@
import type { DelegateTaskArgs } from "./types"
+import { getTaskID } from "./task-id"
/**
* Context for error formatting.
@@ -35,8 +36,9 @@ export function formatDetailedError(error: unknown, ctx: ErrorContext): string {
lines.push(`- subagent_type: ${ctx.args.subagent_type ?? "(none)"}`)
lines.push(`- run_in_background: ${ctx.args.run_in_background}`)
lines.push(`- load_skills: [${ctx.args.load_skills?.join(", ") ?? ""}]`)
- if (ctx.args.session_id) {
- lines.push(`- session_id: ${ctx.args.session_id}`)
+ const taskID = getTaskID(ctx.args)
+ if (taskID) {
+ lines.push(`- task_id: ${taskID}`)
}
}
diff --git a/src/tools/delegate-task/sync-continuation.ts b/src/tools/delegate-task/sync-continuation.ts
index a8e412ece..45b06ea88 100644
--- a/src/tools/delegate-task/sync-continuation.ts
+++ b/src/tools/delegate-task/sync-continuation.ts
@@ -12,6 +12,8 @@ import { syncContinuationDeps, type SyncContinuationDeps } from "./sync-continua
import { setSessionTools } from "../../shared/session-tools-store"
import { normalizeSDKResponse } from "../../shared"
import { buildTaskPrompt } from "./prompt-builder"
+import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract"
+import { getTaskID } from "./task-id"
export async function executeSyncContinuation(
args: DelegateTaskArgs,
@@ -21,7 +23,11 @@ export async function executeSyncContinuation(
): Promise {
const { client, syncPollTimeoutMs, sisyphusAgentConfig } = executorCtx
const toastManager = getTaskToastManager()
- const taskId = `resume_sync_${args.session_id!.slice(0, 8)}`
+ const continuationID = getTaskID(args)
+ if (!continuationID) {
+ throw new Error("task_id is required to continue a sync task")
+ }
+ const taskId = `resume_sync_${continuationID.slice(0, 8)}`
const startTime = new Date()
if (toastManager) {
@@ -42,7 +48,7 @@ export async function executeSyncContinuation(
try {
try {
- const messagesResp = await client.session.messages({ path: { id: args.session_id! } })
+ const messagesResp = await client.session.messages({ path: { id: continuationID } })
const messages = normalizeSDKResponse(messagesResp, [] as SessionMessage[])
anchorMessageCount = messages.length
for (let i = messages.length - 1; i >= 0; i--) {
@@ -55,7 +61,7 @@ export async function executeSyncContinuation(
}
}
} catch {
- const resumeMessageDir = getMessageDir(args.session_id!)
+ const resumeMessageDir = getMessageDir(continuationID)
const resumeMessage = resumeMessageDir ? findNearestMessageWithFields(resumeMessageDir) : null
resumeAgent = resumeMessage?.agent
resumeModel = resumeMessage?.model?.providerID && resumeMessage?.model?.modelID
@@ -71,7 +77,8 @@ export async function executeSyncContinuation(
load_skills: args.load_skills,
description: args.description,
run_in_background: args.run_in_background,
- sessionId: args.session_id,
+ taskId: continuationID,
+ sessionId: continuationID,
sync: true,
command: args.command,
model: resumeModel,
@@ -88,10 +95,10 @@ export async function executeSyncContinuation(
question: false,
...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}),
}
- setSessionTools(args.session_id!, tools)
+ setSessionTools(continuationID, tools)
await promptWithModelSuggestionRetry(client, {
- path: { id: args.session_id! },
+ path: { id: continuationID },
body: {
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
...(resumeModel !== undefined ? { model: resumeModel } : {}),
@@ -105,12 +112,12 @@ export async function executeSyncContinuation(
toastManager.removeTask(taskId)
}
const errorMessage = promptError instanceof Error ? promptError.message : String(promptError)
- return `Failed to send continuation prompt: ${errorMessage}\n\nSession ID: ${args.session_id}`
+ return `Failed to send continuation prompt: ${errorMessage}\n\nTask ID: ${continuationID}`
}
try {
const pollError = await deps.pollSyncSession(ctx, client, {
- sessionID: args.session_id!,
+ sessionID: continuationID,
agentToUse: resumeAgent ?? "continue",
toastManager,
taskId,
@@ -120,7 +127,7 @@ export async function executeSyncContinuation(
return pollError
}
- const result = await deps.fetchSyncResult(client, args.session_id!, anchorMessageCount)
+ const result = await deps.fetchSyncResult(client, continuationID, anchorMessageCount)
if (!result.ok) {
return result.error
}
@@ -133,9 +140,11 @@ export async function executeSyncContinuation(
${result.textContent || "(No text output)"}
-
-session_id: ${args.session_id}
-${resumeAgent ? `subagent: ${resumeAgent}\n` : ""}`
+${buildTaskMetadataBlock({
+ sessionId: continuationID,
+ taskId: continuationID,
+ agent: resumeAgent,
+ })}`
} finally {
if (toastManager) {
toastManager.removeTask(taskId)
diff --git a/src/tools/delegate-task/sync-task.ts b/src/tools/delegate-task/sync-task.ts
index 4ec84696c..6cd42bb93 100644
--- a/src/tools/delegate-task/sync-task.ts
+++ b/src/tools/delegate-task/sync-task.ts
@@ -11,6 +11,7 @@ import { formatDetailedError } from "./error-formatting"
import { syncTaskDeps, type SyncTaskDeps } from "./sync-task-deps"
import { setSessionFallbackChain, clearSessionFallbackChain } from "../../hooks/model-fallback/hook"
import { retrySyncPromptWithFallbacks } from "./sync-task-fallback"
+import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract"
export async function executeSyncTask(
args: DelegateTaskArgs,
@@ -122,6 +123,7 @@ export async function executeSyncTask(
load_skills: args.load_skills,
description: args.description,
run_in_background: args.run_in_background,
+ taskId: sessionID,
sessionId: sessionID,
sync: true,
spawnDepth: spawnContext.childDepth,
@@ -210,9 +212,12 @@ Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}${mod
${result.textContent || "(No text output)"}
-
-session_id: ${sessionID}
-`
+${buildTaskMetadataBlock({
+ sessionId: sessionID,
+ taskId: sessionID,
+ agent: agentToUse,
+ category: args.category,
+ })}`
} finally {
if (toastManager && taskId !== undefined) {
toastManager.removeTask(taskId)
diff --git a/src/tools/delegate-task/task-id.ts b/src/tools/delegate-task/task-id.ts
new file mode 100644
index 000000000..71ec223a6
--- /dev/null
+++ b/src/tools/delegate-task/task-id.ts
@@ -0,0 +1,5 @@
+import type { DelegateTaskArgs } from "./types"
+
+export function getTaskID(args: Pick): string | undefined {
+ return args.task_id ?? args.session_id
+}
diff --git a/src/tools/delegate-task/task-schema.test.ts b/src/tools/delegate-task/task-schema.test.ts
index 0a6f1c7ee..c50d175bc 100644
--- a/src/tools/delegate-task/task-schema.test.ts
+++ b/src/tools/delegate-task/task-schema.test.ts
@@ -41,6 +41,7 @@ function createDelegateTask(...args: Parameters