Skip to content

[Feature] Rename and rewrite task as subagent #128

@Astro-Han

Description

@Astro-Han

What task are you trying to do?

Rename the helper tool currently registered as task to agent. The new name is consistent with Claude Code, Cursor, Codex CLI conventions; it removes the overlap with todowrite semantics; and it now fits because #239 has freed the user-visible primary-agent surface.

This issue picks up after #239 closed (PR #242 merged 2026-04-26) which already removed the visible primary-agent picker AND landed two prerequisite cleanups in task.txt (commit 19236a8):

  • removed the "Launch multiple agents concurrently" guidance
  • removed the fictional greeting-responder / code-reviewer examples

What do you do today?

The model-facing tool id is still task. Its parameter is task_id. The permission namespace key is "task". The settings UI translates this as "Task" / "任务". The tool's output wraps subagent text in a <task_result> XML tag. pawwork.txt does not yet define how question / todowrite / agent / skill divide responsibilities, so weak models delegate work that should stay inline or open a checklist for one-shot answers.

Beyond the user-visible surface, task is wired into the codebase as a structural slot name (registry state field, named() return shape, recursion-guard map key, permission config schema field, multiple model-facing prompt strings, references inside other tool descriptions, references inside the agent generator template, the actual subagent renderer in packages/ui/src/components/message-part.tsx, the inline icon registry, and dev-tool fixtures). A naive rename of the tool id alone would compile but crash at runtime on first subagent dispatch, AND would cause new sessions' subagent cards to render as generic tool cards instead of the special subagent UI.

What would a good result look like?

The model sees a single tool named agent with parameter subagent_session_id (for resuming the prior subagent session) and subagent_type (kept; preserves Claude-Code-aligned training signal). Settings shows "Subagent" / "子智能体". pawwork.txt ships a # Tool collaboration section that tells the model when to use each of the four helpers and when to keep work in the main thread.

Old sessions stored on disk still render their subagent timeline correctly because every renderer surface accepts both task and agent tool names: the desktop subagent renderer in packages/ui/src/components/message-part.tsx is dual-registered under both names with shared render logic and OR-matching internal filters, the timeline helper in packages/app/src/pages/session/message-timeline.tsx OR-matches, and the CLI renderer in packages/opencode/src/cli/cmd/run.ts OR-matches both branches. Cross-tool prompt references (in glob.txt, grep.txt, the agent generator template, and the truncation hint string) all say "the agent tool" rather than "the Task tool". The tool's output now wraps subagent text in <subagent_result> rather than <task_result>. Existing tests that reference the tool by string id are updated; the icon task is renamed to agent for consistency.

There is no task LLM-facing alias and no JSON migration: PawWork's user base at this point is small enough that hard-cutting the tool id, the permission namespace, and the user-config schema field is acceptable, while renderer-layer OR-match preserves historical UI fidelity at modest line cost per render path.

Which audience does this matter to most?

Both. Non-technical users get clearer setting copy and unchanged historical session display. Technical users coming from Claude Code / Cursor / Codex see the same tool name they already know.

Design

Full design and decisions are in the comments below. The latest comment (v5, post-fourth-crosscheck) supersedes earlier comments with tightened spec wording, the <subagent_result> rename, expanded sweep walk into packages/app/, and per-line marker placement guidance. Acceptance criteria below are aligned with the v5 design. Note: line numbers in this body are accurate at HEAD 701f18a05d; expect minor drift on rebase. Regenerate the surface line numbers before opening the implementation PR.

