Skip to content

Tool skills & Agent runtime

Tool skills extend ordinary Skills so they publish one or more Tools the model can call directly (named call, parameters, result). The Agent runtime is the stable context object Tarsk injects into each Tool skill script’s execute function. The same contract is documented for the agent itself in the bundled tarsk-agent Skill.

Host machinery — MCP discovery, deferral / tool_search, and hook dispatch — stays in the Tarsk CLI. It is not part of this Skill API.

Split: Tool logic (description, parameters, orchestration, formatting) belongs in Skill scripts. The CLI / Agent runtime keeps security and light host surfaces only (cwd/path checks, permission gates, seatbelt, fetch allowlist, DB/network clients, truncate helpers, job registry). Project Tool skill scripts must not import modules — only context.* keeps those gates.

TermMeaning
SkillA SKILL.md package (instructions, optional scripts).
ToolA named callable the model invokes with parameters and receives a result from.
Tool skillA Skill whose front matter includes a tools array; it publishes those Tools.
Agent runtimeThe context argument to execute. Scripts must not import from cli/src/….
tarsk-agentBundled Skill that documents this interface for agents editing Tool skills. Not a Tool skill itself.
.agents/skills/my-notes/ # prefer editing the project copy
SKILL.md
scripts/
notes.js # one file per entry in front matter tools:

Tarsk-shipped defaults live under cli/bundled-skills/ with names starting tarsk-. To change one, export it into the Project’s .agents/skills (the tarsk-improve skill’s ensure-exported.ts) and edit that project copy so changes persist. Chat load never creates copies; it only prunes project copies that still match bundled, so untouched packages never linger in the repo. The tarsk- prefix is only the export filter; any Skill with a tools array publishes Tools.

---
name: my-notes # must match the directory name
description: Short trigger text for humans and skill discovery
version: 1.0.0 # semver; used by export
tools:
- notes # model-facing Tool name(s)
# deferred-tools: # optional; subset of tools → behind tool_search
# - notes
---
FieldRequiredDescription
nameYesMust match the skill directory name
descriptionYesSkill discovery / catalog text
versionRecommendedSemver for export comparison
toolsYes (for Tool skills)Inventory of Tool names this Skill publishes
deferred-toolsNoSubset of tools held behind tool_search; omit ⇒ all eager

tools is not the same as allowed-tools (see Skills → Tool allowlists), which only restricts which existing Tools a Skill may use.

Each scripts/{name}.js (or .ts) must export:

ExportTypeRole
descriptionstringShown to the model for this Tool
parametersJSON Schema objectArgument schema
execute(args, context, options?) => Promise<result>Runs the Tool

Result shape:

{
content: Array<{ type: "text"; text: string }>;
details?: unknown;
}

Throw, or return error text in content, on failure.

options (third argument):

{ signal?: AbortSignal; onUpdate?: (partial) => void }

The Tool name comes from the tools array, not from inside the file. A script not listed in tools remains an ordinary skill script (execute_skill_script), not a model Tool.

Edit the project Tool skill (usually .agents/skills/tarsk-<area>/) when:

  • The same tool call fails repeatedly for a fixable reason (bad defaults, weak validation, schema that invites wrong args).
  • Reaching the goal took excessive turns or tokens because of tool design (huge dumps instead of select / truncation, serial grep→read loops that belong in run_js, misleading descriptions).
  • A description or parameter shape no longer matches how the tool should be used.

Prefer a small change to description, parameters, or execute over a one-off workaround in chat. The next tool load picks up the edit. Do not invent host APIs — use context.* below.

GoalApproach
Change when the model uses a Tool, or its argsEdit description / parameters
Pre/post-process around host behaviorCall the matching dedicated context.* API in execute, then format the result
New Tool with logic you ownImplement execute with context primitives only (no module imports)
Deep change to bash / read / edit / browser / …Not possible from a Skill — those engines stay behind dedicated context.* host APIs
  1. Use Project .agents/skills/<name> if that folder exists.
  2. Otherwise use bundled cli/bundled-skills/<name>.

Load never writes. Reload re-resolves and cache-busts ESM imports by script mtime so project edits apply on the next tool load. Project scripts that import/require modules are rejected at load.

Export copies bundled Skills whose names start with tarsk- into .agents/skills. It runs on demand (the tarsk-improve skill’s ensure-exported.ts), never on chat load, and applies version rules:

Project copyAction
MissingCopy from bundled
Same versionLeave alone
Bundled major > project majorOverwrite with bundled
Otherwise (project ahead, or only minor/patch ahead)Leave project copy

