Skip to content
yottacode v0.5.0 is out! 🎉 See the release notes ↗
Built-in tools

Built-in tools

Built-in tools ship in internal/agent (including the LSP tools, todo_write, and the enter_plan_mode / exit_plan_mode pair). The model sees their JSON-schema parameters via the OpenAI tools API; the TUI renders each invocation as a bordered card with a verb-style header (see How tool calls render in the TUI). OpenAI-compatible provider tool calls are validated and normalized before local execution: an empty arguments payload becomes {} (so no-argument tools such as exit_plan_mode still run), while a truncated or otherwise-unparseable payload is reported as an adapter error instead of being run locally. Arguments are also re-sanitized when replaying history to the provider, so a malformed call recorded earlier can’t wedge every later request with a 400. All paths are resolved against the agent’s working directory (absolute paths are also accepted).

In addition to the built-ins, MCP tools register dynamically when an [[mcp_servers]] block is present in ~/.yottacode/config.toml. They appear as mcp/<server>/<tool> and flow through the same approval modal and permission rules — see mcp.md and the MCP(...) rule shape in security-and-allow-lists.md. MCP tools default to approval-required unless the server explicitly hints them as read-only.

ToolApprovalOne-line summary
read_filenoneRead a text or image file (png/jpg/gif/webp) with optional line offset/limit
read_many_filesnoneRead multiple UTF-8 files in one call
read_documentnoneBounded, structured extraction for CSV/TSV/JSON/JSONL/XML/HTML/PDF/xlsx/docx/pptx
search_documentnoneRanked, query-based retrieval over a document’s extracted content
create_documentrequiredGenerate xlsx/pptx (native) or docx/pdf (via pandoc) from structured content
write_filerequiredOverwrite or create a file
edit_filerequiredSurgical old_string→new_string replacement
edit_anchoredrequiredAnchor-validated line edits after anchored reads
apply_hashlinerequiredApply content-hash anchored text edits
apply_diffrequiredApply a legacy unified diff patch
mkdirrequiredCreate a directory and missing parents
copy_filerequiredCopy a file to a new path
move_filerequiredMove or rename a file or directory
delete_filerequiredDelete a file or empty directory
list_git_changed_filesnoneList changed files in the current repo
git_branch_statusnoneShow branch/upstream/dirty state
git_show_file_at_revnoneRead a file from a past revision
git_diff_filesnoneShow a diff for refs and/or files
git_stage_filesrequiredStage specific files or all changes
git_unstage_filesrequiredUnstage specific files
git_create_branchrequiredCreate and switch to a new branch
git_commitrequiredCommit staged changes
git_commit_contextnoneTyped snapshot for drafting a commit message (paired with git_commit_apply)
git_commit_applyrequiredValidate a one-line subject and run git commit with structured result envelope
pr_contextnoneTyped snapshot for opening a PR (base resolution, ahead-count, push state, gh availability, PR template)
pr_createrequiredValidate title and open a PR via the internal/github.Interface adapter with typed result envelope
pr_readnoneFetch PR metadata only via one API call (use instead of run_bash gh pr view --json …)
pr_review_contextnoneFetch PR metadata + diff + check rollup via the internal/github.Interface adapter for review
pr_watch_checksnoneWatch PR checks until pass/fail/timeout and return failed GitHub Actions log tails
pr_check_logsnoneFetch failed GitHub Actions job log tails for a PR
pr_rerun_checksrequiredRerun failed GitHub Actions jobs for a PR
pr_updaterequiredRewrite an existing PR’s title and body via the internal/github.Interface adapter; title validation + non-empty-body guard
pr_add_commentrequiredPost a top-level conversation comment on a PR; body capped, approval-gated
issue_readnoneFetch issue metadata + comments (use instead of run_bash gh issue view --json …)
issue_listnoneList open issues matching label/assignee/milestone filters
issue_contextnoneTyped snapshot for opening an issue (repo detection, gh availability, template discovery)
issue_createrequiredValidate title and open an issue via the internal/github.Interface adapter with typed result envelope
git_pushrequiredPush the current branch to origin with deterministic upstream detection; surfaces the PR URL when one exists
git_log_filenoneShow history for one file
git_blame_linesnoneBlame a line range in a file
git_merge_basenoneFind merge base between two refs
git_checkpointrequiredCreate a local checkpoint commit
rollbackrequiredReset the repo to an earlier commit
run_testsrequiredRun the repo’s test command
debug_startrequiredStart one Go debug session through dlv dap
debug_breakpointnoneSet a Go source breakpoint by file and line
debug_continuenoneContinue a Go debug session and wait up to 30s for a stop event
debug_stepnoneStep a Go debug session with next/stepIn/stepOut
debug_stacknoneReturn Go debug stack frames
debug_varsnoneReturn variables for a Go debug frame or variables reference
debug_evalrequiredEvaluate a Go debug expression in the selected frame/context
debug_stopnoneStop the active Go debug session and tear down Delve
media_probenoneInspect audio/video metadata with ffprobe
media_analyzenoneDetect silence/fluff candidates with ffmpeg
media_composerequiredAssemble title cards, images, and clips into a draft MP4 with ffmpeg templates/effects
media_renderrequiredRender approved edits to YouTube/X MP4 or GIF preview profiles with ffmpeg
list_dirnoneOne-line-per-entry directory listing
globnoneDoublestar pattern match
grepnoneRipgrep (or GNU grep fallback)
lsp_statusnoneDetect supported workspace languages, report server install/probe status, and show initialized capabilities
lsp_symbolsnoneSearch workspace symbols through an installed language server
lsp_document_symbolsnoneList structural symbols declared in one source file
lsp_document_highlightsnoneShow current-file symbol reads/writes/text occurrences
syntax_rangenoneOffline Go AST / cross-language scanner ranges and hashline receipts around a source position
lsp_selection_rangesnoneShow server-backed nested syntax ranges around a source position
lsp_definitionnoneFind definition locations for a source position through an installed language server
lsp_type_definitionnoneFind type definition locations for a source position through an installed language server
lsp_implementationnoneFind implementation locations for an interface, method, or symbol
lsp_referencesnoneFind reference locations for a source position through an installed language server
lsp_diagnosticsnoneReturn compile/type diagnostics from an installed language server
lsp_changed_files_diagnosticsnoneReturn diagnostics for git-changed supported source files after edits
lsp_hovernoneShow hover/type/docs information at a source position
lsp_signature_helpnoneShow callable signatures and active parameter info at a source position
lsp_code_actionsnoneList quick fixes/refactors for a range without applying them
lsp_code_action_previewnonePreview the WorkspaceEdit for one code action without applying it
lsp_rename_previewnonePreview semantic rename edits without applying them
lsp_format_previewnonePreview formatting edits without applying them
lsp_apply_workspace_edityesApply a previously previewed WorkspaceEdit after validation and approval
lsp_call_hierarchynoneShow incoming/outgoing calls for a source position
lsp_impactnoneComposite impact report for a source position: hover, definitions, references, calls, diagnostics, and Code Map imports
code_mapnoneReturn a bounded directory/file/symbol structure map from the experimental code index
code_symbolsnoneReturn indexed symbols for a file or query from the experimental code index
code_structure_projectionconditionalGenerate a compact code-structure projection for agent context; requires approval only when to_file is set
code_dependenciesnoneReturn direct import dependencies for an indexed file/path query
code_dependentsnoneReturn direct import dependents for an indexed file/path query
code_impactnoneReturn dependencies, dependents, transitive dependents, likely tests/docs, and cycles as a blast-radius summary
code_cyclesnoneReturn import cycles, optionally narrowed to one indexed file/path query
code_map_diagramconditionalReturn a Mermaid import dependency diagram, optionally focused around one file; requires approval only when to_file is set
pr_readiness_contextnoneGather a local PR readiness snapshot before opening or updating a PR
fetch_urlnoneFetch a single HTTP(S) URL and return capped textual content
run_bashrequiredShell command via /bin/sh -c; foreground with a timeout, or a background job
processnonePoll, wait on, read the log of, or kill a background shell job (TUI and ACP only)
gitvariesUnified git invocation; read-only auto-runs, mutations prompt
todo_writenoneMaintain the agent’s working task plan, rendered as a card
enter_plan_moderequiredOnly callable OUTSIDE plan mode; requests the read-only planning state via a [Y]/[N] card
exit_plan_moderequiredOnly callable in /plan mode; presents the plan for user approval
AgentnoneDispatch a typed subagent that runs in its own context window; see subagents.md
dispatchnoneExperimental behind dispatch; fan multiple independent subtasks out to concurrent subagents
integratenoneExperimental behind dispatch; merge dispatch worker branches into one PR-ready integration branch
browser_statusnoneExperimental behind browser; report browser manager state (binary, active session, URL, profile dir)
browser_navigaterequiredExperimental behind browser; navigate the active page, lazily launching an isolated headless session
browser_screenshotrequiredExperimental behind browser; capture a PNG of the page or one element
browser_inspectrequiredExperimental behind browser; return an accessibility-tree text snapshot of the page or one element
browser_clickrequiredExperimental behind browser; click the element matching a selector
browser_typerequiredExperimental behind browser; type into a field, optionally submitting with Enter
browser_hotkeyrequiredExperimental behind browser; send a key or key combo to the page
browser_scrollrequiredExperimental behind browser; scroll an element into view, or scroll the page by direction/delta
browser_waitnoneExperimental behind browser; wait for a selector/text/network-idle condition
browser_handoffrequiredExperimental behind browser; reopen the isolated session as a visible window so the user can complete a human-verification challenge
browser_closenoneExperimental behind browser; close the browser session and remove its temp profile
browser_tabsnoneExperimental behind browser; list every tracked tab and which one is active
browser_switch_tabnoneExperimental behind browser; switch which tracked tab later calls act on
browser_close_tabnoneExperimental behind browser; close one tracked tab without closing the session
browser_uploadrequiredExperimental behind browser; set a file input’s files from local paths
browser_downloadrequiredExperimental behind browser; trigger a download (via click or url) and save it to a local path
browser_console_logsrequiredExperimental behind browser; return buffered console.* calls and uncaught exceptions from the active page
browser_network_requestsrequiredExperimental behind browser; return buffered request/response metadata from the active page

“Approval = required” means the tool always pauses for a y / a / N from the user, unless an allow rule in <cwd>/.yottacode/permissions.json (or its gitignored .local.json sibling) matches the call, or --yolo is set (DANGEROUS). See architecture.md for the approval round-trip and the permissions schema.

How tool calls render in the TUI

Every tool call renders as a bordered card with three regions:

go test ./internal/tui/ -run TestDemo_CardOutput -v
go test ./internal/tui/ -run TestDemo_CardOutput -v | sed -n '/^─── /,/^--- PASS/p' | sed '/^--- PASS/d' 
┌ Verb(arg) · 4s
│   <body lines, capped at 10>
└ <summary footer>

The gutter glyphs (┌ │ └) are tinted by result so a long transcript stays scannable: a failed call tints the whole ┌ │ └ frame red so the bad card is findable at a glance, while a clean call keeps the frame neutral dim. The header is bold; the footer is dim, with exit 0 in green and exit N≠0 / ✗ <error> in bold red. Body rows are indented three columns under the gutter so the shape reads as “header, indented content, footer.”

Duration tag. When a call takes at least one second, its elapsed time is shown as compact header metadata (· 4s, · 1m 03s). Sub-second calls — the vast majority — show no tag, so the timing surfaces only when “how long did that take?” is a real question.

Session stream spacing. Completed turns end with the dim ◦ thought · <duration> receipt, followed by one blank spacer row. The spacer separates the prior turn’s timing receipt from the next prompt or tool block without adding another visible status message.

Header verbs. The raw tool name still appears in the agent’s tool-call log; the TUI renames it for readability. Mapping:

ToolHeader
run_bashBash(<command>)
read_fileRead(<path>) or Read(<path> @ L<offset>+<limit>) (images: Read(<path>); anchored reads append · anchors)
read_many_filesRead(N files)
write_fileWrite(<path>)
edit_fileEdit(<path>, single|all)
edit_anchorededit_anchored(<path>, N ops)
syntax_rangeSyntax(range <path>:<line>:<character>)
apply_hashlinePatch(hashline <path>)
apply_diffPatch(apply)
mkdirMkdir(<path>)
copy_fileCopy(<src> → <dst>)
move_fileMove(<src> → <dst>)
delete_fileDelete(<path>)
list_dirList(<path>)
globGlob(<pattern>) or Glob(<pattern> in <root>)
grepGrep("<pattern>" in <path>)
lsp_statusLSP(status <path>)
lsp_symbolsLSP(symbols "<query>") or LSP(symbols "<query>" in <path>)
lsp_document_symbolsLSP(document symbols <path>)
lsp_document_highlightsLSP(document highlights <path>:<line>:<character>)
lsp_selection_rangesLSP(selection ranges <path>:<line>:<character>)
lsp_definitionLSP(definition <path>:<line>:<character>)
lsp_referencesLSP(references <path>:<line>:<character>)
lsp_impactLSP(impact <path>:<line>:<character>)
lsp_signature_helpLSP(signature <path>:<line>:<character>)
fetch_urlFetch(<url>)
run_testsTest(<command>)
rollbackRollback(<target>)
gitGit(<subcommand> <args>)
git_branch_statusGit(branch status)
git_show_file_at_revGit(show <path> @ <rev>)
git_diff_filesGit(diff <base>..<head>)
git_stage_files / git_unstage_filesGit(stage N files) or Git(stage all) / Git(unstage N files)
git_create_branchGit(create branch <name>) or Git(create branch <name> from <start_point>)
git_commitGit(commit <args>). The commit-family tools (git_commit, git_commit_apply, git_commit_amend, git_commit_fixup, git_checkpoint) are also tagged by the harness with whether the session is inside a yottacode worktree, which a rule can require by ending in @worktree; the tag is not part of the descriptor, so arguments cannot forge it
git_log_fileGit(log <path>)
git_blame_linesGit(blame <path>:L<a>-L<b>)
git_merge_baseGit(merge-base <base>..<head>)
git_diff_stat / git_diff_staged / git_diff_unstagedGit(diff_stat …) / Git(diff_staged …) / Git(diff_unstaged …)
git_commits_between / git_branch_ahead_behind / git_branch_diffGit(commits_between …) / Git(branch_ahead_behind …) / Git(branch_diff …)
git_commit_amend / git_commit_fixupGit(commit_amend …) / Git(commit_fixup …)
git_checkpointGit(checkpoint)
git_commit_apply / git_pushGit(commit_apply …) / Git(push …)
git_worktree_{list,add,remove,lock,unlock,prune}Git(worktree_<verb> …)
enter_worktree / exit_worktreeWorktree(enter <name>) / Worktree(exit <cleanup> <name>)
lsp_apply_workspace_editEdit(<path>), one per touched file
media_composeMedia(compose <output>)
memory_curate_applyMemory(curate_apply <scope>:<name>)
list_git_changed_filesGit(list changed)