Acceptance criteria — Tool files

  • packages/opencode/src/tool/task.ts and task.txt are renamed to agent.ts / agent.txt. task.ts and task.txt no longer exist. The import DESCRIPTION from "./task.txt" line in the renamed file points at ./agent.txt.
  • The TS interface is renamed: TaskToolAgentTool, TaskPromptOpsAgentPromptOps. Effect.fn("TaskTool.execute") is renamed to "AgentTool.execute". All importers are updated.
  • The tool id constant is "agent". The Zod parameter schema renames task_id to subagent_session_id; subagent_type is unchanged. The parameter description string is rewritten to use the new name and to include the "subagent dispatch" disambiguation phrase, mirroring the boundary section.
  • The recursion-guard map key in the tool body changes from { task: false } to { agent: false }.
  • Local camelCase identifiers taskID and canTask inside the renamed agent.ts are renamed to agentSessionID and canAgent so the renamed file does not retain confusing legacy terminology.
  • The model-facing output string task_id: ${nextSession.id} (for resuming...) is rewritten to use subagent_session_id and "this subagent" wording.
  • The model-facing XML wrapper around subagent text output (<task_result>...</task_result>) is renamed to <subagent_result>...</subagent_result> to keep the output consistent with the new tool name.
  • agent.txt self-references say "the agent tool" rather than "the Task tool"; the few-shot example Task(description=..., prompt="/check-file ...") is rewritten to Agent(description=..., prompt="/check-file ..."); the Usage notes references to task_id are rewritten to subagent_session_id.

Acceptance criteria — Tool registry

  • packages/opencode/src/tool/registry.ts renames the structural slot from task to agent across all of: import name (TaskToolAgentTool), local type alias (TaskDefAgentDef), State.task field, Interface.named() return shape { task: ... }{ agent: ... }, local binding const task = yield* TaskTool, Tool.init(task) slot key, two tool.task member accesses, the final return { task: s.task, ... }, and the tool.id === TaskTool.id comparison.
  • packages/opencode/src/tool/registry.ts:310 uses Permission.evaluate("agent", item.name, ...). No occurrence of Permission.evaluate("task", ...) remains.

Acceptance criteria — Session prompt + truncate + CLI

  • packages/opencode/src/session/prompt.ts updates: import name; satisfies AgentPromptOps; SubtaskPart field name tasksubtask in destructure and call sites at handleSubtask input/binding/usage; three tool: TaskTool.id writes to subtask part metadata; Permission.evaluate("agent", ...) namespace; the synthetic text strings at lines ~740 and ~1283 ("the task tool output", "call the task tool with subagent: ...") are rewritten to use "the agent tool".
  • packages/opencode/src/tool/truncate.ts renames hasTaskToolhasAgentTool; updates the evaluate("agent", "*", ...) namespace at the recursion-detection check; the call site reference is updated; the model-facing truncation hint string at line ~114 ("Use the Task tool to have explore agent process this file...") is rewritten to say "Use the agent tool".
  • packages/opencode/src/cli/cmd/agent.ts:18 AVAILABLE_TOOLS list replaces "task" with "agent".
  • packages/opencode/src/cli/cmd/run.ts updates: import name; the local function task(...) is renamed to function agent(...); both part.tool === "task" branches (the dispatch branch and the running-status branch) are widened to OR-match "task" and "agent". Each widened conditional carries an inline // agent-rename:legacy-render comment marker on the same line so the regression sweep allowlist excludes that exact line, not the surrounding block.

Acceptance criteria — Cross-tool prompt references and agent generator

  • packages/opencode/src/tool/glob.txt:5 line "use the Task tool instead" is rewritten to "use the agent tool instead".
  • packages/opencode/src/tool/grep.txt:8 same rewrite.
  • packages/opencode/src/agent/generate.txt:44 + 51 two references inside the agent-generator few-shot example ("use the Task tool to launch...") are rewritten to "use the agent tool to launch...". This is the highest-impact prompt rewrite because the file is a template for generated agents; leaving the legacy name pollutes every future generated agent.

Acceptance criteria — Subagent renderer (packages/ui/src/components/message-part.tsx)

