FN-7127: standardize task show tool naming

Unify task-show references so planning, triage, docs, and telemetry use fn_task_show consistently.

- Replace legacy fn_task_get guidance and examples with fn_task_show across agent prompts, mission planning, triage, and docs.
- Keep fn_task_get as a deprecated read-only recognition alias for historical or in-flight calls.
- Update tests and changesets to cover the unified tool name and usage telemetry.

Files changed:
 .changeset/FN-7118-shared-task-read-tools.md       |  2 +-
 .changeset/FN-7127-unify-task-show-tool-name.md    |  7 ++++++
 docs/cli-reference.md                              |  2 +-
 docs/missions.md                                   |  2 +-
 .../cli/skill/fusion/references/engine-tools.md    |  2 +-
 packages/core/src/__tests__/usage-events.test.ts   |  1 +
 packages/core/src/agent-prompts.ts                 | 12 +++++-----
 .../__tests__/milestone-slice-interview.test.ts    |  2 +-
 .../src/__tests__/mission-interview.test.ts        |  2 +-
 .../src/__tests__/planning-board-tools.test.ts     |  6 ++---
 .../dashboard/src/milestone-slice-interview.ts     |  4 ++--
 packages/dashboard/src/mission-interview.ts        |  2 +-
 packages/dashboard/src/planning-board-tools.ts     | 10 +++++---
 packages/dashboard/src/planning.ts                 |  2 +-
 .../src/__tests__/agent-task-read-tools.test.ts    | 28 +++++++++++++++-------
 packages/engine/src/__tests__/triage.test.ts       |  6 ++---
 packages/engine/src/agent-tools.ts                 |  5 +++-
 packages/engine/src/gating-classifications.ts      |  8 +++++--
 packages/engine/src/triage.ts                      | 12 ++++++----
 19 files changed, 74 insertions(+), 41 deletions(-)

Fusion-Task-Id: FN-7127

Fusion-Task-Lineage: adf5a127-2741-4c88-97b3-aab239ab3960

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-06-27 12:25:53 -07:00
parent 59803c243d
commit 3d26d4e74b
19 changed files with 73 additions and 40 deletions

View File

@@ -4,4 +4,4 @@
summary: Permanent and custom agents can list, show, and search tasks during heartbeat runs.
category: feature
dev: Adds shared read-only task tool factories (createTaskListTool/createTaskShowTool/createTaskSearchTool/createTaskReadTools), wires them into createSharedHeartbeatWorkTools, classifies fn_task_search/fn_task_get read-only, and adds cross-surface drift tests.
dev: Adds shared read-only task tool factories (createTaskListTool/createTaskShowTool/createTaskSearchTool/createTaskReadTools), wires them into createSharedHeartbeatWorkTools, classifies fn_task_search and the legacy task-get alias read-only, and adds cross-surface drift tests.

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: The task detail tool is now named fn_task_show consistently across triage, planning, chat, and CLI surfaces.
category: internal
dev: Renames the legacy fn_task_get registration to canonical fn_task_show in createTriageTools (engine) and createPlanningBoardTools (dashboard), updates all prompt references and the FN-7118 cross-surface drift test, and retains fn_task_get in BOTH READONLY_FN_TOOLS and COORDINATION_EXEMPT_TOOLS as a deprecated recognition alias for backward-compatible action-gate classification and analytics.

View File