Chat load never copies. It only prunes: a project copy byte-identical to bundled is deleted, so the repo keeps only packages that actually differ. Bump version when you publish a deliberate Skill revision. A bundled major bump can overwrite local copies.

  • Tool skills are enabled by default.
  • Disabling the Skill (thread skill settings) hides every Tool in its tools array.
  • Project disabled tools still hide individual Tool names (Bot tool sets still apply).
  • Disabling tarsk-agent does not remove Tools; the CLI always injects context.
FieldTypeMeaning
cwdstringWorking directory for the run
projectIdstringCurrent Project id (may be empty)
threadIdstringCurrent Thread id
conversationIdstringCurrent Conversation id
messageIdstring?Optional message id
threadPathstring?Optional Thread checkout path

Project-scoped durable notes:

MethodReturns
list()Note metadata array
read(name, offset?)Paged content object, or null
write(name, content)Metadata, or null when cleared (empty content)
append(name, content)Metadata after append
search(query)Name and content excerpts
MemberMeaning
titleMaxLengthMax characters for a chat title
normalizeTitle(title)Normalize a model-supplied title for storage
context.web.search({
query, numResults?, type?, includeDomains?, excludeDomains?, signal?
}): Promise<{ searchType?, requestId?, results: [...] }>
context.util.truncateHead(text, { maxLines?, maxBytes? })
context.util.withAbort(signal, (isAborted) => Promise<T>)

context.history / context.jobs / context.todos

Section titled “context.history / context.jobs / context.todos”

Light host stores: conversation history search/read; owner-scoped job list / output / kill; thread todos and progress.txt append.

Secure filesystem ops (cwd containment): writeText, glob, applyEdit, read, grep (all structured where listed; Skill formats model copy).

context.shell / context.ask / context.web.fetch

Section titled “context.shell / context.ask / context.web.fetch”

Secure shell (structured run + resolveExit), ask-user wait/normalize, and HTTP fetch (permission gates + credential store in the host). Truncation / background / ask / fetch result formatting lives in the Skill.

DB/telemetry only (resolveId, fetch, recordRetrieved, limits). Paging and miss/continuation copy live in the get_observation Skill.

APITool(s)
context.images.searchAndSave / .generatefind_images, generate_image (Skill formats)
context.projects.query / .launchprojects, prompt_project (Skill formats)
context.tasks.querytasks (Skill formats)
context.browser.runbrowser (Skill formats)
context.js.runrun_js (Skill formats success; host still throws failure diagnostics)
context.skillsCatalog.read / .executeScript / .readReferenceread_skill, execute_skill_script, read_skill_reference (Skill formats)

propose_branch runs entirely in its Skill script (no host API) and is only published for Slack Control Channel plan chat.

Every shipped Tool skill uses a dedicated context.* API (or pure script logic). Skill scripts own description/parameters and malleable formatting; security and heavy engines stay in the CLI behind those APIs.

.agents/skills/echo-tool/SKILL.md:

---
name: echo-tool
description: Echoes a message back via a small custom Tool.
version: 1.0.0
tools:
- echo_message
---
# Echo
Publishes `echo_message`. See the Agent runtime docs or the `tarsk-agent` Skill.

.agents/skills/echo-tool/scripts/echo_message.js:

export const description =
"Echo a short message back to the conversation. Use for smoke-testing Tool skills.";
export const parameters = {
type: "object",
properties: {
message: { type: "string", description: "Text to echo" },
},
required: ["message"],
};
export async function execute(args, context) {
const text = String(args.message ?? "");
return {
content: [{ type: "text", text: `echo (${context.cwd}): ${text}` }],
details: undefined,
};
}

After export, override .agents/skills/tarsk-bash/scripts/bash.js:

export const description = "/* revised guidance for the model */";
export const parameters = {/* keep or tighten the JSON Schema */};
export async function execute(args, context, options) {
const structured = await context.shell.run(args, options);
// Format completed / background / preformatted + resolveExit — see bundled bash.js
}
  • Skills — instructional Skills, scripts, and allowlists
  • Chat — how Tools appear in conversation
  • Permissions — shell and fetch approval (host behavior for bash / fetch)
  • Bundled authoring Skill: tarsk-agent (ships under cli/bundled-skills/; export on demand to edit)
  • End-of-session improver: tarsk-improve (user-invoked; exports tarsk-* into .agents/skills, proposes Tool skill patches on those project copies, then prunes unchanged copies)