ASCII control characters inside an arg (a stray \n in a path, a tab in a filename, etc.) are stripped before the header renders, so a malformed arg can never break the card’s box shape. Long bash commands are clipped to fit the terminal width with a …) tail.

Body. Carries the tool’s interesting output: directory entries, grep matches, command stdout, diff hunks. Capped at 10 visible lines — the model still receives the full output via the agent’s tool-result event. Listing-shaped tools keep the first 10 lines with a trailing …N more line(s) notice; custom listing renderers use the same cap, so large highlighted grep cards show at most 10 match rows plus …N more match(es). Command-envelope tools (run_bash, run_tests, git) keep the last 10 lines behind a leading …N earlier line(s) notice, because the verdict of a command — the test summary, the final compiler error — lives at the end of its output. A few tools have card-specific body shapes:

  • run_bash / run_tests / git split their output into stdout, a ── stderr ── separator, and stderr. The footer carries the process exit code (green when zero, red otherwise).
  • edit_file renders a syntax-highlighted - (red) / + (green) diff in the body instead of the textual confirmation.
  • git with a destructive flag (--force, --hard, -D, --delete, …) prepends a bold-red ⚠ DESTRUCTIVE FLAG(S): <flags> row to the body so it’s hard to y past by reflex.
  • fetch_url drops the raw HTTP body and shows only the response metadata (Status, Content-Type, optional truncation note). The footer reports the response body size. The model still receives the full content; the user is spared 64+ KiB of minified markup.
  • code_review_context shows the ## summary digest and only true exception flags in the card; the default medium diff snapshot is capped at 24 KiB to keep retained context compact. Use effort=high when a larger diff is required.
  • read_file / write_file show no body — the footer’s N lines · M bytes / wrote N bytes carries the entire signal. When multiple successful summary-only read cards land consecutively (read_file, read_many_files, list_dir, list_project_structure, glob), the TUI may group them into one visual card with one row per call. Wrapped continuation rows hang-indent under the row text, and overflow copy names the hidden row type (read calls, list calls, glob calls). This is display-only and does not change the individual tool results the model or saved session receive.
  • Provider-hosted tools (for example xAI web search) reuse the same body wrapping and footer path as local tool cards, so card polish applies uniformly.

Footer. Summarizes the call: N entries, wrote N bytes, N lines · M bytes [(truncated)], N matches, exit N (colored), or the tool’s confirmation message. When the call errored, the footer renders └ ✗ <error> in bold red and the body shows the raw error verbatim (per-tool body shaping is bypassed for errors).


Successful context summarization and mid-turn compaction now use the shared system-message grammar (◇ context · summarized/compacted · ...) and print a copyable yottacode sessions resume <id> command when a pre-summary snapshot is available — running it reopens the full pre-compression transcript as a fresh session. The full snapshot path is intentionally omitted from normal scrollback; only the resume id is shown.

read_file

Read a text or image file. For text files, output is cat -n style (1-indexed line numbers). For image files (.png, .jpg, .jpeg, .gif, .webp), the image data is returned as a native visual content block that vision-capable models can see directly.

ParamTypeDefaultNotes
pathstring—Absolute or cwd-relative
offsetint11-indexed start line (text files only)
limitint2000Max lines to return (text files only)
anchorsboolfalseWhen true, prefix text rows as line#anchor\tcontent and include a hashline receipt for the exact returned byte span

When anchors=true, text output starts with a receipt line such as # hashline path=main.go offset=0 length=128 hash=… ends_with_newline=true. The hash is over the exact bytes returned in the read window, including original line endings. ends_with_newline says whether that span ends with a newline: rendered lines never show their terminator, so without it a\nb and a\nb\n look identical. Use the receipt with apply_hashline when you want the next edit to fail safely if the file changed after the read.

Paging large files. A single read returns at most 512 KiB of content; when more follows the window, the output ends in …[truncated]. That budget caps what one call returns, not how far into a file offset may reach — lines before the window are streamed past and discarded, so a distant offset costs time rather than memory. Page a large log by advancing offset by the number of lines you got back. An empty result means one thing only: the offset is past the last line.

Non-text and special files. Only regular files are read: a directory, FIFO, socket, or device returns a not a regular file (…) error, and a FIFO can no longer hang the call (this applies to image-named paths too). A window that contains NUL bytes or is not valid UTF-8 is not dumped into the context; the result is [skipped: not UTF-8 text (…)] instead, with no line numbers or receipt. This is a normal (non-error) result, like the image-unsupported label below. When one line alone exceeds the 512 KiB budget, the returned prefix stops on a character boundary so it is never cut into invalid UTF-8, and the receipt covers exactly that prefix.

Image support. When the path points to a recognized image file and the provider supports images in tool results (currently Anthropic only), the tool reads the raw bytes (up to 20 MiB) and returns them as an image content block alongside a text label like [image: photo.png, image/png, 12345 bytes]. On providers that don’t support images in tool results, only the text label is returned. The same deny list applies to images.

No approval — the model legitimately needs to read dotfiles, USER.md, /etc/os-release, etc. A narrow deny list still applies: ~/.ssh, ~/.aws, ~/.gnupg, ~/.netrc, ~/.yottacode/.env, ~/.kube/config, ~/.docker/config.json, ~/.config/gh/hosts.yml, ~/.config/gcloud, <cwd>/.env, <cwd>/.env.local. Reading those returns an error — closes the silent prompt-injection exfiltration vector. Use run_bash (which prompts) if you really need them.

read_many_files

Read multiple UTF-8 text files in one call. Useful when the model needs context from a handful of related files without paying for many separate round-trips.

ParamTypeDefaultNotes
paths[]string or string—Required; max 20 files; a single string is accepted for one file
offsetint0Bytes, per file; negatives clamped to 0. May land mid-line, in which case the first line is partial
limitint524288Bytes, per file. A file cut short by limit ends on a line boundary
anchorsboolfalseWhen true, prefix each returned text line with line#anchor\tcontent

offset and limit are byte counts here, unlike read_file, where they are line counts.

Returns sections in the form:

==> path/to/file <==
<content>

Sections come back in sorted path order, not request order, so identical requests produce identical output. A section that was cut short (by limit or by the output cap) ends with …[truncated]. A cut window ends on a line boundary, or on a rune boundary inside a single overlong line, so a follow-up call can continue from a clean offset.

Output cap. The sections together never exceed 512 KiB, including anchors=true prefixes and receipts. A file that only partly fits is returned truncated; once too little budget remains for another file, the rest are named in a trailing …[read budget exceeded …] note (extra to the 512 KiB, and it only echoes paths you supplied) so they can be requested in a follow-up call.

Failures. Every path is checked against the read deny list before any file is opened, and a denied path refuses the whole call. Any other per-file problem does not abort the batch:

SituationResult
Missing, unreadable, directory, FIFO, socket, or deviceInline [error: no such file or directory] (or similar) in that file’s section. Non-regular files are never opened for reading, so a FIFO cannot hang the call
Binary (contains NUL bytes) or not valid UTF-8Inline [skipped: not UTF-8 text (…)]
No file could be read at allThe call returns an error naming each failed path

Anchors. With anchors=true, each section starts with # hashline path=… offset=… length=… hash=… ends_with_newline=… (see read_file for the last field). Line numbers are absolute (counted from the start of the file, so they match read_file and edit_anchored even when offset is non-zero), and the hash covers exactly the bytes rendered as lines in that section, so it can be passed to apply_hashline. If offset lands mid-line, that first line is partial and its anchor covers only the partial text, so edit_anchored rejects it as stale instead of editing the wrong line.

read_document

GA for every format, including PDF; the document_ingestion flag is a no-op kept for one release for compatibility (see experimental.md). PDF extraction needs pdftotext/pdfinfo reachable via host PATH or [sandbox].documents_image — a missing binary returns an actionable error naming exactly where it looked, not a silent failure.

Extract bounded, structured text from a CSV, TSV, JSON, JSONL, XML, HTML, PDF, xlsx, docx, or pptx file. Use it when you need to analyze data in one of these formats: read_file’s raw line-based view shears a CSV field’s embedded newline into a bogus extra row, and dumps HTML/XML markup noise (scripts, styles, tags) verbatim instead of the content underneath.

PDF extraction runs pdftotext/pdfinfo (poppler), routed through the same documents sandbox profile create_document’s docx/pdf path uses: installed on the host when no sandbox is configured, or present in [sandbox].documents_image when one is. Each page becomes its own labeled section (page 3); pdfinfo’s own Title/Author/ CreationDate fields, when present, come back as title/author/ created lines above the preview. An encrypted PDF comes back as a warning, not an error, since that’s still a valid, actionable result — detected from pdfinfo’s structural Encrypted field when it reports one, falling back to a pdftotext-stderr heuristic otherwise (older poppler, or a failed pdfinfo call). When python3+pdfplumber are also reachable through that same documents profile or host PATH, detected tables come back as additional page N table M sections with pipe-joined rows, and detected images as page N image M sections (placed size in inches, plus the source image’s own pixel resolution when known — never image bytes; both ride in one pdfplumber call, since both just read properties off the same already-open page) — best-effort and completely silent (no warning either) when that dependency isn’t available or a page genuinely has neither. A page with no embedded text layer at all falls back to OCR when python3+pytesseract+pdf2image+tesseract-ocr are reachable the same way — per page, not just when the whole requested range is blank, so a partially-scanned document (some pages with real text, some without) still gets OCR only on the pages that actually need it: recognized text comes back as additional page N (ocr) sections with a warning that the text may contain recognition errors, since it isn’t as reliable as the rest of a PDF’s extraction; without that dependency, it’s the same “may be scanned/image-only” warning as before, no OCR section. See document-generation.md.

xlsx, docx, and pptx are parsed natively — no external tools, no sandbox involved, work identically on every platform. xlsx (via excelize, the same library create_document uses for generation) returns one section per sheet (sheet Q1), plus a sheet Q1 formulas section for cells that have one (excelize.GetRows only ever returns a cell’s cached computed value, never the formula, and a freshly generated formula cell has no cached value at all — this section is the only way to see what actually computes a cell; scoped to whichever rows the row/char-capped preview already included), a sheet Q1 merged cells section when any exist, and a sheet Q1 image <cell>-N section per embedded picture (type, pixel size, alt text). docx is a native zip/XML walk of word/document.xml: one document body section, with HeadingN paragraph styles rendered as #-prefixed lines so structure survives in the text preview, plus a document image N section per inline picture — independent of whether that native tier or the richer pandoc tier served the body text. pptx walks ppt/slides/slideN.xml in numeric slide order, one section per slide (slide 3) — max_pages/offset page through slides the same way they page through PDF pages — plus a slide N table M section (pipe-joined rows) for each DrawingML table found on that slide, and a slide N image M section (file name, size, alt text) for each picture. Every image/ picture section across all three formats is metadata only, never image bytes; read the media file directly via read_file if you need the actual image. None of the three attempt rich per-run text formatting or embedded charts/objects (excelize itself has no chart-reading API, only chart-writing); that tier is still unbuilt.

Analyze with read_document, edit with read_file. The two tools are not interchangeable by file extension. read_file returns cat -n output, and those line numbers and exact strings are what feed edit_file and edit_anchored; read_document returns a reformatted, re-pretty-printed preview that cannot drive either. So a .json, .xml, or .html file you are about to edit — package.json, tsconfig.json, an HTML template — still wants read_file. .csv, .tsv, and .jsonl are almost always data being read, where read_document is the right call. The routing decision is intent, not suffix, which is why it lives in the model’s judgement rather than in an automatic dispatch inside read_file.

ParamTypeDefaultNotes
pathstring—Absolute or cwd-relative; extension must be .csv, .tsv, .json, .jsonl, .xml, .html, .htm, .pdf, .xlsx, .docx, or .pptx
max_rowsint200Max CSV/TSV/xlsx rows or JSONL records sampled into the preview
max_charsint20000Max characters of extracted text returned
max_pagesint50PDF or pptx only: max pages/slides to read text from
offsetint0Where the preview window starts — data rows for CSV/TSV/xlsx, records for JSONL, characters for JSON/XML/HTML/docx, pages for PDF, slides for pptx
has_headerboolautoCSV/TSV only: whether row 1 holds column names. Omitted means auto-detect
max_bytesint5 MiBMax bytes read from the source file. Raise it when a result warns the file exceeded the byte cap; clamped to a 32 MiB ceiling, and the clamp is reported as a warning rather than applied silently
ocr_langstringEnglishPDF only: Tesseract language code (fra, deu, …) or +-joined codes (eng+fra) for the OCR fallback tier. Only used when a PDF has no embedded text layer at all; the requested language pack must be installed wherever OCR runs