This is the actual subagent-card renderer shared by desktop and web. v5 uses dual registration + internal OR-match so both old and new sessions render with the full subagent UI (folded card, agent type label, link to subagent session).

  • Line 1746: keep the existing ToolRegistry.register({ name: "task", render }) and add a second ToolRegistry.register({ name: "agent", render }) immediately after. Both calls pass the SAME render identifier reference (not two distinct function literals), so reactive scopes (createMemo, signals) are shared and the two registrations are observationally identical for any tool id.
  • Line 364 case "task": switch branch (in the tool-info builder that decides icon/title/subtitle) is widened so both "task" and "agent" produce the same tool-info object — either via fallthrough case "task":\ncase "agent": { ... } or via duplicate case bodies. The shared body is unchanged.
  • Lines 1324, 1329, 1333: three if (part().tool !== "task") return ... early-return guards in the taskId / taskHref / taskSubtitle reactive memos are widened to OR-match: if (part().tool !== "task" && part().tool !== "agent") return ....
  • Each widened conditional and the two register calls carry an inline // agent-rename:legacy-render comment marker on the same line (or directly above the line for register calls), so the regression sweep allowlist excludes those exact lines.

Acceptance criteria — Icon rename (consistency with tool name)

The inline icon registry at packages/ui/src/components/icon.tsx:50 defines task: '<g transform=...>...</g>' (an inline SVG). Rename the icon key from task to agent so the renamed tool uses an identically-named icon.

  • packages/ui/src/components/icon.tsx:50 key task: '<g...>...'agent: '<g...>...' (SVG content unchanged; only the registry key changes).
  • packages/ui/src/components/icon.stories.tsx:70 icon-name string "task" in the icons-list array → "agent".
  • packages/ui/src/components/message-part.tsx:370 icon: "task"icon: "agent".
  • packages/ui/src/components/message-part.tsx:1814 icon="task"icon="agent".
  • The icon SVG file does not exist as a separate asset (icons are inline strings in icon.tsx), so no asset rename is needed.

Acceptance criteria — Config + UI + dev fixtures

  • packages/opencode/src/config/permission.ts:42 schema field task: Schema.optional(Rule) is renamed to agent: Schema.optional(Rule). Existing user configs containing permission.task are accepted by the schema's Schema.StructWithRest(...) rest pattern (which falls through any unknown key into Record(String, Rule)) and then silently ignored at runtime since the new code never reads permission.task. PawWork user base is <20 and the field is UI-driven; no migration is added. The schema field rename and the i18n key removal MUST land in the same PR; out-of-order land (UI ships before schema) breaks the settings page reading the legacy key.
  • packages/app/src/pages/session/message-timeline.tsx:72 matches both part.tool === "task" and part.tool === "agent" with an inline // agent-rename:legacy-render comment marker.
  • packages/app/src/i18n/zh.ts and en.ts define settings.permissions.tool.agent.{title,description} with values "子智能体" / "启动子智能体" (zh) and "Subagent" / "Launch a subagent" (en). The legacy settings.permissions.tool.task.* keys are removed.
  • packages/ui/src/components/timeline-playground.stories.tsx:355-356 updates the playground fixture: BOTH the dictionary key task: { ... } at L355 (the playground indexes fixtures by tool id) AND the tool: "task" value at L356 are renamed to agent / "agent". If the fixture is intentionally kept as a legacy-render demo, rename to agent AND keep a separate fixture entry under key task: with tool: "task" and an inline // agent-rename:legacy-render marker.

