Skip to content

Commit e3eb155

Browse files
cyrusagentclaudePaytonWebber
authored
Enrich TaskUpdate/TaskGet activities with task subject (CYPACK-797) (#847)
* Enrich TaskUpdate/TaskGet activities with task subject from cache and result parsing TaskUpdate and TaskGet tool inputs don't include the task subject, causing Linear activities to show only "Task #3" without context. This change defers their activity posting from tool_use time to tool_result time, where subject can be sourced from: (1) a cache populated by TaskCreate results, or (2) parsing the Subject field from TaskGet result content. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add changelog entries for CYPACK-797 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Delete packages/edge-worker/test/AgentSessionManager.task-subject-enrichment.test.ts --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Payton Webber <53197664+PaytonWebber@users.noreply.github.com>
1 parent 1736ffe commit e3eb155

3 files changed

Lines changed: 105 additions & 11 deletions

File tree

CHANGELOG.internal.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This changelog documents internal development changes, refactors, tooling update
77
### Changed
88
- Refactored formatting strategy from TodoWrite to Task tools (TaskCreate, TaskUpdate, TaskList, TaskGet). Added `formatTaskParameter()` method to IMessageFormatter interface and updated AgentSessionManager to handle Task tools as thought activities. ([CYPACK-788](https://linear.app/ceedar/issue/CYPACK-788), [#837](https://github.com/ceedaragents/cyrus/pull/837))
99
- Redesigned TaskCreate formatting for parallel execution (concise `⏳ **subject**` checklist items), improved TaskUpdate/TaskGet to show subject names with status emojis, added ToolSearch formatting (`🔍 Loading`/`🔍 Searching tools`) rendered as non-ephemeral thought in AgentSessionManager, and added TaskOutput formatting (`📤 Waiting for`/`📤 Checking`). Updated both ClaudeMessageFormatter and GeminiMessageFormatter with matching logic. ([CYPACK-795](https://linear.app/ceedar/issue/CYPACK-795), [#846](https://github.com/ceedaragents/cyrus/pull/846))
10+
- Deferred TaskUpdate/TaskGet activity posting from tool_use time to tool_result time to enrich with task subject. Added `taskSubjectsByToolUseId` and `taskSubjectsById` caches to AgentSessionManager for subject resolution from TaskCreate results and TaskGet result parsing. ([CYPACK-797](https://linear.app/ceedar/issue/CYPACK-797), [#847](https://github.com/ceedaragents/cyrus/pull/847))
1011

1112
### Added
1213
- Subroutine result text is now stored in procedure history when advancing between subroutines. On error results (e.g. `error_max_turns` from single-turn subroutines), `AgentSessionManager` recovers by using the last completed subroutine's result via `ProcedureAnalyzer.getLastSubroutineResult()`, allowing the procedure to continue to completion instead of failing. Added `disallowAllTools` parameter to `buildAgentRunnerConfig` and `tools` config pass-through to `ClaudeRunner` for properly disabling built-in tools. ([CYPACK-792](https://linear.app/ceedar/issue/CYPACK-792), [#843](https://github.com/ceedaragents/cyrus/pull/843))

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
77
### Changed
88
- **Updated Claude SDK dependencies** - Updated `@anthropic-ai/claude-agent-sdk` to v0.2.34 and `@anthropic-ai/sdk` to v0.73.0. See [claude-agent-sdk changelog](https://github.com/anthropics/claude-agent-sdk-typescript/blob/main/CHANGELOG.md#v0234) for details. ([CYPACK-788](https://linear.app/ceedar/issue/CYPACK-788), [#837](https://github.com/ceedaragents/cyrus/pull/837))
99
- **Improved task and tool activity display** - Task creation now shows as concise checklist items instead of verbose multi-line entries, task status updates display the task name with status emoji, and tool search/background task output activities are now cleanly formatted. ([CYPACK-795](https://linear.app/ceedar/issue/CYPACK-795), [#846](https://github.com/ceedaragents/cyrus/pull/846))
10+
- **Task status updates now show task descriptions** - Task update and task detail activities now display the task subject alongside the task number (e.g., "Task #3 — Fix login bug") instead of just the number. ([CYPACK-797](https://linear.app/ceedar/issue/CYPACK-797), [#847](https://github.com/ceedaragents/cyrus/pull/847))
1011

1112
### Fixed
1213
- **Procedures no longer fail when a subroutine exits with an error** - When a single-turn subroutine hits the max turns limit, Cyrus now recovers by using the last successful subroutine's result, allowing the workflow to continue to completion instead of stopping mid-procedure. ([CYPACK-792](https://linear.app/ceedar/issue/CYPACK-792), [#843](https://github.com/ceedaragents/cyrus/pull/843))

packages/edge-worker/src/AgentSessionManager.ts

Lines changed: 103 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ export class AgentSessionManager extends EventEmitter {
9393
private activeTasksBySession: Map<string, string> = new Map(); // Maps session ID to active Task tool use ID
9494
private toolCallsByToolUseId: Map<string, { name: string; input: any }> =
9595
new Map(); // Track tool calls by their tool_use_id
96+
private taskSubjectsByToolUseId: Map<string, string> = new Map(); // Cache TaskCreate subjects by toolUseId until result arrives with task ID
97+
private taskSubjectsById: Map<string, string> = new Map(); // Cache task subjects by task ID (e.g., "1" → "Fix login bug")
9698
private activeStatusActivitiesBySession: Map<string, string> = new Map(); // Maps session ID to active compacting status activity ID
9799
private procedureAnalyzer?: ProcedureAnalyzer;
98100
private sharedApplicationServer?: SharedApplicationServer;
@@ -1014,8 +1016,91 @@ export class AgentSessionManager extends EventEmitter {
10141016
this.toolCallsByToolUseId.delete(entry.metadata.toolUseId);
10151017
}
10161018

1019+
// Handle TaskCreate results: cache the task ID → subject mapping
1020+
const baseToolName = toolName.replace("↪ ", "");
1021+
if (baseToolName === "TaskCreate" && entry.metadata?.toolUseId) {
1022+
const cachedSubject = this.taskSubjectsByToolUseId.get(
1023+
entry.metadata.toolUseId,
1024+
);
1025+
if (cachedSubject) {
1026+
// Parse task ID from result like "Task #1 created successfully: ..."
1027+
const taskIdMatch = toolResult.content?.match(/Task #(\d+)/);
1028+
if (taskIdMatch?.[1]) {
1029+
this.taskSubjectsById.set(taskIdMatch[1], cachedSubject);
1030+
}
1031+
this.taskSubjectsByToolUseId.delete(
1032+
entry.metadata.toolUseId!,
1033+
);
1034+
}
1035+
}
1036+
1037+
// Handle TaskUpdate/TaskGet results: post enriched thought with subject
1038+
if (baseToolName === "TaskUpdate" || baseToolName === "TaskGet") {
1039+
const formatter = session.agentRunner?.getFormatter();
1040+
if (!formatter) {
1041+
console.warn(
1042+
`[AgentSessionManager] No formatter available for session ${linearAgentActivitySessionId}`,
1043+
);
1044+
return;
1045+
}
1046+
1047+
// Try to enrich toolInput with subject from cache or result
1048+
const enrichedInput = { ...toolInput };
1049+
if (!enrichedInput.subject) {
1050+
const taskId = enrichedInput.taskId || "";
1051+
// First try: look up subject from our cache
1052+
const cachedSubject = this.taskSubjectsById.get(taskId);
1053+
if (cachedSubject) {
1054+
enrichedInput.subject = cachedSubject;
1055+
} else if (baseToolName === "TaskGet" && toolResult.content) {
1056+
// Second try: parse subject from TaskGet result content
1057+
// Format: "ID: 123\nSubject: Fix bug\nStatus: ..."
1058+
const subjectMatch =
1059+
toolResult.content.match(/^Subject:\s*(.+)$/m);
1060+
if (subjectMatch?.[1]) {
1061+
enrichedInput.subject = subjectMatch[1].trim();
1062+
// Also cache it for future TaskUpdate calls
1063+
if (taskId) {
1064+
this.taskSubjectsById.set(
1065+
taskId,
1066+
enrichedInput.subject,
1067+
);
1068+
}
1069+
}
1070+
} else if (
1071+
baseToolName === "TaskUpdate" &&
1072+
toolResult.content
1073+
) {
1074+
// Try to parse subject from TaskUpdate result content
1075+
// Format: "Updated task #3 subject" or may contain task details
1076+
const subjectMatch =
1077+
toolResult.content.match(/^Subject:\s*(.+)$/m);
1078+
if (subjectMatch?.[1]) {
1079+
enrichedInput.subject = subjectMatch[1].trim();
1080+
if (taskId) {
1081+
this.taskSubjectsById.set(
1082+
taskId,
1083+
enrichedInput.subject,
1084+
);
1085+
}
1086+
}
1087+
}
1088+
}
1089+
1090+
const formattedTask = formatter.formatTaskParameter(
1091+
baseToolName,
1092+
enrichedInput,
1093+
);
1094+
content = {
1095+
type: "thought",
1096+
body: formattedTask,
1097+
};
1098+
ephemeral = false;
1099+
break;
1100+
}
1101+
10171102
// Skip creating activity for TodoWrite/write_todos results since they already created a non-ephemeral thought
1018-
// Skip Task tool results (TaskCreate, TaskUpdate, etc.) since they already created a non-ephemeral thought
1103+
// Skip TaskCreate/TaskList results since they already created a non-ephemeral thought
10191104
// Skip ToolSearch results since they already created a non-ephemeral thought
10201105
// Skip AskUserQuestion results since it's custom handled via Linear's select signal elicitation
10211106
if (
@@ -1024,12 +1109,8 @@ export class AgentSessionManager extends EventEmitter {
10241109
toolName === "write_todos" ||
10251110
toolName === "TaskCreate" ||
10261111
toolName === "↪ TaskCreate" ||
1027-
toolName === "TaskUpdate" ||
1028-
toolName === "↪ TaskUpdate" ||
10291112
toolName === "TaskList" ||
10301113
toolName === "↪ TaskList" ||
1031-
toolName === "TaskGet" ||
1032-
toolName === "↪ TaskGet" ||
10331114
toolName === "ToolSearch" ||
10341115
toolName === "↪ ToolSearch" ||
10351116
toolName === "AskUserQuestion" ||
@@ -1129,12 +1210,7 @@ export class AgentSessionManager extends EventEmitter {
11291210
};
11301211
// TodoWrite/write_todos is not ephemeral
11311212
ephemeral = false;
1132-
} else if (
1133-
toolName === "TaskCreate" ||
1134-
toolName === "TaskUpdate" ||
1135-
toolName === "TaskList" ||
1136-
toolName === "TaskGet"
1137-
) {
1213+
} else if (toolName === "TaskCreate" || toolName === "TaskList") {
11381214
// Get formatter from runner
11391215
const formatter = session.agentRunner?.getFormatter();
11401216
if (!formatter) {
@@ -1156,6 +1232,22 @@ export class AgentSessionManager extends EventEmitter {
11561232
};
11571233
// Task tools are not ephemeral
11581234
ephemeral = false;
1235+
1236+
// Cache TaskCreate subject by toolUseId so we can map it to task ID when result arrives
1237+
if (
1238+
toolName === "TaskCreate" &&
1239+
toolInput?.subject &&
1240+
entry.metadata.toolUseId
1241+
) {
1242+
this.taskSubjectsByToolUseId.set(
1243+
entry.metadata.toolUseId,
1244+
toolInput.subject,
1245+
);
1246+
}
1247+
} else if (toolName === "TaskUpdate" || toolName === "TaskGet") {
1248+
// Skip posting at tool_use time — defer to tool_result time
1249+
// so we can enrich with subject from result or cache
1250+
return;
11591251
} else if (toolName === "ToolSearch") {
11601252
// Get formatter from runner
11611253
const formatter = session.agentRunner?.getFormatter();

0 commit comments

Comments
 (0)