@@ -495,7 +495,7 @@ Use planning mode to turn a rough idea into a triage task through an interactive
When supported by your configured runtime/model provider, planning sessions can also use builtin `WebSearch` and `WebFetch` tools for live context gathering.
Planning sessions also have read-only board tools: `fn_task_list` (list active backlog tasks) and `fn_task_get` (read full task details, including PROMPT.md) so interviews can avoid duplicate in-flight plans and anchor questions to existing work. `fn_task_list` output is bounded and falls back to a defensive formatter if the runtime task-list clamp helper is unavailable, so board reads return text instead of failing during ambient planning or heartbeat checks. `fn_task_list` also accepts `includeDeleted: true` to surface soft-deleted blockers when diagnosing stalled dependency chains, and `fn_task_show` now auto-falls back to include soft-deleted tasks with a `[SOFT-DELETED at ...]` marker.
Planning sessions also have read-only board tools: `fn_task_list` (list active backlog tasks) and `fn_task_show` (read full task details, including PROMPT.md) so interviews can avoid duplicate in-flight plans and anchor questions to existing work. `fn_task_list` output is bounded and falls back to a defensive formatter if the runtime task-list clamp helper is unavailable, so board reads return text instead of failing during ambient planning or heartbeat checks. `fn_task_list` also accepts `includeDeleted: true` to surface soft-deleted blockers when diagnosing stalled dependency chains, and `fn_task_show` now auto-falls back to include soft-deleted tasks with a `[SOFT-DELETED at ...]` marker.
```bash
fn task plan [description]

View File

@@ -244,7 +244,7 @@ The dashboard supports mission planning workflows where you can:
- Persisted missions with `interviewState: "in_progress"` remain visible as interview-styled mission cards in the main mission list so planning work does not disappear after reloads
- Resume in-progress mission interview sessions directly from separate transient session rows in the main missions list (`mission_interview` sessions in `generating`, `awaiting_input`, `error`, or `complete`) before a mission record is created; `complete` means the plan summary is ready for review/approval but has not been converted into a mission yet
- Mission interview closes are non-destructive: the modal now uses a single close action for header close, backdrop click, and Escape. Closing preserves the in-progress `mission_interview` session, and Missions re-fetches project-scoped transient rows (including on the mobile stacked Missions view) so resume/retry remains discoverable without losing persisted `interviewState: "in_progress"` mission cards. Resume-launched interview modals also expose a **Send to background** action that performs the same non-destructive park without cancelling the session. Deletion remains an explicit sidebar action.
- Mission interview, milestone interview, and slice interview agents have read-only board visibility via `fn_task_list` and `fn_task_get`, so they can reference active backlog context and avoid duplicating in-flight tasks while asking planning questions
- Mission interview, milestone interview, and slice interview agents have read-only board visibility via `fn_task_list` and `fn_task_show`, so they can reference active backlog context and avoid duplicating in-flight tasks while asking planning questions
### Mission Interview Drafts

View File

@@ -64,7 +64,7 @@ These tools are **not** part of the user-invokable extension surface. They are i
|---|---|---|
| `fn_task_list` | List active tasks during specification (duplicate check, discovery) | none |
| `fn_task_search` | Keyword search tasks (including done/archived by default) for duplicate detection | `query` (string), `limit?` (number), `includeDone?` (boolean), `includeArchived?` (boolean) |
| `fn_task_get` | Fetch full task detail including PROMPT.md | `id` (string) |
| `fn_task_show` | Fetch full task detail including PROMPT.md | `id` (string) |
| `fn_review_spec` | Spawn spec reviewer and return `APPROVE`/`REVISE`/`RETHINK`/`UNAVAILABLE` | none |
## Executor-only runtime tools (`executor.ts`)

View File

@@ -96,6 +96,7 @@ describe("usage_events", () => {
["semantic_search", "read"],
["fn_task_list", "read"],
["fn_task_show", "read"],
// FNXC:UsageAnalytics 2026-06-27-00:00: Keep legacy `fn_task_get` categorized as read-only so historical usage events remain comparable after live surfaces move to `fn_task_show`.
["fn_task_get", "read"],
["fn_task_search", "read"],
["fn_list_agents", "read"],

View File

@@ -372,12 +372,12 @@ If this task REMOVES existing functionality (deleting modules, settings, API end
## Duplicate check
Before writing a spec, call \`fn_task_list\` to find existing active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
For any likely match in \`done\` or \`archived\`, call \`fn_task_get\` to inspect details before deciding.
For any likely match in \`done\` or \`archived\`, call \`fn_task_show\` to inspect details before deciding.
If an existing task already covers the same work, do NOT write a PROMPT.md. Instead write exactly:
\`DUPLICATE: {existing-task-id}\`
## Dependency awareness
When adding a dependency in \`## Dependencies\`, first call \`fn_task_get\` for that task and read its PROMPT.md.
When adding a dependency in \`## Dependencies\`, first call \`fn_task_show\` for that task and read its PROMPT.md.
Use that context to align file paths, APIs, assumptions, and completion expectations. If the dependency has no PROMPT.md yet, note that explicitly.
## Decision-only task flag (noCommitsExpected)
@@ -610,13 +610,13 @@ tests. Manual verification is NOT a test.
## Duplicate check
Before writing a spec, first call \`fn_task_list\` to see active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
For any likely match in \`done\` or \`archived\`, call \`fn_task_get\` to inspect details before deciding.
For any likely match in \`done\` or \`archived\`, call \`fn_task_show\` to inspect details before deciding.
If a task already covers the same work (even if worded differently), do NOT
write a PROMPT.md. Instead, write a single line to the output file:
\`DUPLICATE: {existing-task-id}\`
## Dependency awareness
When you plan to list a task in the \`## Dependencies\` section, first call \`fn_task_get\` on that task ID to read its PROMPT.md.
When you plan to list a task in the \`## Dependencies\` section, first call \`fn_task_show\` on that task ID to read its PROMPT.md.
Use what you learn — file scope, APIs, patterns, completion criteria — to make the new spec accurate: reference the right paths, avoid conflicting assumptions, and describe what the dependency must deliver before this task starts.
If the dependency task has no PROMPT.md yet (not yet specified), note that in the Dependencies section.
@@ -656,7 +656,7 @@ For tasks you assess as Size M or L, consider whether splitting into 2-5 child t
You have these extra tools during triage:
- \`fn_task_list\` — list existing active tasks
- \`fn_task_search\` — keyword search across tasks, including done and archived tasks
- \`fn_task_get\` — inspect a task and its PROMPT.md
- \`fn_task_show\` — inspect a task and its PROMPT.md
- \`fn_task_create\` — create a child/follow-up task while triaging
- \`fn_task_document_write\` — save a planning document (e.g., key="plan")
- \`fn_task_document_read\` — read back a previously saved document
@@ -744,7 +744,7 @@ If the task targets a different task ID (audit, forensic walk, historical reconc
- Authoritative target-task artifacts live at the **project root**: \`<rootDir>/.fusion/tasks/{TARGET_ID}/\` (\`task.json\`, \`PROMPT.md\`, \`attachments/\`, agent logs).
- Authoritative task DB rows live at the **project root** SQLite file: \`<rootDir>/.fusion/fusion.db\` (WAL mode). Read via \`TaskStore\` APIs; do not instruct direct SQL surgery.
- \`.fusion/\` is gitignored, so a fresh worktree from \`main\` does **not** include \`.fusion/tasks/{TARGET_ID}/\` or \`.fusion/fusion.db\`. The running worktree's own \`.fusion/\` (if present) is scratch/session state for the running task only, not source of truth.
- Prefer \`fn_task_get\` / \`fn_task_list\` when the target task ID is known; fall back to project-root filesystem reads only when tools cannot provide needed evidence.
- Prefer \`fn_task_show\` / \`fn_task_list\` when the target task ID is known; fall back to project-root filesystem reads only when tools cannot provide needed evidence.
<!-- Frontend UX criteria are applied deterministically by packages/core/src/frontend-ux-policy.ts and mirror the "frontend-ux-design" reviewer persona in packages/core/src/types.ts. -->`;;