Acceptance criteria — Existing tests

  • packages/opencode/test/session/prompt-effect.test.ts (and any other existing tests under packages/opencode/test/ that reference the tool by string id) updates: every llm.tool("task", { ... }) call switches to llm.tool("agent", ...); every part.tool === "task" predicate either switches to "agent" or widens to OR-match both names where the test fixture explicitly exercises legacy-session compatibility (with an inline // agent-rename:legacy-render marker on widened lines).

Acceptance criteria — Boundary section in pawwork.txt

  • packages/opencode/src/session/prompt/pawwork.txt gains a # Tool collaboration section, inserted between # Work style and # Communication, with the four-tool boundary text agreed in the v5 design comment. The section uses agent (subagent dispatch): (not bare agent:) to disambiguate from PawWork's product-level "agent" concept, and the todowrite rule includes a negative example covering trivial multi-step chores.

Acceptance criteria — Regression tests

  • packages/opencode/test/tool/agent-rename.test.ts (new file) asserts via fs walk + regex over packages/opencode/src/, packages/opencode/test/, packages/ui/src/, packages/app/src/, and packages/app/test/ (excluding the test file itself and any line annotated with an inline // agent-rename:legacy-render marker on that exact line). Patterns:
    • Case-sensitive (TS identifiers, walk-wide): TaskTool, TaskPromptOps, TaskDef, task_id, Permission\.evaluate\(\s*["']task["'], evaluate\(\s*["']task["'], tool\.task\b, s\.task\b, \{\s*task:\s*false\s*\}, Tool\.init\(task\)
    • Case-sensitive, scoped to the renamed agent.ts only: \bcanTask\b, \btaskID\b (legacy local-variable names; intentionally not flagged outside agent.ts to avoid false positives on unrelated identifiers)
    • Case-insensitive (English prose, walk-wide, exclude lines containing agent-rename:legacy-render marker): Task tool (subsumes "the task tool" and "task tool output" since case-insensitive matching makes the leading article and trailing word irrelevant), Task\(description=. The regex sweep treats overlapping prose patterns as a single match per line, not multiple.
    • XML wrapper assertion: source tree contains no <task_result> or </task_result> strings.
    • Tool API asserts: AgentTool.id === "agent"; Zod schema has subagent_session_id and no task_id; agent.ts exists; task.ts does not.
  • packages/app/test/i18n/agent-rename.test.ts (new file) asserts the new settings.permissions.tool.agent.* keys exist with the agreed values and the legacy task.* keys are gone (both zh and en).
  • packages/ui/test/components/icon-registry-rename.test.ts (new file) asserts that the icon registry exported from icon.tsx includes the key agent and does NOT include the key task. Locks the icon rename against future drift independent of the broader literal sweep.
  • packages/ui/test/components/message-part-rename.test.ts (or equivalent location) asserts: a synthetic message part with tool: "task" and one with tool: "agent" both render identically through the ToolRegistry.render path. The assertion checks not just that the renderer returns a non-fallback element but also that the derived tool-info properties (icon name, title, subtitle) match between the two parts, locking the dual-register path AND the four OR-match filter sites AND the case branch widening.
  • packages/app/test/session/message-timeline-rename.test.ts (new file) asserts the timeline helper accepts both names dual-render style.
  • packages/opencode/test/cli/run-rename.test.ts (or merge into existing CLI test directory) asserts the CLI render path at cli/cmd/run.ts:420 + 475-480 accepts both names dual-render style.

Out of scope

  • Plan tool ([Feature] Replace visible Plan Mode with lightweight plan approval tool #127): unblocked by this rename in the boundary section (the section leaves a slot for it), but designed and shipped separately.
  • Default global AGENTS.md content ([Feature] Improve PawWork system prompt and instruction fallback order #230): consumes the stable model-facing surface this issue produces.
  • Refactoring Tool.define / ToolRegistry to support id aliases natively: explicitly rejected. The dual-register approach in message-part.tsx is one extra register call sharing the same render reference; supporting aliases in the framework would create upstream sync debt.
  • Introducing a shared isAgentDispatch(part) helper across packages: explicitly rejected. The 7 OR-match call sites are one-off rename compatibility code; introducing a cross-package shared file (with its own tsconfig path setup) is heavier than 7 inline conditionals each carrying a marker.
  • Migrating historical session JSON tool: "task" strings to agent: explicitly rejected. Renderer-layer OR-match (desktop renderer + timeline helper + CLI renderer) achieves the same user-visible outcome.
  • Migrating historical user permission configs that contain permission.task: explicitly rejected. Field is UI-driven and recovers by re-toggling once.
  • Renaming the taskfile.svg icon under packages/ui/src/assets/icons/file-types/: out of scope. That icon represents the taskfile.dev task runner, unrelated to the renamed tool.

Dependencies

Blocked by #239 (CLOSED — PR #242 merged 2026-04-26). Aligns with #127 (Plan as tool) which will append its line to the boundary section when it lands. Indirectly unblocks #230 (default global AGENTS.md).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High priorityappApplication behavior and product flowsenhancementNew feature or requestharnessModel harness, prompts, tool descriptions, and session mechanics

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions