Skip to main content

Tool Annotations

Every built-in tool declares optional annotations — metadata that tells ak-coder and the LLM how to treat the tool at runtime. Annotations are defined on ToolAnnotations in packages/core/src/ports.ts and set in each tool file under packages/core/src/features/tools/.

Annotation fields​

FieldTypeMeaning
titlestringHuman-readable label shown in the REPL spinner (e.g. Read File)
readOnlyHintbooleanTool has no side effects — enables parallel execution when the LLM calls multiple read-only tools in one turn
destructiveHintbooleanTool may mutate state — documented for LLM providers that consume MCP-style hints; write/bash tools always go through confirmation policy regardless
idempotentHintbooleanRepeated calls with the same arguments produce the same result — informational hint for the LLM
openWorldHintbooleanTool reaches outside the workspace (network, sub-agents, shell) — informational hint for the LLM

How ak-coder uses annotations​

Parallel execution (readOnlyHint)​

When the LLM returns multiple tool calls in one turn, ak-coder checks whether every call is read-only:

all tools have readOnlyHint: true → Promise.allSettled (parallel)
any tool lacks readOnlyHint → sequential execution

This is enforced in AgentCore.processMessage — only readOnlyHint: true tools participate in parallel batches. Mixed batches (e.g. read_file + write_file) always run sequentially.

Set readOnlyHint: true only when the tool has zero side effects. A tool that writes logs, mutates session state, or spawns processes must not set it.

LLM tool schema​

Annotations are attached to the tool definition sent to the LLM provider:

function: {
name: 'read_file',
description: '...',
parameters: { ... },
annotations: { title: 'Read File', readOnlyHint: true, idempotentHint: true }
}

Providers that support MCP-style tool hints can use these fields to reason about safe parallelization or destructive operations.

Confirmation policy (separate from annotations)​

File writes and shell commands are gated by ConfirmationPolicy presets (default, yolo, plan, etc.) — not by destructiveHint. The destructiveHint flag documents intent for the LLM; actual prompts come from the policy + CommandSafetyGate.

In plan mode, mutating tools (write_file, patch_file, bash) are removed from the tool list entirely — independent of annotations.

Built-in tool annotation matrix​

ToolreadOnlyHintdestructiveHintidempotentHintopenWorldHint
read_file✅✅
write_file✅
str_replace✅
patch_file✅
list_directory✅✅
bash✅✅
glob✅✅
grep_search✅✅
index_workspace
semantic_search✅✅
web_fetch✅✅✅
delegate_task✅

index_workspace has no annotations — it mutates the in-memory vector index (session state), so it is not read-only and runs sequentially.

Adding annotations to a core tool​

In packages/core/src/features/tools/my_tool.ts:

export const myTool = (ctx: ToolContext): CoreToolDefinition<typeof schema> => ({
name: 'my_tool',
description: 'What the tool does.',
annotations: {
title: 'My Tool',
readOnlyHint: true, // only if truly side-effect free
idempotentHint: true, // optional — same args → same result
},
schema,
handler: async (args) => { ... }
});

Register the tool in packages/core/src/core-tools.ts.

Rules:

  • readOnlyHint: true → enables parallel execution with other read-only tools
  • Never set readOnlyHint on tools that write files, run commands, or modify session state
  • destructiveHint and openWorldHint are advisory — document intent accurately
  • Use ctx.resolveWorkspacePath() for all file paths

Plugin tools​

The Plugin SDK (@ak-coder/sdk) does not yet expose an annotations field on registerTool. Plugin tools always run sequentially when batched with other calls. Core-tool annotation rules above apply when adding annotation support to the SDK in the future.

See Building a Plugin for plugin authoring.