refactor(task): align continuation ids with task_id
This commit is contained in:
@@ -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 = `<critical_overrides>
|
||||
- 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**
|
||||
</critical_overrides>`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
<session_continuity>
|
||||
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.
|
||||
</session_continuity>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -387,10 +387,10 @@ Post-delegation: delegation never substitutes for verification. Always run \`<ve
|
||||
|
||||
### Session continuity
|
||||
|
||||
Every \`task()\` returns a session_id. Use it for all follow-ups:
|
||||
- Failed/incomplete → \`session_id="{id}", prompt="Fix: {specific error}"\`
|
||||
- Follow-up → \`session_id="{id}", prompt="Also: {question}"\`
|
||||
- Multi-turn → always \`session_id\`, never start fresh
|
||||
Every \`task()\` returns a task_id. Use it for all follow-ups:
|
||||
- Failed/incomplete → \`task_id="{id}", prompt="Fix: {specific error}"\`
|
||||
- Follow-up → \`task_id="{id}", prompt="Also: {question}"\`
|
||||
- Multi-turn → always \`task_id\`, never start fresh
|
||||
|
||||
This preserves full context, avoids repeated exploration, saves 70%+ tokens.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("buildTaskMetadataBlock", () => {
|
||||
// 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(
|
||||
"<task_metadata>\nsession_id: ses_bg_123\ntask_id: bg_123\nbackground_task_id: bg_123\nsubagent: explore\ncategory: quick\n</task_metadata>"
|
||||
"<task_metadata>\nsession_id: ses_bg_123\ntask_id: ses_bg_123\nbackground_task_id: bg_123\nsubagent: explore\ncategory: quick\n</task_metadata>"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)}`
|
||||
|
||||
@@ -115,15 +115,15 @@ task(subagent_type="plan", load_skills=[], run_in_background=false, prompt="<gat
|
||||
|
||||
### SESSION CONTINUITY WITH PLAN AGENT (CRITICAL)
|
||||
|
||||
**Plan agent returns a session_id. USE IT for follow-up interactions.**
|
||||
**Plan agent returns a task_id. USE IT for follow-up interactions.**
|
||||
|
||||
| Scenario | Action |
|
||||
|----------|--------|
|
||||
| Plan agent asks clarifying questions | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="<your answer>")\` |
|
||||
| Need to refine the plan | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="Please adjust: <feedback>")\` |
|
||||
| Plan needs more detail | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="Add more detail to Task N")\` |
|
||||
| Plan agent asks clarifying questions | \`task(task_id="{returned_task_id}", load_skills=[], run_in_background=false, prompt="<your answer>")\` |
|
||||
| Need to refine the plan | \`task(task_id="{returned_task_id}", load_skills=[], run_in_background=false, prompt="Please adjust: <feedback>")\` |
|
||||
| Plan needs more detail | \`task(task_id="{returned_task_id}", load_skills=[], run_in_background=false, prompt="Add more detail to Task N")\` |
|
||||
|
||||
**WHY SESSION_ID IS CRITICAL:**
|
||||
**WHY TASK_ID IS CRITICAL:**
|
||||
- Plan agent retains FULL conversation context
|
||||
- No repeated exploration or context gathering
|
||||
- Saves 70%+ tokens on follow-ups
|
||||
@@ -134,7 +134,7 @@ task(subagent_type="plan", load_skills=[], run_in_background=false, prompt="<gat
|
||||
task(subagent_type="plan", load_skills=[], run_in_background=false, prompt="Here's more info...")
|
||||
|
||||
// CORRECT: Resume preserves everything
|
||||
task(session_id="ses_abc123", load_skills=[], run_in_background=false, prompt="Here's my answer to your question: ...")
|
||||
task(task_id="ses_abc123", load_skills=[], run_in_background=false, prompt="Here's my answer to your question: ...")
|
||||
\`\`\`
|
||||
|
||||
**FAILURE TO CALL PLAN AGENT = INCOMPLETE WORK.**
|
||||
|
||||
@@ -161,13 +161,13 @@ task(subagent_type="plan", load_skills=[], run_in_background=false, prompt="<gat
|
||||
|
||||
### SESSION CONTINUITY WITH PLAN AGENT (CRITICAL)
|
||||
|
||||
**Plan agent returns a session_id. USE IT for follow-up interactions.**
|
||||
**Plan agent returns a task_id. USE IT for follow-up interactions.**
|
||||
|
||||
| Scenario | Action |
|
||||
|----------|--------|
|
||||
| Plan agent asks clarifying questions | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="<your answer>")\` |
|
||||
| Need to refine the plan | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="Please adjust: <feedback>")\` |
|
||||
| Plan needs more detail | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="Add more detail to Task N")\` |
|
||||
| Plan agent asks clarifying questions | \`task(task_id="{returned_task_id}", load_skills=[], run_in_background=false, prompt="<your answer>")\` |
|
||||
| Need to refine the plan | \`task(task_id="{returned_task_id}", load_skills=[], run_in_background=false, prompt="Please adjust: <feedback>")\` |
|
||||
| Plan needs more detail | \`task(task_id="{returned_task_id}", load_skills=[], run_in_background=false, prompt="Add more detail to Task N")\` |
|
||||
|
||||
**FAILURE TO CALL PLAN AGENT = INCOMPLETE WORK.**
|
||||
|
||||
|
||||
@@ -12,12 +12,13 @@ export function createTaskResumeInfoHook() {
|
||||
if (outputText.startsWith("Error:") || outputText.startsWith("Failed")) return
|
||||
if (outputText.includes("\nto continue:")) return
|
||||
|
||||
const sessionId = extractTaskLink(output.metadata, outputText).sessionId
|
||||
if (!sessionId) return
|
||||
const link = extractTaskLink(output.metadata, outputText)
|
||||
const taskId = link.taskId ?? link.sessionId
|
||||
if (!taskId) return
|
||||
|
||||
output.output =
|
||||
outputText.trimEnd() +
|
||||
`\n\nto continue: task(session_id="${sessionId}", load_skills=[], run_in_background=false, prompt="...")`
|
||||
`\n\nto continue: task(task_id="${taskId}", load_skills=[], run_in_background=false, prompt="...")`
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -60,6 +60,7 @@ describe("createTaskResumeInfoHook", () => {
|
||||
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: {},
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string, unknown> }
|
||||
|
||||
//#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([
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export function createBackgroundCancel(manager: BackgroundManager, _client: Back
|
||||
|
||||
To continue a cancelled task, use:
|
||||
\`\`\`
|
||||
task(session_id="<session_id>", prompt="Continue: <your follow-up>")
|
||||
task(task_id="<task_id>", prompt="Continue: <your follow-up>")
|
||||
\`\`\`
|
||||
|
||||
Continuable sessions:
|
||||
|
||||
@@ -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 <system-reminder> notification first.
|
||||
|
||||
<task_metadata>
|
||||
session_id: ${task.sessionID}
|
||||
${task.agent ? `subagent: ${task.agent}\n` : ""}</task_metadata>`
|
||||
${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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,11 +104,14 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () =>
|
||||
//#then - output and metadata should include canonical session linkage
|
||||
expectFn(result).toContain("<task_metadata>")
|
||||
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 () => {
|
||||
|
||||
@@ -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<task_metadata>\nsession_id: ${sessionId}\ntask_id: ${task.id}\nbackground_task_id: ${task.id}\n</task_metadata>`
|
||||
? `\n\n${buildTaskMetadataBlock({
|
||||
sessionId,
|
||||
taskId: sessionId,
|
||||
backgroundTaskId: task.id,
|
||||
agent: task.agent,
|
||||
category: args.category,
|
||||
})}`
|
||||
: ""
|
||||
|
||||
return `Background task launched.
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string> {
|
||||
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)"}
|
||||
|
||||
<task_metadata>
|
||||
session_id: ${args.session_id}
|
||||
${resumeAgent ? `subagent: ${resumeAgent}\n` : ""}</task_metadata>`
|
||||
${buildTaskMetadataBlock({
|
||||
sessionId: continuationID,
|
||||
taskId: continuationID,
|
||||
agent: resumeAgent,
|
||||
})}`
|
||||
} finally {
|
||||
if (toastManager) {
|
||||
toastManager.removeTask(taskId)
|
||||
|
||||
@@ -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)"}
|
||||
|
||||
<task_metadata>
|
||||
session_id: ${sessionID}
|
||||
</task_metadata>`
|
||||
${buildTaskMetadataBlock({
|
||||
sessionId: sessionID,
|
||||
taskId: sessionID,
|
||||
agent: agentToUse,
|
||||
category: args.category,
|
||||
})}`
|
||||
} finally {
|
||||
if (toastManager && taskId !== undefined) {
|
||||
toastManager.removeTask(taskId)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { DelegateTaskArgs } from "./types"
|
||||
|
||||
export function getTaskID(args: Pick<DelegateTaskArgs, "task_id" | "session_id">): string | undefined {
|
||||
return args.task_id ?? args.session_id
|
||||
}
|
||||
@@ -41,6 +41,7 @@ function createDelegateTask(...args: Parameters<typeof import("./tools").createD
|
||||
|
||||
//#then
|
||||
expect(description).toContain("subagent_type: Use specific agent directly")
|
||||
expect(description).toContain("task_id: Existing task to continue")
|
||||
expect(description).not.toContain("sisyphus")
|
||||
expect(description).not.toContain("hephaestus")
|
||||
expect(description).not.toContain("prometheus")
|
||||
|
||||
@@ -84,13 +84,14 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini
|
||||
${categoryList}
|
||||
- subagent_type: Use specific agent directly (explore, librarian, oracle, metis, momus)
|
||||
- run_in_background: REQUIRED. true=async (returns task_id), false=sync (waits). Use background=true ONLY for parallel exploration with 5+ independent queries.
|
||||
- session_id: Existing Task session to continue (from previous task output). Continues agent with FULL CONTEXT PRESERVED - saves tokens, maintains continuity.
|
||||
- task_id: Existing task to continue (from previous task output). Continues the same subagent session with FULL CONTEXT PRESERVED.
|
||||
- session_id: Deprecated alias for task_id. Accepted for backward compatibility.
|
||||
- command: The command that triggered this task (optional, for slash command tracking).
|
||||
|
||||
**WHEN TO USE session_id:**
|
||||
- Task failed/incomplete → session_id with "fix: [specific issue]"
|
||||
- Need follow-up on previous result → session_id with additional question
|
||||
- Multi-turn conversation with same agent → always session_id instead of new task
|
||||
**WHEN TO USE task_id:**
|
||||
- Task failed/incomplete → task_id with "fix: [specific issue]"
|
||||
- Need follow-up on previous result → task_id with additional question
|
||||
- Multi-turn conversation with same agent → always task_id instead of new task
|
||||
|
||||
Prompts MUST be in English.`
|
||||
|
||||
@@ -103,11 +104,15 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini
|
||||
run_in_background: tool.schema.boolean().describe("REQUIRED. true=async (returns task_id), false=sync (waits). Use false for task delegation, true ONLY for parallel exploration."),
|
||||
category: tool.schema.string().optional().describe(`REQUIRED if subagent_type not provided. Do NOT provide both category and subagent_type.`),
|
||||
subagent_type: tool.schema.string().optional().describe("REQUIRED if category not provided. Do NOT provide both category and subagent_type."),
|
||||
session_id: tool.schema.string().optional().describe("Existing Task session to continue"),
|
||||
task_id: tool.schema.string().optional().describe("Existing task to continue. Canonical resume identifier."),
|
||||
session_id: tool.schema.string().optional().describe("Deprecated alias for task_id. Existing task to continue."),
|
||||
command: tool.schema.string().optional().describe("The command that triggered this task"),
|
||||
},
|
||||
async execute(args: DelegateTaskArgs, toolContext) {
|
||||
const ctx = toolContext as ToolContextWithMetadata
|
||||
if (!args.task_id && args.session_id) {
|
||||
args.task_id = args.session_id
|
||||
}
|
||||
|
||||
if (args.category) {
|
||||
if (args.subagent_type && args.subagent_type !== SISYPHUS_JUNIOR_AGENT) {
|
||||
@@ -158,7 +163,7 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini
|
||||
|
||||
const parentContext = await resolveParentContext(ctx, options.client)
|
||||
|
||||
if (args.session_id) {
|
||||
if (args.task_id || args.session_id) {
|
||||
if (runInBackground) {
|
||||
return executeBackgroundContinuation(args, ctx, options, parentContext)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ export interface DelegateTaskArgs {
|
||||
category?: string
|
||||
subagent_type?: string
|
||||
run_in_background: boolean
|
||||
task_id?: string
|
||||
/** @deprecated Use task_id instead. */
|
||||
session_id?: string
|
||||
command?: string
|
||||
load_skills: string[]
|
||||
|
||||
Reference in New Issue
Block a user