View File

@@ -365,7 +365,7 @@ describe("milestone-slice-interview module", () => {
const createFnAgentCallArg = await waitForCreateFnAgentOptions() as { customTools?: Array<{ name: string }> };
const customToolNames = createFnAgentCallArg.customTools?.map((tool) => tool.name) ?? [];
expect(customToolNames).toContain("fn_task_list");
expect(customToolNames).toContain("fn_task_get");
expect(customToolNames).toContain("fn_task_show");
expect(session?.targetId).toBe("ms-123");
expect(session?.targetTitle).toBe("Launch Platform");
expect(session?.missionContext).toBe("Mission: Launch Platform v2");

View File

@@ -912,7 +912,7 @@ describe("mission-interview module", () => {
expect(lastCall[0].builtinToolsAllowlist).toEqual(["WebSearch", "WebFetch"]);
const customToolNames = (lastCall[0].customTools as Array<{ name: string }> | undefined)?.map((tool) => tool.name) ?? [];
expect(customToolNames).toContain("fn_task_list");
expect(customToolNames).toContain("fn_task_get");
expect(customToolNames).toContain("fn_task_show");
});
it("uses override prompt when promptOverrides provided", async () => {

View File

@@ -73,7 +73,7 @@ describe("createPlanningBoardTools", () => {
expect(emptyResult.content[0]?.text).toBe("No active tasks.");
});
it("fn_task_get returns full details and not-found fallback", async () => {
it("fn_task_show returns full details and not-found fallback", async () => {
const store = createStoreMock({
getTask: vi.fn(async (id: string) => ({
id,
@@ -84,7 +84,7 @@ describe("createPlanningBoardTools", () => {
})) as TaskStore["getTask"],
});
const taskGet = createPlanningBoardTools(store).find((tool) => tool.name === "fn_task_get");
const taskGet = createPlanningBoardTools(store).find((tool) => tool.name === "fn_task_show");
expect(taskGet).toBeDefined();
const result = await taskGet!.execute("c3", { id: "FN-10" });
expect(result.content[0]?.text).toContain("ID: FN-10");
@@ -96,7 +96,7 @@ describe("createPlanningBoardTools", () => {
const notFoundStore = createStoreMock();
const missingResult = await createPlanningBoardTools(notFoundStore)
.find((tool) => tool.name === "fn_task_get")!
.find((tool) => tool.name === "fn_task_show")!
.execute("c4", { id: "FN-404" });
expect(missingResult.content[0]?.text).toBe("Task FN-404 not found.");
});

View File

@@ -171,7 +171,7 @@ A milestone represents a major phase or deliverable within a larger mission. Eac
## Board tools
- fn_task_list — list active tasks
- fn_task_get — read a task's full details and PROMPT.md
- fn_task_show — read a task's full details and PROMPT.md
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
## Response Format
@@ -220,7 +220,7 @@ A slice represents a focused work unit within a milestone that can be activated
## Board tools
- fn_task_list — list active tasks
- fn_task_get — read a task's full details and PROMPT.md
- fn_task_show — read a task's full details and PROMPT.md
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
## Response Format

View File

@@ -160,7 +160,7 @@ PREFER structured question types over free-text. This makes the interview faster
## Board tools
- fn_task_list — list active tasks
- fn_task_get — read a task's full details and PROMPT.md
- fn_task_show — read a task's full details and PROMPT.md
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
## Response Format

View File

@@ -83,8 +83,12 @@ export function createPlanningBoardTools(store: TaskStore): ToolDefinition[] {
},
};
const taskGet: ToolDefinition = {
name: "fn_task_get",
/**
* FNXC:AgentTooling 2026-06-27-00:00:
* Planning-board interviews must expose the task detail read tool as canonical `fn_task_show`, matching prompt text and the FN-7118 shared read-tool factory so every agent surface learns one model-visible show-tool name.
*/
const taskShow: ToolDefinition = {
name: "fn_task_show",
label: "Get Task",
description:
"Get full details of a specific task including its PROMPT.md content. " +
@@ -115,5 +119,5 @@ export function createPlanningBoardTools(store: TaskStore): ToolDefinition[] {
},
};
return [taskList, taskGet];
return [taskList, taskShow];
}

View File

@@ -215,7 +215,7 @@ When ready to complete, generate:
## Board tools
- fn_task_list — list active tasks
- fn_task_get — read a task's full details and PROMPT.md
- fn_task_show — read a task's full details and PROMPT.md
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
## Response Format

View File

@@ -140,21 +140,31 @@ describe("shared task read tools", () => {
}
});
it("pins per-surface task-read tool name parity", () => {
it("pins per-surface task-read tool name parity on canonical fn_task_show", () => {
const triageProcessor = new TriageProcessor(createStore() as never, "/tmp/fn-test");
const triageNames = toolNames((triageProcessor as unknown as { createTriageTools: (opts: unknown) => Array<{ name: string }> }).createTriageTools({
parentTaskId: "FN-TRIAGE",
allowTaskCreate: true,
createdSubtasksRef: { current: [] },
})).filter((name) => name.startsWith("fn_task_") && name !== "fn_task_create");
expect(triageNames).toEqual(["fn_task_list", "fn_task_search", "fn_task_get"]);
expect(toolNames(createPlanningBoardTools(createStore())).filter((name) => name.startsWith("fn_task_"))).toEqual([
"fn_task_list",
"fn_task_get",
]);
const surfaces = {
triage: triageNames,
planningBoard: toolNames(createPlanningBoardTools(createStore())).filter((name) => name.startsWith("fn_task_")),
cliExtension: extractRegisteredCliTaskReadNames(),
sharedFactory: toolNames(createTaskReadTools(createStore())),
};
expect(extractRegisteredCliTaskReadNames()).toEqual(["fn_task_list", "fn_task_show"]);
expect(toolNames(createTaskReadTools(createStore()))).toEqual(["fn_task_list", "fn_task_show", "fn_task_search"]);
expect(surfaces).toEqual({
triage: ["fn_task_list", "fn_task_search", "fn_task_show"],
planningBoard: ["fn_task_list", "fn_task_show"],
cliExtension: ["fn_task_list", "fn_task_show"],
sharedFactory: ["fn_task_list", "fn_task_show", "fn_task_search"],
});
const deprecatedGetName = ["fn_task", "get"].join("_");
for (const names of Object.values(surfaces)) {
expect(names).toContain("fn_task_show");
expect(names).not.toContain(deprecatedGetName);
}
});
});

View File

@@ -2286,7 +2286,7 @@ describe("taskCreate tool model inheritance", () => {
expect(toolNames).toContain("fn_task_create");
expect(toolNames).toContain("fn_task_list");
expect(toolNames).toContain("fn_task_search");
expect(toolNames).toContain("fn_task_get");
expect(toolNames).toContain("fn_task_show");
expect(tools).toHaveLength(4);
});
@@ -4494,7 +4494,7 @@ describe("TriageProcessor delegation tools", () => {
};
}
it("createTriageTools returns fn_task_list, fn_task_search, fn_task_get, fn_task_create (no delegation tools — those are in customTools)", () => {
it("createTriageTools returns fn_task_list, fn_task_search, fn_task_show, fn_task_create (no delegation tools — those are in customTools)", () => {
const store = createMockStore();
const processor = new TriageProcessor(store as any, "/tmp/root");
@@ -4507,7 +4507,7 @@ describe("TriageProcessor delegation tools", () => {
const toolNames = tools.map((t: any) => t.name);
expect(toolNames).toContain("fn_task_list");
expect(toolNames).toContain("fn_task_search");
expect(toolNames).toContain("fn_task_get");
expect(toolNames).toContain("fn_task_show");
expect(toolNames).toContain("fn_task_create");
// fn_list_agents and fn_delegate_task are added in customTools, not createTriageTools
expect(toolNames).not.toContain("fn_list_agents");

View File

@@ -1044,7 +1044,10 @@ function formatTaskSummaryLine(task: { id: string; column: string; title?: strin
/**
* FNXC:AgentTooling 2026-06-27-14:05:
* Shared read-only task discovery factories must return host-safe text and be reusable by triage, chat/planning, and heartbeat surfaces. Heartbeat agents now receive task read tools through this single store-backed implementation instead of bespoke copies, while model-visible legacy `fn_task_get` surfaces remain separately pinned by drift tests.
* Shared read-only task discovery factories must return host-safe text and be reusable by triage, chat/planning, and heartbeat surfaces. Heartbeat agents now receive task read tools through this single store-backed implementation instead of bespoke copies.
*
* FNXC:AgentTooling 2026-06-27-00:00:
* Triage and planning-board surfaces now use canonical `fn_task_show`; deprecated `fn_task_get` survives only as a recognition alias in action-gate and analytics compatibility paths, not as a model-visible registered tool.
*/
export function createTaskListTool(store: TaskStore): ToolDefinition {
return {

View File

@@ -118,8 +118,9 @@ export const READONLY_FN_TOOLS: ReadonlySet<string> = new Set([
"fn_artifact_view",
"fn_task_list",
"fn_task_show",
// FNXC:ToolGovernance 2026-06-27-14:16: Task search and legacy task-get surfaces are read-only duplicate-discovery tools; classify them positively so heartbeat/triage calls never rely on the unknown-tool exempt fallback.
// FNXC:ToolGovernance 2026-06-27-14:16: Task search is a read-only duplicate-discovery tool; classify it positively so heartbeat/triage calls never rely on the unknown-tool exempt fallback.
"fn_task_search",
// FNXC:ToolGovernance 2026-06-27-00:00: `fn_task_get` is a deprecated recognition-only alias. It is no longer registered as a live tool, but historical/in-flight calls must still classify as read-only instead of falling through to unknown-tool handling.
"fn_task_get",
"fn_task_create",
"fn_task_document_write",
@@ -172,7 +173,10 @@ export const COORDINATION_EXEMPT_TOOLS = [
"fn_task_document_read",
/**
* FNXC:ToolGovernance 2026-06-27-15:22:
* Task list/show/search/get are read-only discovery tools. Put them on the action-gate exempt registry, not only READONLY_FN_TOOLS, because evaluateAgentActionGate recognizes coordination exemptions directly and otherwise unknown fn_task_* reads silently fall through to exempt allow.
* Task list/show/search tools are read-only discovery tools. Put them on the action-gate exempt registry, not only READONLY_FN_TOOLS, because evaluateAgentActionGate recognizes coordination exemptions directly and otherwise unknown fn_task_* reads silently fall through to exempt allow.
*
* FNXC:ToolGovernance 2026-06-27-00:00:
* `fn_task_get` is no longer registered as a live tool, but it remains here as a deprecated recognition-only alias so stray in-flight legacy calls classify as known read-only coordination instead of relying on the unknown-tool fallback.
*/
"fn_task_list",
"fn_task_show",

View File

@@ -1575,8 +1575,12 @@ export class TriageProcessor {
},
};
const taskGet: ToolDefinition = {
name: "fn_task_get",
/**
* FNXC:AgentTooling 2026-06-27-00:00:
* Triage must expose the task detail read tool as canonical `fn_task_show`, matching prompt text and the FN-7118 shared read-tool factory so every agent surface learns one model-visible show-tool name.
*/
const taskShow: ToolDefinition = {
name: "fn_task_show",
label: "Get Task",
description:
"Get full details of a specific task including its PROMPT.md content. " +
@@ -1605,7 +1609,7 @@ export class TriageProcessor {
};
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
planLog.warn(`${options.parentTaskId}: fn_task_get lookup failed for ${params.id}: ${msg}`);
planLog.warn(`${options.parentTaskId}: fn_task_show lookup failed for ${params.id}: ${msg}`);
return {
content: [
{ type: "text" as const, text: `Task ${params.id} not found.` },
@@ -1743,7 +1747,7 @@ export class TriageProcessor {
},
};
return [taskList, taskSearch, taskGet, taskCreate];
return [taskList, taskSearch, taskShow, taskCreate];
}
/**