Paging

offset moves the preview window, so a file larger than one page is reachable rather than permanently truncated at the first max_rows. Page by repeating the call with offset advanced by however many rows/records came back:

read_document(path="sales.csv", max_rows=200)            → rows 1-200
read_document(path="sales.csv", max_rows=200, offset=200) → rows 201-400

The unit is whatever that format’s preview is made of — data rows for CSV/TSV, records for JSONL, characters for the JSON/XML/HTML text preview. There’s one offset rather than separate row_offset and char_offset because every result labels the window it actually returned (rows 201-400, document characters 11-20), so the unit is unambiguous exactly where you read it.

Three things hold across pages: the CSV header repeats on every page (later pages would be unreadable otherwise), JSONL labels stay absolute source line numbers (JSONL line 412), and rows/RowCount keeps reporting the file’s true total so you know how far you can keep going. An offset past the end is reported as such — an empty window otherwise looks identical to an empty file and would end a paging loop one page early.

Skipping is a forward scan, not a seek: rows are variable-length and a quoted field may contain newlines. Skipped content is parsed and discarded rather than retained, so deep paging costs time, not memory. It does spend the max_bytes budget, and a window lying past that cap says so rather than looking like the end of the file.

Real-world export quirks

Files that come out of Excel, BI tools, and database exports rarely match the textbook format. Three cases are handled automatically, because each one otherwise produces a wrong answer that looks like a right one:

  • UTF-8 BOM. Windows Excel prefixes exports with a byte-order mark. Go’s CSV reader folds it into the first column’s name (id arrives as <BOM>id, so every later match on that column misses), and its JSON decoder rejects the whole document with invalid character 'ï'. The mark is stripped silently — there’s no reading in which it was content.
  • Non-comma delimiters. Excel writes ;-delimited .csv in every European locale. Read as comma-delimited, each line collapses into a single column and reports as a valid one-column table. The delimiter is sniffed from the first line (,, ;, tab, |, counted outside quotes), and a non-default choice is always reported as a warning since it changes how every row was parsed. .tsv is not sniffed — its extension is unambiguous.
  • No header row. Treating row 1 as column names unconditionally both loses that record and mislabels every column. A first row containing a number is read as data, and the decision is stated in the warnings. The known false positive is a genuine header of bare years (2024,2025) — pass has_header: true for that, or false to force the other direction.
  • JSONL content in a .json file. Read as one document it doesn’t degrade, it errors — encoding/json rejects the second value and the whole file comes back unusable. When whole-document parsing fails and every non-blank line parses on its own, the file is re-read as JSONL and the reinterpretation is reported. The bar is deliberately strict: a single malformed line means the original invalid JSON error stands, since that’s the more useful answer for a genuinely broken file.

Response shape

The response always starts with a structure summary before any content:

  • CSV/TSV — column headers and a total row count, then the sampled rows.
  • JSON — a one-line shape summary (object key set, or array length
    • element type), then a pretty-printed preview. A .json file larger than the byte cap falls back to a raw-text preview instead of attempting to parse a document truncated at an arbitrary byte offset.
  • JSONL — each sampled record is its own section labeled with its source line number (JSONL line 412), so the model can cite an exact line back to the user. Malformed lines are skipped and counted in a warning, not silently dropped. A record too large for the remaining character budget is skipped individually and counted; sampling continues past it, and the gap stays readable because the labels are absolute line numbers.
  • XML — the root element name plus the most frequent child element tags, then the visible text content.
  • HTML — the <title> and headings, then the visible text with <script>/<style> bodies stripped.

Every cap that actually truncated something shows up as an explicit warning line naming which cap did it (e.g. showing rows 1-50 of 250 data rows read (200-row sample cap)) — truncation is never silent, and the reason is always stated so you know whether to raise max_rows, max_chars, or max_bytes, or to page on with offset. A file larger than the byte cap stops reading there regardless of how many rows/records that covers, so a pathological single-line file can’t be read unbounded into memory.

max_bytes is the only cap with a hard ceiling (32 MiB), because it is the one that bounds the others — no extractor can sample more rows or characters than the bytes it was allowed to read. The ceiling matters most for .json, the one format that buffers its whole allowance and decodes it into an in-memory tree several times larger than the source text; the streaming extractors grow only linearly with the allowance.

Read-only, no approval — same trust posture as read_file, including the same credential-path deny list.

Not in scope for this tool: full-fidelity Office parsing (rich per-run text formatting, embedded charts/objects, actual image bytes — table structure and picture metadata are extracted for docx/pptx, and formulas/merged cells/picture metadata for xlsx, but xlsx/docx/pptx text extraction otherwise stays structural, not full-fidelity), legacy binary .doc/.xls/.ppt, .md/.txt/.log (already covered by read_file), and any file fetched from a URL — local files only. PDF text extraction requires pdftotext/pdfinfo reachable through the active command sandbox — see document-generation.md; with no sandbox and no host install, PDF calls fail with an actionable error rather than falling back silently.

search_document

Query-based retrieval over a document’s extracted content — use this instead of paging read_document with offset when you don’t know which page, sheet, or section of a large file holds what you need.

Internally it runs the exact same extraction read_document would (same format dispatch, same PDF-availability rules, same path-trust boundary), splits the result into paragraph-sized chunks, and ranks them against your query with the same BM25 engine memory_search uses — keyword-based ranking, not exact substring matching, so word order and minor phrasing differences don’t prevent a match. Results come back numbered, scored, and labeled with their location (page 4 (part 2), Sheet1, document body), each with a short snippet, ranked most relevant first.

ParamTypeDefaultNotes
pathstring—Absolute or cwd-relative; same supported extensions as read_document
querystring—Search query — keywords work better than a long natural-language sentence
max_resultsint10Maximum number of ranked matches to return
ocr_langstringEnglishPDF only: Tesseract language code for the OCR fallback tier, same as read_document’s ocr_lang
search_document(path="quarterly_report.pdf", query="capital expenditure forecast")

found 2 matches for "capital expenditure forecast" in quarterly_report.pdf:

1. page 14 (score=3.241)
   Capital expenditure for the coming fiscal year is forecast to increase...

2. page 22 (score=1.008)
   ...prior capital expenditure decisions informed this year's forecast...

A query that matches nothing returns a plain string (no matching text found for "..." in ...), not an error — the same “not-found is a valid result” convention read_document and memory_search both follow.

Read-only, no approval — same trust posture as read_document, including the same credential-path deny list. Nothing is indexed or persisted between calls: each call re-extracts and re-ranks the file fresh, so results always reflect the file’s current on-disk content.

create_document

GA for every format, including docx/pdf; the document_generation flag is a no-op kept for one release for compatibility (see experimental.md). docx/pdf need pandoc/weasyprint reachable via host PATH or [sandbox].documents_image — a missing binary returns an actionable error naming exactly where it looked, not a silent failure. Generates a new xlsx, docx, pdf, or pptx file from structured content — the write-side counterpart to read_document. See document-generation.md for the full design and setup.

ParamTypeDefaultNotes
formatstring—xlsx, docx, pdf, or pptx
output_pathstring—Path to write the generated document to
overwriteboolfalseMust be explicit to replace an existing output
templatestring—format=docx only: path to an existing docx to fill instead of generating a new one from content.blocks — see below
content.sheets[]object—xlsx only: one entry per sheet — name, rows (array of arrays of cells)
content.blocks[]object—docx/pdf only, ignored when template is set: ordered content blocks
content.slides[]object—pptx only: one entry per slide
content.replacementsobject—format=docx with template set only: flat {name: value} map

xlsx cell fields: value (string/number/bool), formula (without the leading =; overrides value), bold, italic, number_format (an Excel number format code, e.g. 0.00% or yyyy-mm-dd).

docx/pdf block fields: type (heading, paragraph, list, table, code, or image), level (heading 1-6), text (heading/paragraph/code plain text), spans (heading/paragraph: inline-formatted runs — [{"text": ..., "bold": ..., "italic": ...}] — overrides text when set), ordered + items (list plain text) + item_spans (list: parallel to items, one spans array per item, overrides that item’s plain text), header + rows (table, string cells), language (code), path

  • alt (image: local file path — validated as a read path the same way read_file validates one — and alt text).

pptx slide fields: title, bullets (array of strings), notes (speaker notes), image (local PNG/JPEG/GIF file path — validated as a read path the same way read_file validates one) + image_alt (written to the picture description field), and layout (currently advisory; the native Go renderer uses one fixed production-safe layout). An image’s placement defaults to a fixed right-half layout; set image_layout to left, right, or full for a preset, or set all four of image_left/image_top/image_width/image_height (inches) for an exact bounding box — the two are mutually exclusive, and an explicit box must fit within the 13.33in x 7.5in (16:9) slide.

format=docx with template set fills an existing document instead of generating a new one: every {{name}} token found anywhere in the template (paragraphs, table cells, headers, footers) is replaced with content.replacements[name]. template is validated as a read path the same way an image block’s path is. Runs python3+python-docx through the active command sandbox — see document-generation.md for the dependency story and the per-paragraph formatting trade-off (a paragraph with a replaced token collapses to its first run’s style; every other paragraph is untouched).

{"format": "docx", "output_path": "invoice.docx", "template": "templates/invoice.docx",
 "content": {"replacements": {"customer_name": "Jane Doe", "total": "$42.00"}}}
{"format": "xlsx", "output_path": "report.xlsx", "content": {"sheets": [
  {"name": "Q1", "rows": [
    [{"value": "Item", "bold": true}, {"value": "Qty", "bold": true}],
    [{"value": "Widgets"}, {"value": 42}],
    [{"value": "Total"}, {"formula": "SUM(B2:B2)"}]
  ]}
]}}
{"format": "docx", "output_path": "notes.docx", "content": {"blocks": [
  {"type": "heading", "level": 1, "text": "Weekly Notes"},
  {"type": "paragraph", "spans": [
    {"text": "Summary: "}, {"text": "shipped early", "bold": true}, {"text": "."}
  ]},
  {"type": "list", "items": ["Shipped X", "Fixed Y"]},
  {"type": "image", "path": "assets/chart.png", "alt": "Weekly progress chart"}
]}}
{"format": "pptx", "output_path": "deck.pptx", "content": {"slides": [
  {"title": "Weekly Update", "layout": "title_only"},
  {"title": "Progress", "bullets": ["Shipped X", "Fixed Y"], "notes": "Mention the Y fix took longer than expected"},
  {"title": "Growth chart", "image": "assets/chart.png", "image_alt": "Weekly growth chart"}
]}}

xlsx and pptx are generated natively in Go — no external tools, works regardless of sandbox configuration. docx/pdf run pandoc (pdf additionally needs weasyprint as pandoc’s PDF engine), routed through the documents sandbox profile: installed on the host when no sandbox is configured, or present in [sandbox].documents_image when one is. A missing binary returns an actionable error naming where it was checked (host PATH or the sandbox label) rather than failing silently — see document-generation.md for a reference Containerfile with everything docx/pdf generation and PDF extraction need.

Always prompts for approval; refuses to overwrite an existing file unless overwrite=true. Not in scope: any document parsing beyond what read_document already covers.

write_file

Full overwrite. Creates parent directories as needed.

ParamTypeDefaultNotes
pathstring—Absolute or cwd-relative
contentstring—Full new file contents

Always prompts for approval. The preview shows the path and a 200-char content snippet.

The preferred workflow is read_file(path, anchors=true) followed by one batched apply_hashline or edit_anchored call. Copy the exact line#hash anchor or the complete JSON receipt from the read result; do not reconstruct a multi-line old_string when a receipt is available. apply_hashline uses one strict addressing mode per hunk: line anchor or receipt/byte span, never both. Direct edit calls serialize same-file operations through a canonical-path queue; loop-dispatched calls also use the existing mutation claims. edit_file remains a compatibility path for exact string replacement, and syntax/LSP operations are preferred for structural or semantic changes.

ParamTypeDefaultNotes
pathstring—Must exist
old_stringstring—Must be non-empty and != new_string unless receipt is supplied
new_stringstring—The replacement
replace_allboolfalseDisable uniqueness check
receiptobject—Complete receipt from an anchored read; validates the exact byte span and allows old_string to be omitted

Always prompts for approval. The TUI’s approval modal renders a colored diff (red − / green +) so you see exactly what’s about to change.

edit_anchored

Apply line-oriented edits validated against anchors returned by read_file(..., anchors=true) or read_many_files(..., anchors=true).

ParamTypeDefaultNotes
pathstring—Must exist
operations[]object—Ordered operations; each object has op plus anchor or start_anchor/end_anchor

Supported op values:

  • replace_range — replace the inclusive line range from start_anchor to end_anchor with new_text
  • delete_range — delete the inclusive line range from start_anchor to end_anchor
  • insert_before — insert new_text before anchor
  • insert_after — insert new_text after anchor

Anchors should be passed as full line#hash references, for example 42#a8f13c2b. The tool re-reads the file and rejects stale or ambiguous anchors before writing, so it is the preferred path for drift-sensitive block edits that would be fragile with edit_file or a stale diff. Missing-anchor and stale-anchor failures are recoverable: re-read the target block with anchors=true, then retry with the current required anchor or start_anchor/end_anchor values.

The standard approval modal offers allow once, allow for this session, always allow, deny, and always deny when their derived permission rules are available. For edit_anchored, these choices use the same Edit(...) permission namespace as edit_file and apply_hashline, so an existing Edit(pattern) rule can allow, ask, or deny all three edit paths consistently. Permission approval never bypasses stale-anchor, overlap, or no-op validation.

Anchor hashes include the line number, so an earlier edit that shifts lines makes later anchors from the same read stale. When an anchor’s line number no longer matches, the tool looks within 100 lines for exactly one line whose text hashes to that anchor under its original line number and uses it, reporting original→current line numbers in the result; a repeated line never relocates. Relocation matches on text, so if the anchored line itself was rewritten and an identical line exists elsewhere nearby, the edit lands on that line; re-read when unsure. If nothing relocates, the stale-anchor error quotes the current lines around the anchor with fresh line#hash tokens so the retry needs no extra read. apply_hashline anchor hunks use the same resolution, and empty or zero offset/length/hash/old values sent next to an anchor are ignored.

Like apply_hashline, the target file is checked against a 64 MiB size limit before it is read (file_too_large if over).

syntax_range

Return offline syntax ranges around a source position. Go uses its standard-library AST parser; TypeScript/JavaScript, Python, and Rust use conservative structural scanners. This read-only helper chooses a local edit target and never writes files.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset
max_resultsint50Clamped to 500

Output rows include kind name [detail], the LSP range, one-based lines, exact half-open bytes=start-end, an anchor_read hint, and a JSON receipt containing offset, length, hash, and the exact escaped old source text. The receipt fields can be passed directly to apply_hashline together with the replacement new text. Receipts larger than 64 KiB are omitted with a receipt_omitted marker, and total tool output is capped at 256 KiB; use the bounded anchor_read hint to select a smaller range. The hash is the first 16 hexadecimal characters of SHA-256 over exactly those bytes, with no normalization. Ranges are ordered smallest-to-largest. Canonical kinds are function, method, type, call, import_block, field, file, block, control, declaration, and container. Recoverable warnings are deduplicated and printed after ranges.

Covers Go (standard library parser), TypeScript/JavaScript (including .mjs and .cjs) and Rust (a shared Chroma-token brace-depth scanner), and Python (a Chroma-token indentation scanner). Scanner backends emit only constructs with unambiguous boundaries and omit uncertain or incomplete calls, imports, and fields. Other languages should use lsp_selection_ranges when a language server is installed. GA; the syntax_ranges flag is a no-op kept for one release for compatibility.

apply_hashline

Apply one or more content-hash anchored text edits to a single file. This is the safest patch path after a fresh read_file or read_many_files with anchors=true: the tool verifies the old byte span by hash before writing, relocates only when the old span appears uniquely near the recorded offset, and otherwise returns a recoverable stale/ambiguous-anchor error with a suggested re-read range. It never applies a guess.

ParamTypeDefaultNotes
pathstring—File to edit; absolute or cwd-relative
anchorstring—Line-addressed form: the exact line#hash token printed by read_file/read_many_files (anchors=true) or accepted by edit_anchored, e.g. 42#a1b2c3d4. Mutually exclusive with offset/length/hash/old
offsetint—Byte-addressed form: byte offset from the hashline receipt
lengthint—Byte length from the hashline receipt; must equal the byte length of old
hashstring—16-hex SHA-256 prefix copied from the receipt (a model cannot compute it)
oldstring—Exact old text covered by the anchor; must be non-empty and end with a newline exactly when the receipt says ends_with_newline=true
newstring—Replacement text; may be empty (deletes the line, for an anchor hunk)
hunks[]object—Optional multi-hunk form; each hunk is either anchor-addressed (anchor + new) or byte-addressed (offset/length/hash/old/new)

Line addressing. anchor is a second way to point at a hunk, alongside the byte-based offset/length/hash/old. It takes the exact line#hash token read_file/read_many_files print before each line with anchors=true (line numbers are absolute, so the token is valid regardless of what offset/limit window it came from) — copy it as-is, no parsing needed. The tool re-reads the file, resolves the anchor the same way edit_anchored does (stale-line and hash-mismatch errors read identically), and derives that line’s exact byte span, text, and hash itself — old, hash, offset, and length are never needed for an anchor hunk. This is the point of anchor: touching one line inside a large read_file window no longer means reproducing the whole window as old. new replaces the anchored line (its own terminator included), so an empty new deletes the line, and a multi-line new can turn one line into several. Two hunks may not target the same line (rejected as overlapping_hunks), but adjacent lines each addressed by their own hunk compose into one multi-line edit in a single call. An anchor hunk may be freely mixed with byte-addressed hunks in the same hunks array.

Always prompts for approval, validates the same write-path rules as edit_file (which refuse a symlinked leaf today), and writes atomically via same-directory temp file plus rename — resolved through any symlink chain first, so a symlinked target is written in place rather than replaced by a plain file. On stale_anchor or ambiguous_anchor, re-read the suggested range with anchors=true, copy the current text and receipt, then retry.

Inserting. There is no separate insert: an empty span hashes to a constant, so it could not prove the file is unchanged, and an insert at a stale offset would land in the wrong place (or split a multi-byte character). An empty old is rejected with empty_anchor. To insert with a byte hunk, anchor on adjacent text: set old to the neighbouring line and new to that line plus the addition. To insert with a line anchor, set new to the anchored line’s own text plus the addition — an anchor with no new, or an empty new, deletes the line instead. Use write_file to fill an empty or new file.

Length is verified. length must equal the byte length of old (invalid_range otherwise). It decides how many bytes are replaced, so a value that disagreed with the hashed text could otherwise replace bytes nobody verified.

Line endings. The hash is byte-exact, but models emit plain newlines and cannot reliably reproduce carriage returns. In a file that is predominantly CRLF, old is used exactly as sent when it matches its hash; only if it does not, and its CRLF form does, is the CRLF form used, so a real mismatch is never masked. new is converted to CRLF when old was CRLF-only or had no newline, so an edit cannot leave mixed endings. If old itself mixes endings, the model is managing them and neither side is rewritten. LF files are never converted.

Size limit. The target file is stat’d and rejected (file_too_large) before it is read if it exceeds 64 MiB. This bounds a single file this tool will load into memory to apply an edit; it is unrelated to the 512 KiB read-window cap on read_file/read_many_files, which bounds what one read call returns, not how large the file itself may be.

Preview. On success the tool returns a bounded unified diff: distant edits are separate hunks, lines are clipped, the whole preview is capped at 16 KiB and 80 changed lines, and …[truncated diff] always appears when anything was left out. The same renderer backs the diffs in lsp_rename_preview, lsp_format_preview, and lsp_code_action_preview, where a rename touches many places in one file.

apply_diff

Apply a legacy unified diff patch using git apply. Prefer apply_hashline for edits after a fresh read_file or read_many_files receipt; keep apply_diff for broad unified diffs, generated patches, or multi-file changes that do not have hashline anchors.

ParamTypeDefault
diffstring—

Always prompts for approval. The diff header is parsed and each touched file is run through the same write-path validator write_file / edit_file use — yottacode-managed state, .git internals, paths outside cwd, and symlinks are refused before git apply runs. Before path validation, the tool rejects common model-authored wrappers such as markdown fences and apply_patch-style *** Begin Patch blocks with typed malformed-patch errors; the error includes a minimal unified-diff example. These structural checks also run before the approval prompt, so a patch that can never apply is bounced to the model without asking you to approve it (other tools can opt in via the ArgsPrevalidator interface). After validation, common model-authored defects such as miscounted @@ hunk headers and whitespace drift in context lines are tolerated while applying. Malformed patch syntax (corrupt patch, bare @@, invalid hunk headers) and stale context (patch does not apply) are classified separately so the TUI can show compact recovery guidance instead of the raw patch payload. Stale-context failures should prompt a fresh read — often with anchors=true — and retry with apply_hashline when the change can be expressed against a current hashline receipt. Use edit_anchored only when a line anchor is the safer fit. A Deny(Edit(<pattern>)) rule applies if any target path matches; an Allow(Edit(<pattern>)) rule auto-approves only when every target path matches (mixed-path diffs still prompt).

mkdir

Create a directory and any missing parents.

ParamTypeDefault
pathstring—

Always prompts for approval.

copy_file

Copy a file from src to dst. Creates destination parent directories if needed.

ParamTypeDefault
srcstring—
dststring—

Always prompts for approval.

move_file

Move or rename a file or directory. Creates destination parent directories if needed.

ParamTypeDefault
srcstring—
dststring—

Always prompts for approval.

delete_file

Delete a file or an empty directory.

ParamTypeDefault
pathstring—

Always prompts for approval.

list_git_changed_files

List changed files in the current git repo, combining staged, unstaged, and optionally untracked files.

ParamTypeDefault
stagedbooltrue
unstagedbooltrue
untrackedbooltrue

No approval.

git_branch_status

Show the current branch, upstream, ahead/behind counts, and whether the working tree is dirty.

This is a compact status helper for coding sessions where the model wants repo state without parsing full git status output.

No parameters. No approval.

git_show_file_at_rev

Read a file from a specific git revision without changing the working tree. Useful for regressions, comparisons, and historical inspection.

ParamTypeDefault
pathstring—
revstringHEAD

No approval.

git_diff_files

Show a diff for specific refs and/or file paths.

ParamTypeDefaultNotes
basestringcurrent working treeOptional base revision
headstring—Optional head revision
paths[]string—Restrict diff to one or more files

Examples:

  • diff current working tree: omit both base and head
  • diff one revision vs working tree: set base
  • diff two revisions: set both base and head

No approval.

git_diff_stat

Compact diffstat — files touched plus line counts, no hunks. The cheap first-pass review surface: call it before pulling full diffs so the context budget goes to the files that matter.

ParamTypeDefaultNotes
basestringworking treeOptional base revision
headstring—Optional head revision (requires base)
paths[]string—Optional path filter

No approval.

git_diff_staged

Diff of the staged changes — exactly what git commit would record. The dedicated surface for /git-commit-style flows, so nothing has to infer --cached by hand.

ParamTypeDefault
paths[]string—

No approval.

git_diff_unstaged

Diff of the unstaged tracked edits (work not yet git add-ed). Untracked files appear in no diff — enumerate those with list_git_changed_files.

ParamTypeDefault
paths[]string—

No approval.

git_stage_files

Stage specific files (git add -- ...) or all changes (git add -A).

ParamTypeDefaultDescription
paths[]string—Paths to stage. Mutually exclusive with all.
allboolfalseStage all tracked, untracked, and deleted files (git add -A). Mutually exclusive with paths.

Provide either paths or all, not both. Returns staged N file(s) for path mode or staged all changes for the bulk mode.

Always prompts for approval.

git_unstage_files

Unstage specific files with git reset HEAD -- ....

ParamTypeDefault
paths[]string—

Always prompts for approval.

git_create_branch

Create a new local branch and switch HEAD to it (git switch -c <name> [<start_point>]). The branch name is validated via git check-ref-format --branch before any switch happens, and the tool refuses with a branch_exists error if a local branch by that name already exists — it never overwrites or fast-forwards. The working tree is left as-is (no precheck against dirty state); git itself will refuse the switch if local changes would be clobbered.

ParamTypeDefaultDescription
namestring—Branch name to create (required).
start_pointstringHEADOptional starting ref (commit / branch / tag).

Returns created=true branch=<name> from=<short_sha> on success.

Always prompts for approval.

git_commit

Create a commit from the currently staged changes.

ParamTypeDefault
messagestring—

Always prompts for approval.

git_commit_amend

Amend the last commit: fold the staged changes into it, optionally replacing the message (empty keeps it). Rewrites that commit — the approval preview carries a ⚠ rewrites the last commit line, and the tool description warns against amending already-pushed commits.

ParamTypeDefaultNotes
messagestringkeep currentReplacement message; empty → --amend --no-edit

Always prompts for approval.

git_commit_fixup

Create a fixup! commit from the staged changes targeting an earlier commit (git commit --fixup=<commit>). The history rewrite happens at the user’s later git rebase --autosquash, not here.

ParamTypeDefault
commitstring—

Always prompts for approval.

git_commit_context

Composite read-only snapshot used to draft a one-line commit message without parsing bash heredoc output. Returns labeled sections under ## state, ## staged.name-status, ## staged.diff, ## recent.subjects, ## branch.commits, ## prose, ## unstaged, and ## untracked. The ## state block carries the deterministic staged_empty=, detected_style= (one of conventional / ticket-prefix / plain, chosen by majority over the last 15 subjects), and branch= fields so callers branch on typed flags rather than text inference.

Pair with git_commit_apply — context tool gathers state, apply tool validates and commits.

ParamTypeDefault
(none)

No approval. Parallel-safe.

git_commit_apply

Composite mutator that validates a one-line subject and runs git commit -F - against the currently staged changes. The following rejections fire before invoking git (deterministic Go, not model judgment):

  • empty staging (committed=false reason=staged_empty)
  • empty / whitespace-only message
  • multi-line message (no body or footer accepted)
  • subject longer than 72 characters
  • subject ending in a period

Returns a typed envelope. On success: committed=true sha=<hash> plus post-commit ## unstaged / ## untracked sections. On pre-commit hook failure: committed=false reason=hook_error followed by the hook’s verbatim output. The tool never auto-retries, auto-stages, or amends.

ParamTypeDefault
messagestring—

Always prompts for approval.

pr_context

Composite read-only snapshot used to open a pull request without parsing multi-step bash output. Returns labeled sections under ## state, ## diff.stat, ## commits.log, and ## pr.template. The ## state block carries deterministic flags callers branch on: resolved_base=, base_resolution= (one of explicit, origin-head, fallback:<name>, unresolved), current_branch=, base_equals_current=, ahead_count=, pushed_to_origin=, and gh_available=.

Base resolution priority: explicit base argument → origin/HEAD symbolic ref → first of main / master / develop that exists locally. The pushed_to_origin check uses git ls-remote --exit-code --heads origin <branch> so empty-remote repos correctly report false without crashing.

Pair with pr_create — context tool gathers state, create tool validates the title and opens the PR.

ParamTypeDefault
basestring(auto-resolved from origin/HEAD then fallback chain)

No approval.

pr_create

Composite mutator that validates a PR title and opens the pull request through the typed internal/github.Interface adapter. The following rejections fire before dialing the adapter (deterministic Go, not model judgment):

  • empty title / body / base (created=false reason=validation)
  • multi-line title
  • title longer than 72 characters
  • title ending in a period

Returns a typed envelope. On success: created=true url=<url> number=<n>. On a missing or unauthenticated gh CLI: created=false reason=github_unavailable so the procedural /git-create-pr can fall through to draft-only output without surfacing an opaque exec failure. On other gh errors: created=false reason=github_error followed by the gh output verbatim. The tool never auto-retries, auto-edits, or auto-merges.

The adapter behind this tool is internal/github.TypedClient, backed by the go-github/v66 REST client. Auth resolves through a three-tier precedence chain: $GITHUB_TOKEN env var → gh auth token shell-out (one-shot, cached for the session) → ~/.yottacode/github.json (yottacode-native PAT, written by a future yottacode setup github flow). The gh CLI is no longer required for API calls — only optionally used to source the token when $GITHUB_TOKEN isn’t set.

ParamTypeDefault
basestring—
titlestring—
bodystring—
draftboolfalse

Always prompts for approval.

pr_read

Read-only fetch of a single pull request’s metadata in one API call: number, title, body, state, draft flag, base/head refs, head SHA, mergeable state, author, labels, and URL. No diff, no check runs.

Prefer this over run_bash gh pr view --json ... whenever the goal is reading PR metadata — no subprocess, and the result is structured. Reach for pr_review_context instead when the diff or CI status also matters (PR review, audit).

Returns a typed snapshot keyed by section headers (## state, then ## pr). The ## state block flags not_found and github_unavailable, so a caller branches on typed fields instead of parsing free-text errors.

ParamTypeDefault
refstring(PR for the current branch)

ref accepts a PR number ("17") or a branch name.

No approval. Parallel-safe.

pr_review_context

Composite read-only fetcher for the procedural /git-review-pr flow. Calls the three Interface read methods (ReadPR, ListPRChecks, ReadPRDiff) and folds their results into one typed snapshot under ## state, ## pr, ## checks.summary, ## checks, and ## diff headers.

The ## state block carries deterministic flags callers branch on: ref=, not_found= (the gh CLI couldn’t resolve the ref to an existing PR), github_unavailable= (gh missing or unauthenticated), and failing_checks= (comma-separated names of check runs whose conclusion was FAILURE, CANCELLED, TIMED_OUT, or ACTION_REQUIRED). When not_found or github_unavailable are true the snapshot short-circuits — the pr/checks/diff sections are omitted to keep the model’s branching clean.

Failing-check classification covers both the GraphQL check-run shape (Conclusion populated) and the legacy status-context shape (State=“FAILURE”/“ERROR” with empty Conclusion). The diff is capped at 64 KiB with a truncation marker pointing at gh pr diff <ref> for the full content.

ParamTypeDefault
refstring(uses current branch’s PR)

No approval. Touches the network (not parallel-safe).

pr_watch_checks

Read-only PR CI watcher. It resolves the PR, polls ListPRChecks until checks pass, fail, or the timeout expires, and when a check fails it fetches capped GitHub Actions job log tails through the typed GitHub adapter. Use this instead of run_bash gh run watch … followed by gh run view --log-failed | tail.

The returned snapshot uses ## state, ## pr, ## checks.summary, ## checks, and ## failed_logs. State flags include not_found=, github_unavailable=, timed_out=, all_success=, failed=, and failing_checks=.

ParamTypeDefault
refstring(uses current branch’s PR)
timeout_secondsint900
poll_interval_secondsint15
log_tail_linesint240

No approval. Touches the network (not parallel-safe).

pr_check_logs

Read-only helper for failed CI logs. It resolves the PR ref (number, branch, or current branch when omitted), finds failed GitHub Actions workflow runs for the PR head SHA, and returns bounded tails for the failed jobs only. Use it instead of run_bash patterns such as gh run view <id> --log-failed | tail -240.

ParamTypeDefault
refstringcurrent branch’s PR
max_linesinteger240

No approval. Touches the network (not parallel-safe).

pr_rerun_checks

Approval-gated helper for retrying failed CI. It resolves the PR ref, finds failed GitHub Actions workflow runs for the PR head SHA, and asks GitHub to rerun failed jobs for each failed run. It does not rerun successful jobs.

ParamTypeDefault
refstringcurrent branch’s PR

Always prompts for approval because it mutates remote CI state and may consume CI minutes.

pr_update

Composite mutator that rewrites an existing PR’s title and body. Paired with the procedural /git-update-pr slash command for the “follow-up commits made the original description stale” workflow.

Deterministic guarantees:

  • Title validation (reuses validatePRTitle from pr_create): rejects empty, multi-line, oversize (>72 chars), and trailing-period titles before dialing the adapter.
  • Non-empty body guard: empty body would clobber the existing PR description, which is almost never intended. Caught in Go with updated=false reason=validation.
  • Scope-pinned: only edits title and body. Labels, base, reviewers, draft state, milestone, and projects are not accepted — /git-update-pr enforces the same scope at the prompt level.

Returns a typed envelope. On success: updated=true url=<url> number=<n>. On a missing PR: updated=false reason=not_found with a hint pointing at /git-create-pr. On gh-unavailable: updated=false reason=github_unavailable. On other gh errors: updated=false reason=github_error with the gh output verbatim. The tool never auto-retries, auto-edits other fields, or auto-merges.

ParamTypeDefault
refstring(uses current branch’s PR)
titlestring—
bodystring—

Always prompts for approval.

pr_add_comment

Composite mutator that posts a top-level conversation comment on a pull request. Used to cross-link related issues (Refs #42), leave follow-up notes after a /git-review-pr run, or surface structured summaries.

Deterministic guarantees:

  • Non-empty body: a blank or whitespace-only body is rejected in Go before dialing the adapter (posted=false reason=validation).
  • Body cap: 16 KiB (16384 characters) of comment Markdown, also enforced before the adapter call.
  • Scope-pinned: top-level conversation comments only. Inline review comments on specific lines are a different GitHub API surface and are deliberately out of scope.

The approval modal renders the full body, so the comment is read before it lands. Returns a typed envelope. On success: posted=true url=<url>. Otherwise posted=false with reason= discriminating validation, not_found, github_unavailable, and github_error — each carrying a hint rather than a raw error string.

ParamTypeDefault
refstring(PR for the current branch)
bodystring— (required, <= 16384 chars)

Always prompts for approval.

git_push

Composite mutator that pushes the current branch to origin. Deterministic guarantees:

  • Upstream-aware: detects whether the current branch already tracks an upstream and adds -u origin HEAD only on first push.
  • Detached-HEAD early exit: rejects the push before invoking git when HEAD isn’t on a branch — returns pushed=false reason=detached_head.
  • No force-push surface: --force / --force-with-lease are intentionally not accepted. Use the unified git tool when you actually need force-push.

Returns a typed envelope. On success: pushed=true branch=<name> set_upstream=<bool> plus a best-effort PR-URL lookup via internal/github.Interface.ReadPR. When a PR exists for the branch, pr_number=<n> and pr_url=<url> are populated so callers (and /git-push) can surface a “PR updated” footer. When no PR exists, the envelope hints at /git-create-pr as the next step.

Git errors (exit non-zero) populate pushed=false reason=git_error with the verbatim git output. The tool never auto-retries, auto-force-pushes, or rebases to “fix” a rejection.

ParamTypeDefault
(none)

Always prompts for approval.

git_log_file

Show history for a single file.

ParamTypeDefault
pathstring—
limitint10

No approval.

git_blame_lines

Show blame output for a line range in a file.

ParamTypeDefault
pathstring—
startint—
endint—

No approval.

git_merge_base

Find the merge base between two refs.

ParamTypeDefault
basestring—
headstring—

No approval.

git_commits_between

One-line commit summaries in base..head (commits reachable from head but not base), newest first. The range view for review and PR explanation.

ParamTypeDefault
basestring—
headstringHEAD
limitinteger20

No approval.

git_branch_ahead_behind

Branch relationship in one call: ahead=N behind=M between head and base, plus their merge base — replaces composing merge-base + rev-list --left-right --count by hand.

ParamTypeDefault
basestring—
headstringHEAD

No approval.

git_branch_diff

One-stop branch review summary against a base branch: ## state (branch, merge base, ahead/behind), ## commits (newest first, capped at 30), ## changed-files (name + status), and ## diffstat — everything except the hunks. The backbone for “what changed vs main?”; pull actual diffs afterwards with git_diff_files for just the files that matter.

ParamTypeDefault
basestring—

No approval.

git_checkpoint

Create a local checkpoint commit from all current changes.

ParamTypeDefault
messagestringcheckpoint

Always prompts for approval.

rollback

Reset the git working tree to a target commit. Defaults to HEAD~1. This is destructive and discards uncommitted changes.

ParamTypeDefault
targetstringHEAD~1

Always prompts for approval.

run_tests

Run a test command in the repo. Defaults to go test ./.... Successful runs return only the command and exit=0; failed runs return the nonzero exit code and a bounded tail of combined failure output so repeated test loops do not fill model context with routine successful logs.

ParamTypeDefault
commandstringgo test ./...
pathstring.

Prompts for approval in foreground use. When a command invokes Go, run_tests sets HOME, TMPDIR/GOTMPDIR, XDG_CACHE_HOME, XDG_CONFIG_HOME, GOCACHE, GOMODCACHE, and GOTELEMETRY=off to yottacode-owned scratch/cache paths so Go cannot leave repo-root .cache/, .config/, .local/, or go/ artifacts. Background dispatch workers cannot run run_tests because tests execute project code without a human approval surface.

Go debug tools

Go debugger tools are backed by dlv dap; see debug.md for the full workflow and policy. debug_start always prompts because it executes the selected program or test package. debug_eval also prompts because expression evaluation is more powerful than passive inspection. The remaining debug tools operate inside the already-approved live session without another prompt.

v1 allows one live Go debug session per yottacode session. debug_continue and debug_step wait up to 30 seconds for a stop event, then report still running after 30s. yottacode resolves dlv from PATH only and never downloads binaries.

media_probe

Inspect a local audio/video file with ffprobe and return compact stream metadata: duration, container, video dimensions/fps/codec, audio codec/sample rate/channels, and rotation when present. The media bytes are never sent to the model.

ParamTypeDefault
pathstring—

No approval. Requires ffprobe on PATH; yottacode doctor reports readiness.

media_analyze

Run ffmpeg detectors and return a unified edit decision list for review before rendering. mode=auto runs audio silence detection when an audio stream exists and visual idle detection when a video stream exists, so silent terminal demos still produce useful candidates.

ParamTypeDefault
pathstring—
modestringauto
detectors[]stringselected from streams
silence_threshold_dbnumber-35
min_silence_durationnumber0.6
visual_noise_thresholdnumber0.003 (0.0015 in terminal_demo)
min_idle_durationnumber1.0 (0.8 in terminal_demo)

Modes: auto, audio_silence, visual_idle, terminal_demo, or all. Returned candidates include start, end, duration, detector, confidence, and reason so the agent can explain why each cut was proposed. No approval. Requires ffmpeg on PATH.

media_compose

Compose an approved storyboard into one draft MP4 with ffmpeg. It assembles ordered title-card, image, and clip segments after validating every input path as a read and the output path as a write. It refuses to overwrite existing files unless overwrite=true is passed. See video-tools.md for examples and the capability matrix.

ParamTypeDefaultNotes
outputstring—Draft MP4 output path; must end with .mp4
segments[]object—Ordered title, image, or clip segments
widthint1920Canvas width
heightint1080Canvas height
fpsnumber30Output frame rate
background_colorstring#0b0f0dTitle-card background color
overwriteboolfalseMust be explicit to replace an existing output

Segment fields:

FieldApplies toNotes
typealltitle, image, or clip
texttitleRequired title-card text
pathimage, clipRequired local input path
durationtitle, imageRequired positive duration in seconds, capped at 300 seconds per synthetic segment
keep_rangesclipOptional single range to trim a clip; pre-render complex cuts with media_render first
captionimage, clipOptional lower-third text overlay
templatealldefault, hero, feature, or closing branded layout accents
motionimagenone, zoom_in, or zoom_out image motion
transitionallnone or fade

Always prompts for approval. Requires ffmpeg on PATH. The current implementation renders video-only draft MP4s with optional branded templates, lower thirds, simple fades, and image zoom/pan motion; use media_render afterward for final platform profiles and caption burn-in.

media_render

Render an approved edit plan with ffmpeg. The tool validates the source/caption/intro/outro paths as reads, validates output paths as writes, and refuses to overwrite existing files unless overwrite=true is passed.

ParamTypeDefaultNotes
inputstring—Source media file
outputstring—Output file, or base output when multiple profiles are requested
profiles[]string["youtube_16x9"]youtube_16x9, x_16x9, x_vertical_9x16, gif_preview, gif_preview_large
gif_widthintprofile defaultOptional GIF width override in pixels
gif_fpsnumber12Optional GIF frame-rate override
speednumber1Optional GIF playback speed multiplier, e.g. 1.5 or 2
keep_ranges[]object—Approved ranges to keep, in seconds; multiple ranges are joined with ffmpeg concat
cut_ranges[]object—Approved ranges to remove; converted to keep ranges from probed duration when keep_ranges is omitted
captions_pathstring—Optional .srt / .ass subtitle file to burn in
intro_path / outro_pathstring—Optional composition assets
overwriteboolfalseMust be explicit to replace an existing output

Always prompts for approval. Requires ffmpeg on PATH. Multi-profile outputs append the profile name to the file stem, such as demo-youtube_16x9.mp4, demo-x_16x9.mp4, and demo-gif_preview.gif. Multi-range edits are rendered with ffmpeg trim/atrim + concat, so approved fluff cuts are actually removed from the final output. gif_preview renders a 960px-wide 12 fps looping GIF and gif_preview_large renders a 1440px-wide version for readable terminal text; both use ffmpeg palette generation/paletteuse. For slow terminal clips, set speed to 1.5 or 2 to shorten the GIF without changing the approved edit ranges. Use GIF profiles for short teasers because GIFs grow quickly. See marketing-videos.md for the recommended screen-recording workflow and video-tools.md for the full video capability matrix.

list_dir

One line per entry: <type>\t<name> where <type> is d (dir), f (file), or l (symlink). Capped at 100 entries.

ParamTypeDefault
pathstring.

No approval.

glob

Doublestar pattern match (** recursive). Returns paths relative to the cwd. Capped at 200 results.

ParamTypeDefaultNotes
patternstring—e.g. **/*.go, internal/agent/*.go
cwdstringtool’s cwdRoots the search

No approval.

grep

Prefers rg (ripgrep); falls back to GNU grep -E/-F. Pattern arguments are passed via argv — no shell, no injection.

ParamTypeDefaultNotes
patternstring—Required
pathstring.File or directory
regexboolfalseWhen false, treats pattern as a fixed string
ignore_caseboolfalse
max_resultsint50Hard cap

Output is capped at 256 KiB. Exit code 1 (no matches) is treated as “no results”, not as an error. The TUI card is separately capped at 10 visible match rows and then shows …N more match(es); the model still receives every match returned by the tool.

No approval.

lsp_status

Detect supported source languages in the workspace and report whether the matching language server command is installed on PATH. It never installs anything; when a server is missing it includes a concise install hint and an exact install_command that the agent may offer to run through normal bash approval when the active task would benefit from LSP. Each row also reports the offline structure fallback as syntax=parser, syntax=regex, or syntax=none.

Supported language servers and fallback modes:

LanguageServer commandOffline syntaxInstall hint
Gogoplsparsergo install golang.org/x/tools/gopls@latest and ensure $(go env GOPATH)/bin is on PATH
TypeScript/JavaScripttypescript-language-server --stdioparsernpm install -g typescript typescript-language-server
Pythonpyright-langserver --stdioparsernpm install -g pyright
Rustrust-analyzerparserinstall through rustup, your package manager, or rust-analyzer’s upstream docs
ParamTypeDefaultNotes
pathstring.Workspace path to scan

No approval.

lsp_symbols

Search workspace symbols through an installed language server. If path points to a source file, its extension selects the language. If path is omitted or a directory, yottacode scans the workspace and picks the first detected language with an available server. Missing servers return unavailable: ... plus the same install hint as lsp_status.

ParamTypeDefaultNotes
querystring—Required symbol query
pathstring.File or workspace path used for language detection
max_resultsint50Clamped to 500

No approval.

lsp_document_symbols

List structural symbols declared in a single source file through the matching language server. Hierarchical server responses are flattened and include the parent symbol as the Container column when available.

ParamTypeDefaultNotes
pathstring—Required source file
max_resultsint50Clamped to 500

No approval.

lsp_document_highlights

Show current-file symbol occurrences for a source position through textDocument/documentHighlight. This is narrower than lsp_references: it returns only the open document’s read/write/text highlights, so agents can inspect local usage without pulling workspace-wide reference noise into context.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset

Output rows are kind\tpath:startLine:startColumn-endLine:endColumn, where kind is text, read, write, or kindN for unknown server values. No approval.

lsp_selection_ranges

Show nested syntax selection ranges around a source position through textDocument/selectionRange. Ranges are returned from the smallest expression to larger enclosing ranges such as statement, block, or function when the server provides them. This helps agents choose the right read/edit scope before making semantic changes.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset
max_resultsint50Clamped to 500

Output rows are depth\tpath:startLine:startColumn-endLine:endColumn, with depth 0 as the smallest range. No approval.

lsp_definition

Find definition locations for a source position through the matching language server. line and character are zero-based LSP positions; returned locations are one-based path:line:column rows for terminal readability.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset

No approval.

lsp_type_definition

Find type definition locations for a source position. Parameters and output match lsp_definition.

No approval.

lsp_implementation

Find implementation locations for an interface, method, or symbol position. Parameters and output match lsp_definition.

No approval.

lsp_references

Find reference locations for a source position through the matching language server. Like lsp_definition, inputs are zero-based and output locations are one-based.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset
include_declarationboolfalseInclude the declaration in references
max_resultsint50Clamped to 500

No approval.

lsp_diagnostics

Return compile/type diagnostics for a source file by opening it in the matching language server and reading publishDiagnostics. Output distinguishes published clean results from diagnostics not published before timeout, and includes severity, source, code, tags, and related locations when the server provides them. Missing servers return an unavailable result with install hints.

ParamTypeDefaultNotes
pathstring—Required source file

No approval.

lsp_changed_files_diagnostics

Run diagnostics across git-changed supported source files. Use this after edits in an LSP-supported language before declaring the change done. Multi-file results are summarized as clean/issues/pending/skipped counts so clean checks do not print one repeated row per file.

ParamTypeDefaultNotes
max_filesint20Maximum changed source files to inspect

No approval.

lsp_hover

Show hover/type/documentation information for a source position.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset

No approval.

lsp_signature_help

Show callable signatures at a source position, including the active signature and active parameter when the server reports them. This is useful before writing or changing a function call.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset

No approval.

lsp_code_actions

List language-server code actions and quick fixes for a range without applying them. Output includes each action’s zero-based index and whether it carries an edit, command, related diagnostics, or requires a server-side resolve step. Use lsp_code_action_preview for editable actions before applying them with lsp_apply_workspace_edit.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based start line
characterint—Zero-based UTF-16 start character
end_lineintlineZero-based end line
end_characterintcharacterZero-based UTF-16 end character

No approval.

lsp_code_action_preview

Preview one code action’s normalized WorkspaceEdit JSON without writing files. Select the action by exact title or by zero-based index from lsp_code_actions; index is preferred when titles repeat. The preview output includes bounded diff hunks plus per-file preview hashes. Pass the returned apply_payload to lsp_apply_workspace_edit only after reviewing the diff.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based start line
characterint—Zero-based UTF-16 start character
end_lineintlineZero-based end line
end_characterintcharacterZero-based UTF-16 end character
titlestring—Exact code action title
indexint0 when title is omittedZero-based action index

No approval.

lsp_rename_preview

Preview a semantic rename as normalized WorkspaceEdit JSON without writing files. When the server supports textDocument/prepareRename, yottacode validates the target position first and returns an explicit unavailable result if the cursor is not renameable. Preview output includes bounded diff hunks plus per-file preview hashes. Pass the returned apply_payload to lsp_apply_workspace_edit only after reviewing the affected files.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset
new_namestring—Replacement symbol name

No approval.

lsp_format_preview

Preview server formatting edits for one file without writing files. Preview output includes bounded diff hunks plus per-file preview hashes so the later apply step can reject stale previews.

ParamTypeDefaultNotes
pathstring—Required source file

No approval.

lsp_apply_workspace_edit

Apply a previously previewed WorkspaceEdit through yottacode’s path validator, checkpoint snapshot flow, and normal approval modal. The language server never writes directly. Preview payloads carry per-file hashes so apply can reject stale previews, and overlapping edit ranges are refused before any file write. In this experimental version, still be cautious with non-ASCII / UTF-16-heavy edit ranges until broader real-server coverage lands.

ParamTypeDefaultNotes
editobject—WorkspaceEdit object from a preview tool’s apply_payload

Requires approval.

lsp_call_hierarchy

Show incoming and outgoing call hierarchy entries for a source position.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset

No approval.

lsp_impact

Return a composite impact report for a source position. The tool batches hover, definition, references, call hierarchy, diagnostics, and optional Code Map import blast radius into one compact result. It is intended for pre-refactor planning and review, where raw LSP calls would otherwise require several tool rounds. When code_map is not enabled, the Code Map section reports unavailable and the LSP sections still run.

ParamTypeDefaultNotes
pathstring—Required source file
lineint—Zero-based line
characterint—Zero-based UTF-16 character offset
include_declarationboolfalseInclude declarations in reference results
max_resultsint50Cap per section, clamped to 500

No approval.

code_map

Read-only. Experimental behind code_map.

Returns a bounded repository structure map from yottacode’s code index: directories, files, symbols, and cheap counts such as LOC and exported/private symbols. Pass query to filter by path, symbol name, kind, or container. Output is capped by max_results so agents can orient themselves without reading files first.

code_symbols

Read-only. Experimental behind code_map.

Returns indexed symbols either for one path or for a query. This is the symbol-only companion to code_map; use it when names and locations are enough and opening whole files would waste context.

code_structure_projection

Read-only unless to_file is set. Experimental behind code_map.

Generates a compact, token-efficient projection of the indexed structure: root counts, important files, and their symbols. It is designed for agent context projection, not for dependency or impact analysis; see code_dependencies, code_dependents, and code_impact for those. Set to_file to write the full, untruncated projection to disk instead of the max_results-capped inline result — this is a write like any other (approval-gated, checkpoint- tracked), sized for exporting a projection too large for one turn’s context window rather than for routine use.

code_dependencies

Read-only. Experimental behind code_map.

Returns direct outgoing import dependencies for an indexed file/path query, proximity-ranked (same directory as the target first). Go, TypeScript/ JavaScript, Python, and Rust in-workspace imports are resolved; unresolved standard-library, third-party, or ambiguous imports are omitted rather than invented. See docs/code-map.md for exact per-language resolution scope.

code_dependents

Read-only. Experimental behind code_map.

Returns direct incoming import dependents for an indexed file/path query. This is the reverse edge of code_dependencies and is the safest first blast-radius query for Go changes.

code_impact

Read-only. Experimental behind code_map.

Returns a conservative, proximity-ranked impact summary: files the queried file imports, files that import the queried file, transitive dependents up to depth, likely tests, likely docs/config, and import cycles involving the target. depth defaults to all transitive dependents; pass a positive integer to cap traversal. Pass format: "summary" for a compact, counts-plus-top-names projection sized for planning-turn context instead of the full sectioned report. Pass include_calls: true to best-effort supplement the report with live LSP call-hierarchy callers/callees for the target file’s exported symbols — this only works when a language server is available for that file’s language, is slower than the static graph query (it makes live LSP round-trips), and is never persisted into the cached index.

code_cycles

Read-only. Experimental behind code_map.

Returns detected import cycles, optionally narrowed to cycles involving path. Cycles are only reported from resolvable in-workspace import edges (Go, TypeScript/JavaScript, Python, Rust — see docs/code-map.md).

code_map_diagram

Read-only unless to_file is set. Experimental behind code_map.

Returns a bounded Mermaid graph TD diagram from the import graph. Pass path to focus the diagram around one file’s direct incoming/outgoing import edges; omit it for a bounded workspace diagram. This is intended for copy-pasting into issues, docs, or PR descriptions. Set to_file to write the full, untruncated diagram to disk instead — a write like any other (approval-gated, checkpoint-tracked).

pr_readiness_context

Gather a cheap local PR-readiness snapshot: branch, dirty state, changed files, and whether docs/tests were touched. It is read-only and does not contact GitHub.

No parameters. No approval.

fetch_url

Fetch a single HTTP or HTTPS URL and return capped textual content. This is the local-network fallback for models that do not have provider-native hosted web search.

ParamTypeDefaultNotes
urlstring—Required; must start with http:// or https://
max_bytesint65536Hard cap is 262144; larger values clamp to the default

The tool only returns textual content types such as HTML, plain text, JSON, XML, and JavaScript responses. Binary content types are rejected.

run_bash

Run a shell command via /bin/sh -c in the session’s cwd.

ParamTypeDefaultNotes
commandstring—Passed verbatim to /bin/sh -c
timeout_msinteger180000Deadline for a foreground command; zero or negative selects the default. Above 600000 (10 min) the command is promoted to a background job (see below)
backgroundbooleanfalseStart a background job and return a session_id at once. Only offered where background jobs exist (TUI, ACP)
notify_on_completebooleanfalseBackground only: re-prompt the model with the exit code and output tail when the job exits. Setting it on a foreground call is an error

Always prompts for approval. Output is exit=<code>\n--- stdout ---\n…\n--- stderr ---\n…, each stream capped at 256 KiB; truncation is announced in the result.

Foreground timeout

When the deadline passes, the whole process group is killed (so backgrounded children don’t survive) and the call returns a normal result — exit=124 (as GNU timeout), run_bash: timed out after <duration>…, plus whatever stdout/stderr was captured — so the model can retry with a narrower command or a larger timeout_ms. Pressing Esc/Ctrl+C is a cancel, not a timeout, and is reported as before. Commands that block indefinitely, such as grep -R reaching a named pipe with no writer, are bounded by this deadline; prefer the grep and glob tools for searching.

Background jobs

background: true runs the command as a job whose lifetime is the session, not the turn: Esc ends the turn but does not kill it. The call returns session_id, pid, and whether you will be notified. Jobs go through the same approval, hardline/privilege checks, cwd handling and sandbox (host or Podman) as a foreground command; approval is per start.

A timeout_ms above the 10 minute foreground cap is not refused or clamped: the command is promoted to a background job with notify_on_complete, and the result tells the model not to re-run it. Where background jobs are not available (oneshot, dispatch workers) it falls back to the clamped foreground timeout.

  • Completion. With notify_on_complete the TUI banners the exit and wakes the model with the exit code and the last 2000 characters of output. Without it the job is silent until polled. If the model already collected the outcome with process wait/poll/kill, no wake is sent.
  • Dock and stop. Each job is a task in the subagent registry (type shell): it appears in the dock and /subagents, and /subagents stop kills it. Jobs do not count against the subagent concurrency caps.
  • Limits. 64 tracked jobs (the oldest finished job is evicted; a start is refused when all 64 are running). Finished jobs stay pollable for 30 minutes. Output is a rolling 256 KiB buffer per job, stdout and stderr interleaved; older bytes are dropped and reported.
  • Lifetime. Jobs are killed when the session closes and are not resumed with a saved session. Jobs belong to the parent session: subagents (foreground, background or dispatch workers) get a foreground-only run_bash and no process tool.
  • Kill. Kill reuses the sandbox cancel path: SIGKILL to the process group on the host, the marker-based in-container kill under Podman. Podman’s documented double-fork limitation applies (see sandbox.md).

After approval, host run_bash detects when inherited HOME, TMPDIR, or XDG cache/config/data/state variables point inside the current checkout. In that polluted-env case it redirects those variables to yottacode-owned scratch paths under ~/.yottacode/host-shell/<workspace>/ before running the command. This keeps tools that follow freedesktop defaults from leaking repo-root .local/, .cache/, or .config/ directories. If a command intentionally needs the real home directory, pass the required path explicitly instead of relying on inherited HOME.

run_bash refuses command segments that invoke sudo, doas, or pkexec before starting the shell. Those privilege frontends can hand the terminal an OS-owned password prompt that yottacode cannot render as an approval card, cancel reliably, or audit, so the safe path is to run the elevated command yourself in a separate terminal and then ask yottacode to continue.

There is no in-process sandbox, and there will not be one — yottacode keeps its core small and does not ship bwrap/firejail/landlock backends. By default the command runs directly on the host. Two ways to add real isolation: run yottacode itself inside a container or devcontainer (covers every tool, all-or-nothing), or enable the command sandbox ([sandbox] backend = "podman") to route supported command execution through lazy rootless Podman profile containers instead — see sandbox.md.

process

Manage background shell jobs started with run_bash(background=true). Only registered where background jobs exist (interactive TUI and ACP). It acts only on jobs this session started, so it never prompts.

ParamTypeDefaultNotes
actionstring—poll, wait, log or kill
session_idstring—From the run_bash start result; an unambiguous prefix of at least 4 characters works
timeout_msinteger60000wait only; clamped to 300000. Re-issue wait to keep waiting
linesinteger200log only; clamped to 2000
  • poll returns status plus the output produced since the previous poll/wait, and never blocks.
  • wait blocks until the job exits, the timeout elapses, or the turn is cancelled, then reports like poll. A caller blocked in wait receives the completion itself, so notify_on_complete does not fire a second time.
  • log returns the last N lines of retained output without moving the poll cursor.
  • kill stops the job and reports its final state.

An unknown or expired session_id is a normal tool result, not an error.

git

Unified git tool. Args are normally passed as a JSON string array and run without a shell, so there’s nothing to escape and nothing to inject. For model-authored calls that accidentally provide one string (for example "status --short"), yottacode conservatively splits it into argv, rejects shell control operators, and applies the same approval policy to the parsed arguments.

ParamTypeDefaultNotes
args[]string or string—e.g. ["status"], ["log", "--oneline", "-n", "5"], or the tolerated string form "status --short"

Approval policy is allowlist-shaped and flag-aware, in three tiers:

  • Auto-execute — unconditionally read-only subcommands: status, diff, log, show, blame, grep, ls-files, rev-parse, merge-base, describe, etc. (One guard: --output[=<file>] turns these into a file write, so it always prompts.)
  • Auto-execute — read spellings of ambiguous subcommands: listings like bare branch / branch --list / branch --show-current, bare tag / tag -l, bare remote / remote -v / remote get-url, stash list / stash show, and bare reflog / reflog show. The mutating spellings of the same subcommands (branch -d, tag v1.0, remote add, bare stash — which pushes! — reflog expire) prompt. Unknown flags fall through to a prompt; the policy never guesses.
  • Prompt for approval for everything else (commit, push, pull, checkout, reset, rebase, merge, …). Global pre-subcommand flags (-c, -C, --git-dir, …) always prompt — they can reconfigure git underneath the subcommand check.

Approval previews carry risk-tiered warning copy. History-rewriting or working-tree-destructive invocations get a specific ⚠ HIGH RISK: <what it destroys> line — reset --hard (“discards every uncommitted change”), clean -f, rebase, checkout -- <paths>, restore (worktree), branch -D, tag -d, push --force[-with-lease], reflog expire — so they can’t be approved with the same reflex as an add. Dangerous flags outside the classified set keep the generic ⚠ DESTRUCTIVE FLAG(S): line.

Stdout is capped at 1 MiB; stderr at 64 KiB.

todo_write

Maintain the agent’s working task plan. The list is owned by the session and rendered as a live card in the TUI’s in-flight area — updates in place on every todo_write call, so the user sees status flips without each call stacking a new card. At turn end one final-state snapshot lands in scrollback as the historical receipt for that turn.

ParamTypeDefaultNotes
todos[]object—Complete plan; previous list is replaced wholesale

Each item has content (short human-readable description) and status (pending / in_progress / completed / skipped, at most one in_progress). Use skipped when the agent changes course and is intentionally not doing a previously planned step; the TUI renders it as a dim ✗ row instead of leaving stale pending work visible. No filesystem or network side effects — purely a visibility primitive, so it never prompts for approval.

The model is instructed to call todo_write proactively for any task with three or more distinct steps and to update it as soon as each step finishes. When work has both a design/research phase and an execution phase, the model should ask for user agreement before implementation only when the user explicitly requested a plan first, the session is in plan mode, or the next step is risky/destructive/ambiguous. Routine todo-card creation is not a permission gate; the approval policy still lives on the mutating tools themselves. Pass an empty list to clear the plan; the live card disappears and no end-of-turn snapshot is emitted.

enter_plan_mode

The model’s request to switch the session into plan mode — the surface behind “make a plan first” / “drop into plan mode” asked in natural language. Mirrors Claude Code’s EnterPlanMode. Only advertised while plan mode is OFF (the schema filter is the inverse of exit_plan_mode’s), and registered in the TUI build only — oneshot has no approval surface to host the handshake.

ParamTypeDefaultNotes
(none)——The plan topic comes from the conversation; the plan file is derived from the turn’s user message

The call renders a two-hotkey confirmation card:

  • [Y] enter plan mode (also Enter) — runs the same entry sequence as /plan (exits auto mode if on, flips the shared state), then derives the plan file from the message that triggered the turn — so the agent can start writing the plan in the same turn instead of waiting for your next message.
  • [N] stay in current mode — declines; the model is steered to continue in the current mode and not re-request.

enter_plan_mode NEVER auto-approves — not in auto mode, not under --yolo, not via a permissions Allow rule. The approval round-trip is the handshake in which the TUI actually flips the mode state; skipping it would tell the model “entered plan mode” while the session never moved. The same guard applies to exit_plan_mode. There is deliberately no model-side way to enter auto mode or bypass: the agent can ask to restrict its own permissions, never to escalate them.

Subagents never see this tool (or exit_plan_mode) — children inherit the parent’s plan-mode state by pointer, and only the top-level loop may transition it.

exit_plan_mode

Only callable while /plan mode is active (the registry hides it from the adapter schema otherwise). Takes no arguments — the TUI reads the plan body from the resolved plan file on disk and renders it in the approval card. Matches Claude Code’s ExitPlanMode shape exactly.

ParamTypeDefaultNotes
(none)——The plan content comes from ~/.yottacode/plans/<slug>.md

If the plan file doesn’t exist or is empty when exit_plan_mode is called, the TUI auto-denies the call with a console notice — the model is expected to write the plan to the file first, then call this tool.

The TUI renders the plan inside an approval card with four hotkeys:

  • [A] auto-approval — exits plan mode AND turns on auto mode for the implementation, so mutating tools auto-allow without per-call prompts (safety floor still applies: run_bash, git_commit, git_checkpoint, rollback).
  • [M] manual approval — exits plan mode and the agent resumes execution, but per-tool prompts continue as normal so you can review each step.
  • [L] later — exits plan mode but ends the turn; the plan stays on disk for resume via /plan list or --plan-resume.
  • [K] keep planning — stays in plan mode; model receives refinement guidance.

--yolo does NOT skip this approval — that approval is the user-visible signal, not a safety gate.

While /plan is active, every other mutating tool is blocked except writes to the resolved plan file under ~/.yottacode/plans/<slug>.md. Read-only tools auto-allow as usual; writes to the plan file auto-allow too (no per-edit prompt — the plan file is the model’s only legitimate mutation surface during planning). See tui-slash-commands.md#plan-mode for the full plan-mode flow.

Agent

Dispatch a typed subagent. The subagent runs agent.Turn in its own message history, with a filtered tool registry and its own iteration budget. The parent’s adapter context never sees the subagent’s intermediate reasoning or tool calls — only the child’s final reply is returned as the tool result.

Mirrors Claude Code’s Agent / Task tool surface.

ParamTypeDefaultNotes
subagent_typestring (required)—The name of a registered agent definition (e.g. general-purpose, Explore, Plan, or a custom entry under .yottacode/agents/).
promptstring (required)—The task for the subagent. The subagent has no access to the parent conversation, so be self-contained.
descriptionstring""A 3-5 word label shown to the user while the subagent runs.
run_in_backgroundbooleanfalseIf true (TUI only), return immediately with a task id; the subagent runs to completion in the background. oneshot rejects this with a recoverable error so the model can retry without the flag.

A foreground subagent shares the parent’s current working directory and files — unlike dispatch, it gets no isolated worktree and no file-ownership partitioning. Agent is parallel-safe, so several Agent calls issued in one assistant message run concurrently; if two of them (or a subagent and the parent) edit the same file, a session-wide mutation lock rejects whichever call loses the race with a recoverable error instead of letting both read-modify-write cycles interleave. If you need guaranteed non-overlapping concurrent writes, use dispatch and declare disjoint files.

dispatch

Fan a batch of independent subtasks out to subagents that run concurrently. Write-capable subtasks each run in their own git worktree + branch and must declare disjoint files; read-only subtasks share the current working directory. Write batches default to background in the TUI and return a batch id plus worker branches immediately; all-read batches default to foreground and return the findings together.

ParamTypeDefaultNotes
goalstring—Overall objective the subtasks add up to.
tasks[]object—Two or more {subagent_type, description, prompt, files} entries. files is required for write-capable subagents and must not overlap.
backgroundboolsmart defaultDefaults true for write batches in the TUI, false for all-read batches; oneshot falls back to foreground.

No approval for orchestration. Experimental behind dispatch. Background workers auto-approve owned-file writes; run_tests, run_bash, git/GitHub mutations, and other approval-requiring tools are denied because there is no prompt surface. Pair with integrate to assemble committed worker branches. See dispatch.md.

integrate

Merge dispatch worker branches into one integration branch in a dedicated worktree. Experimental behind dispatch. It leaves the user’s current checkout untouched. On conflict it stops, reports the conflicted files and integration worktree path, and can resume after the merge is resolved and committed.

ParamTypeDefaultNotes
branches[]string—Worker branches to merge in order. May be empty only when finalizing an existing integration_branch after resolving the last conflict.
integration_branchstringgeneratedExisting or new integration branch. Reuse the same value to resume after conflicts.
basestringHEADBase ref for a newly created integration branch.

No approval. On clean success it reports the integration branch to push/open a PR from and reclaims merged worker worktrees/branches where safe.

browser_status

Experimental behind browser (see experimental.md). The browser_* tools drive a real, headless Chrome/Chromium instance over the Chrome DevTools Protocol via chromedp/cdproto — no Node.js, no Playwright. The manager is session-scoped: at most one browser process, launched lazily on the first call that needs it, sharing one isolated temp profile directory for the whole session. See browser.md for what to use this for and worked examples, and security-and-allow-lists.md for the full safety posture.

In /auto mode the browser tools are auto-approved except browser_upload, browser_download, and browser_navigate to any host other than localhost / 127.x.x.x / ::1, which stay in the auto-mode safety floor and always prompt (only /yolo skips that). A saved per-site rule such as Browser(navigate example.com) skips the navigate prompt for that site.

Every browser tool that prompts for approval can be remembered from the prompt with [S] (this session), [A] (saved) or [D] (block) — see security-and-allow-lists.md for the Browser(...) rules those create and what they deliberately don’t cover (browser_upload/browser_download, non-web URLs). Where a tool below says “Always prompts for approval”, read “prompts unless a rule you created allows it”.

browser_status reports the binary path found (if any), whether a session is active, its mode (headless, or headed after browser_handoff), its current URL, and its profile directory. It never launches a browser.

ParamTypeDefault
(none)

No approval.

browser_navigate

Navigate the single active page to a URL, waiting for the given lifecycle event before returning. Lazily launches the browser (discovering a system Chrome/Chromium binary and creating the isolated profile directory) on the first call in a session.

ParamTypeDefaultNotes
urlstring—Required. Only http://, https:// and about:blank are accepted
wait_untilstringloadload, domcontentloaded, or networkidle

Always prompts for approval. Any other scheme — file:, chrome:, view-source:, javascript:, data: — is refused before a browser is even launched: a file:// URL would let the browser read files the read tools deny (like .env) and hand their contents to the model. To look at a local file or built site, serve it from a local web server and open its http://localhost URL. browser_download’s url form has the same rule.

browser_screenshot

Capture a PNG screenshot of the current page, or of one element if selector is given. Returns an image block like read_file does for an image file — including the same degrade-to-text-label behavior when the active model doesn’t accept image input.

ParamTypeDefaultNotes
selectorstring—CSS selector to screenshot just that element
full_pagebooleanfalseCapture the full scrollable page, not just the viewport. Ignored when selector is set.

Always prompts for approval — a screenshot can surface on-screen private data even without clicking anything.

browser_inspect

Return an accessibility-tree text snapshot (indented role/name/value outline) of the current page, or of one element’s subtree if selector is given. The token-cheap way to “read” a page without a screenshot.

ParamTypeDefaultNotes
selectorstring—CSS selector to scope the snapshot to that element’s subtree

Always prompts for approval — same privacy rationale as browser_screenshot.

browser_click

Click the element matching a CSS selector, waiting for it to become interactable first.

ParamTypeDefault
selectorstring— (required)

Always prompts for approval.

browser_type

Clear and type text into the element matching a CSS selector, waiting for it to become interactable first.

ParamTypeDefaultNotes
selectorstring—Required
textstring—Required
submitbooleanfalsePress Enter after typing

Always prompts for approval.

browser_hotkey

Send a "+"-joined key spec to the page, e.g. "Enter" or "Control+a". The last token is pressed and released; every earlier token is held down as a modifier for its duration.

ParamTypeDefault
keysstring— (required)

Always prompts for approval.

browser_scroll

Scroll an element into view (if selector is given — direction/ delta_x/delta_y are ignored in that case), or scroll the page by a direction (a default ~400px magnitude) or an explicit pixel delta.

ParamTypeDefaultNotes
selectorstring—Scroll this element into view
directionstring—up, down, left, or right
delta_x / delta_ynumber—Explicit pixel delta; overrides direction

Always prompts for approval.

browser_wait

Wait for a CSS selector to become visible, for text to appear anywhere on the page, and/or for the network to go idle.

ParamTypeDefaultNotes
selectorstring—Wait for this selector to become visible
textstring—Wait for this text to appear on the page
network_idlebooleanfalseWait for the network to go idle
timeout_msintegerbuilt-inDeadline in milliseconds

No approval — it blocks on an already-visible condition and never launches the browser: a session that was never started has nothing to wait for yet.

browser_handoff

Hand the isolated browser to the user so they can complete a step only a person can do — typically a human-verification challenge (a “Press & Hold” box, a CAPTCHA) that the headless session can’t show them. It reopens the session as a visible window on the current URL, and the session stays visible for the rest of its life.

It does not solve, click through, or bypass anything. The user does the verification themselves; the tool result tells the agent to say what to do, wait for the user’s reply, then confirm the real page loaded with browser_inspect/browser_screenshot.

ParamTypeDefault
(none)

Always prompts for approval — it launches a new browser process and opens a window on the user’s display.

Behavior worth knowing:

  • Fresh isolated profile, not a copy. Chrome can’t switch from headless to headed in place, and carrying the headless session’s cookies across would also carry whatever “this client is automated” verdict the site already attached to them. Only the current URL carries over; anything else you did in the headless session (a login, a filled form) has to be redone.
  • A normal, resizable window. It starts at the same 1280×800 size as the headless session, but unlike headless (which has no real display to resize), this is an ordinary OS window: the page follows along when you resize or maximize it.
  • Launch-first, then swap. The visible browser is started before the headless one is torn down, so a failure leaves the existing session untouched.
  • Needs a display. On Linux that means DISPLAY or WAYLAND_DISPLAY is set; macOS always qualifies. On an SSH, container, or CI host the call fails with an error that tells the agent to ask you to open the page in your own browser and paste what it needs.
  • Stays headed. If you close the window or it crashes, the next action relaunches a normal headless session rather than popping a window you didn’t ask for; call browser_handoff again if needed.
  • Sites may still re-challenge. The window is a real Chrome under automation control, and a site’s bot detection can decide to challenge it again. Nothing here tries to evade that.

browser_close

Close the browser session and remove its isolated temp profile directory. Idempotent — safe to call more than once, and also runs automatically at session shutdown (CleanupRegistryTools) even if never called explicitly.

ParamTypeDefault
(none)

No approval — it only ever reduces capability.

browser_tabs

List every tab the session has seen — the main page plus any popup or target="_blank" tab opened since launch — with each one’s index, title, URL, and whether it’s the currently active tab. Indexes are for the current listing; closing a tab can shift later indexes, so list again before acting on a stale index. Never launches the browser.

ParamTypeDefault
(none)

No approval.

browser_switch_tab

Switch which tracked tab subsequent browser_* actions act on, by the index browser_tabs reports. The index refers to the current listing; closing another tab can shift it, so list again before switching.

ParamTypeDefault
indexinteger— (required)

No approval.

browser_close_tab

Close one tracked tab by the index browser_tabs reports, without closing the browser session — the narrower counterpart to browser_close. The index refers to the current listing; closing another tab can shift it, so list again before closing a stale index. Refuses to close the only remaining tab.

ParamTypeDefault
indexinteger— (required)

No approval — same reasoning as browser_close: it only ever reduces capability.

browser_upload

Set a <input type=file> element’s files from local paths, lazily launching the browser like every other mutating action.

ParamTypeDefault
selectorstring— (required)
pathsarray of string— (required)

Always prompts for approval, and the prompt lists the files being uploaded. Every path is resolved and validated the same way write_file validates its destination (inside the session workspace or an --allow-paths root, no symlink writes) and against the same credential read deny list read_file uses (.env, ~/.ssh, …) — uploading a file hands its bytes to whatever origin the active page is on, so a file the model can’t read it also can’t upload. One denied path refuses the whole batch.

browser_download

Trigger a download and save it to a local path. Exactly one of selector (click an element, e.g. a download link/button) or url (navigate directly to a downloadable resource) is required.

ParamTypeDefaultNotes
selectorstring—Click this element to trigger the download
urlstring—Navigate directly to this URL to trigger the download
pathstring—Required. Local destination path

Always prompts for approval. path goes through the identical write-path validation as browser_upload/write_file. Reports the site’s suggested filename and byte size alongside the path it actually saved to.

browser_console_logs

Return recently buffered console.* calls and uncaught JS exceptions from the active page, most recent last. Capture is continuous from the moment a page is tracked — not a one-shot read triggered by this call — so it catches messages logged at any point (page load, an async callback, a later click), not just ones produced during this call.

ParamTypeDefault
limitinteger50

Always prompts for approval — console output can carry private data (a logged token, PII) even though nothing was clicked to produce it, same rationale as browser_screenshot/browser_inspect.

browser_network_requests

Return recently buffered requests the active page has made — method, URL, status, and failures — most recent last. Metadata only: never response bodies, and no request blocking/mocking (out of scope for v1).

ParamTypeDefault
limitinteger50

Always prompts for approval — request URLs can carry private data (a session token in a query param) and reveal what backend APIs a page talks to, same rationale as browser_console_logs.


v1 scope for the whole browser_* family: headless by default (a visible window only when the user is handed the session via browser_handoff), a fresh isolated profile per session (never your real, logged-in Chrome — no cookies, history, or saved logins), and no dispatch worker access. The session tracks every tab it has opened, but only ever acts on one active tab at a time — the tools don’t implement ParallelSafeTool, so calls always serialize through the same approval queue as everything else regardless of how many tabs are tracked. Tab indexes are point-in-time listing indexes and can shift when a tab closes; list again before using a stale index. browser_click and browser_type (with submit: true) auto-follow a tab opened as a direct result of that action (a

target="_blank" link, window.open()) within a fixed ~300ms detection window — the tool’s result message reports the resulting current url and tabs count either way, and browser_tabs/ browser_switch_tab/browser_close_tab cover every other case (an already-open tab, switching back, more than one new tab, cleaning up a popup once you’re done with it). With --experimental browser unset, every browser_* tool call returns an explanatory message and takes no action. With it set but no system Chrome/Chromium found, the first call fails with an actionable error naming the paths searched — the same “detect, don’t download” posture read_document uses for pdftotext/pandoc.

A JS-initiated dialog (alert/confirm/prompt/beforeunload) is auto-dismissed rather than left to block the page — there’s no human to click it, and dismissing (not accepting) is the safer default since confirm() can gate its own destructive action beyond what the triggering click’s approval covered.

Every action is bounded by a 60s default timeout unless the caller’s own context is already tighter (browser_wait’s timeout_ms overrides it directly). A single browser process serves the whole session, so without this bound an unreachable site, a load event that never fires, or a Chrome binary that starts but never opens its debug port wouldn’t just fail that one call — it would wedge every later browser_* call, including browser_close, until the process itself was killed. Hitting the timeout returns a recoverable error instead.

If the browser process itself dies (crashes, gets OOM-killed, or is killed externally) the next action detects this — a cheap process liveness check, not a CDP round-trip — and transparently relaunches a fresh session rather than failing forever with an opaque dead-connection error. browser_status reports the crashed session as inactive without recovering it itself (it never changes state); browser_wait reports an error rather than silently claiming its condition was satisfied when nothing was actually checked.

issue_read

Read-only fetch of a single GitHub issue: number, title, body, state, author, URL, labels, assignees, and the most-recent comments (capped).

Prefer this over run_bash gh issue view <n> --json ... whenever the goal is reading issue context — no subprocess, structured result, and state flags the caller can branch on.

Returns labeled sections ## state, ## issue, and ## comments. The ## state block flags not_found and github_unavailable so the caller can surface a clean error and stop.

ParamTypeDefault
numberinteger— (required)
max_commentsinteger0

max_comments caps the comment fetch: 0 uses the default of 20, -1 skips comments entirely, and any positive value caps at that many.

No approval. Parallel-safe.

issue_list

Lists open issues for the current repo, with optional label, assignee, and milestone filters (AND-ed together). Returns lightweight summaries — number, title, author, URL, labels, assignees. Bodies and comments are dropped; follow up with issue_read for the full content of any one issue.

Prefer this over run_bash gh issue list --json ... when enumerating open issues: the filter fields map directly to the GitHub API. Returns labeled sections ## state and ## issues.

Returns the first page only (GitHub’s default, roughly 30 issues). Refine the filters if you need a narrower set.

ParamTypeDefault
labelsstring[](no label filter)
assigneestring(no assignee filter)
milestonestring(no milestone filter)

No approval. Parallel-safe.

issue_context

Composite read-only snapshot used to open an issue without parsing bash output. Returns labeled sections under ## state, ## template, ## templates, ## blank_issue, and ## contact_links. The ## state block carries deterministic fields: owner=, repo=, gh_available= (whether the GitHub auth token chain resolves — the /git-create-issue directive branches to draft-only when false).

## template preserves the legacy single-template view (path=, optional choices=, and content=). ## templates is the richer chooser view: each entry has an index, name, kind (markdown or issue_form), path, optional description/title prefix/labels/assignees, and rendered Markdown content. Discovery order matches GitHub’s own precedence: directory-style .github/ISSUE_TEMPLATE/*.{md,yml,yaml} first in filename order, then the legacy single-file locations (ISSUE_TEMPLATE.md in .github/, the repo root, or docs/, either casing). Markdown frontmatter is stripped. YAML issue forms are parsed and rendered into Markdown sections because GitHub’s public create-issue API accepts a normal issue body, not a submitted form payload.

config.yml is not treated as a template. Its blank_issues_enabled flag is reported under ## blank_issue, and contact_links are reported under ## contact_links so documentation, discussions, and security-report links can be surfaced without creating public issues for them.

Pair with issue_create — context tool gathers state, create tool validates the title and opens the issue.

ParamTypeDefault
(none)

No approval. Parallel-safe.

issue_create

Composite mutator that validates an issue title and opens the issue through the typed internal/github.Interface adapter. The following rejections fire before dialing the adapter (deterministic Go, not model judgment):

  • empty title (created=false reason=validation)
  • multi-line title
  • title longer than 72 characters
  • title ending in a period

Returns a typed envelope. On success: created=true url=<url> number=<n>. On a missing or unauthenticated gh CLI: created=false reason=github_unavailable so the procedural /git-create-issue can fall through to draft-only output without surfacing an opaque exec failure. On other gh errors: created=false reason=github_error followed by the gh output verbatim. The tool never auto-retries, auto-edits, or auto-assigns labels beyond what the user explicitly provided or the selected issue template declares.

The adapter behind this tool is internal/github.TypedClient, backed by the go-github/v66 REST client. Auth resolves through a three-tier precedence chain: $GITHUB_TOKEN env var → gh auth token shell-out (one-shot, cached for the session) → ~/.yottacode/github.json (yottacode-native PAT, written by a future yottacode setup github flow). The gh CLI is no longer required for API calls — only optionally used to source the token when $GITHUB_TOKEN isn’t set.

ParamTypeDefault
titlestring—
bodystring—
labels[]string—
assignees[]string—

Always prompts for approval.

The full documentation — file format for custom agents, the /subagents command, transcript layout, and the recursion + iteration safeguards — is in subagents.md.