Skip to main content
Glama

Hirð

The king's hirð wading a river, drawn by Erik Werenskiold

A hirð is a Norse king's household guard: sworn retainers, each with a named duty, answerable to one lord. Illustration by Erik Werenskiold for Magnús Erlingsson's saga in Snorri's Heimskringla (public domain, via Wikimedia Commons).

A typed language for long-running agent systems on BEAM: effect-row tracking, auditable tool effects, typed actors, and OTP supervision. Python agent frameworks hide side effects in coroutine soup; Hirð makes every tool call, every actor message, and every supervisor boundary visible in the types and queryable by tooling.

What that buys: deterministic replay of real agent traffic. Every tool call is recorded unconditionally in a canonical wire format, so a recorded run is a file you can replay — the same calls, in the same order, each served the result the recorded run got back, with no service contacted. That is a regression test with no oracle to maintain, a bug report that reproduces, and a fixed environment to evaluate a change in. It is also not something you can retrofit onto a framework that hides its side effects: it needs the effects in the types and a single dispatch path underneath them. hird demo is that claim in one command: it records a run of the demo planner, replays that one recording against three variants of the program, and prints where each parted from it.

And systems that stand. A Hirð program is not a script that exits: fn main can start a supervision tree and stand, leaving typed actors serving after its own work is done — driving their own periodic rounds off a clock capability, crashing and restarting under a declared budget, every round on the audit stream. hird run demo/agent_fleet is that claim running: a hirð of three retainers that keeps working through a deliberate crash.

Status: pre-1.0 and experimental. The v0.1 compiler pipeline works end to end (the demos below type-check, compile to Erlang, and run on BEAM), but the language surface is unstable, nothing is published to crates.io, and breaking changes land without deprecation cycles. The roadmap lives in the in-repo issue tracker (see .beads/README.md).

Install

Prebuilt binaries for Linux, macOS, and Windows are attached to every release: extract the archive for your platform and put hird (the compiler), hird-lsp, and hird-mcp on your PATH.

From source, with Rust 1.97 or newer:

cargo install --git https://github.com/no-materials/hird hird-cli
cargo install --git https://github.com/no-materials/hird hird-lsp  # optional
cargo install --git https://github.com/no-materials/hird hird-mcp  # optional

With Nix, the same three binaries are flake outputs (nix run github:no-materials/hird#hird-mcp).

Compiling and running programs needs Erlang/OTP on PATH (apt install erlang, brew install erlang, …); hird check works without it.

Related MCP server: fatta

Quick start

Hirð has no ambient print. Anything a program tells the outside world goes through a tool — a declared, typed, audited external operation — so the smallest observable program is a tool call. Save this as hello.hird:

module Hello

tool Say : { message: String } → ()

fn quiet_say(args: { message: String }) → () = ()

fn main() → () ! {} =
  handle {
    Tool<Say> → quiet_say,
  } in say({ message: "hello, world" })
hird run hello.hird
{"schema_version":1,"tool":"Say","args":{"message":"hello, world"},"result":{"ok":null},"timestamp":"…","caller":"Hello.main"}

Three things happened. Declaring tool Say created the effect Tool<Say> and a callable say. The handle block supplied an implementation and discharged that effect, so main is honestly ! {}. And the call was recorded on the audit stream — unconditionally, because mocked and real tool calls audit identically. ASCII operator spellings (->) normalise to their Unicode forms () at lex time, so either is legal input.

Command

What it does

hird check <file-or-dir>

type- and effect-check; coded diagnostics

hird build <file-or-dir>

emit readable Erlang, compile it to .beam

hird run <file-or-dir>

build, then execute fn main on BEAM

hird demo

record one run of the built-in demo, replay it against variants of the program

hird emit-ast <file> --json

the typed IR of every definition

hird emit-effect-graph <file-or-dir> --json

actors, mailboxes, handler rows, supervisors, tools, function rows

hird effect-diff --exact <baseline.json> <file-or-dir>

fail when the effect graph drifts from a committed baseline

docs/writing-hird-human.md is the guided tour, and phrasebook.md the dense syntax reference.

The flagship demo: a standing hirð of agents

A hirð is retainers with named duties; demo/agent_fleet/ is the metaphor made literal. Three supervised actors serve for as long as the program stands: a Planner ticks itself on a clock and forges each round's order (pure planning imported from a second module — the source spans a real use boundary), an Executor carries the order out through Tool<RunErrand> and reports onward, an Auditor chronicles every outcome through Tool<Chronicle>. Round 3 crashes the executor on purpose: FleetSup restarts rest_for_one, so the auditor — downstream of the crash — restarts with it, the planner keeps its round counter, and the rounds keep coming. Actor state dies with its process; the audit stream is the durable record.

hird run demo/agent_fleet
{"schema_version":1,"tool":"RunErrand","args":{"errand":"mend the palisade","round":2},"result":{"ok":"done"},"timestamp":"…","caller":"Executor.handle_msg/Carry"}
{"schema_version":1,"tool":"Chronicle","args":{"note":"done","round":2},"result":{"ok":null},"timestamp":"…","caller":"Auditor.handle_msg/Record"}
{"schema_version":1,"tool":"Log","args":{"level":"info","message":"executor takes its post"},"result":{"ok":null},"timestamp":"…","caller":"Executor.init"}
{"schema_version":1,"tool":"Log","args":{"level":"info","message":"auditor takes its post"},"result":{"ok":null},"timestamp":"…","caller":"Auditor.init"}
{"schema_version":1,"tool":"RunErrand","args":{"errand":"scout the border","round":4},"result":{"ok":"done"},"timestamp":"…","caller":"Executor.handle_msg/Carry"}

Round 3 never beats — the crash consumed its order — and the two re-posted inits are the supervisor's work, visible in the same stream as everything else. The tree itself is queryable; its effect graph is the system's live org chart, every retainer with its duty and its effects:

hird emit-effect-graph demo/agent_fleet

Record and replay a run

demo/agent_planner.hird drives one planning round against a supervised Planner: repository state in through Tool<ReadRepo>, pure analysis, tickets out through Tool<CreateTicket>, progress through Tool<Log>. Every tool invocation — mocked or real — lands on the audit stream, one canonical JSON line per call:

{"schema_version":1,"tool":"CreateTicket","args":{"body":"The parser has no fuzz harness.","title":"Fuzz the parser"},"result":{"ok":{"ctor":"TicketId","args":["Fuzz the parser"]}},"timestamp":"2026-07-28T06:44:42.893Z","caller":"AgentPlanner.file_tickets"}

Because the stream is complete — every call, full arguments, tagged result — a recorded run is a replayable environment:

hird run demo/agent_planner.hird --audit-file run.jsonl   # record
hird run demo/agent_planner.hird --replay run.jsonl       # replay

The replay cursor outranks every handle and install block, so no tool runs and no service is contacted; each call receives its logged result, failures included. Matching is strict: the call at each position must be the one the log recorded there, or the run crashes with a replay_divergence naming the position, the recorded call and the offered one — and a log the run did not read to the end fails too.

So a checked-in recording is a regression test with no oracle to maintain: demo/agent_planner.golden.jsonl is one run of the planner, replayed by the demo suite in CI, and the build fails the moment the program's decisions drift from it. And because the log serves every result, one recording is a fixed environment to compare variants of a program in — every arm meets a byte-identical world, so what differs is attributable to the programs:

baseline        agreed with all 7 calls
announce-first  parted at call 2 (tool_mismatch)
eager           parted at call 4 (args_mismatch)

That evaluation is hird demo: no arguments, nothing to install beyond Erlang, and nothing checked in that it has to be trusted about — it writes the planner and the two edited variants into _build/hird-demo, records the episode itself, and replays it against all three.

docs/audit-evidence.md states what the stream guarantees and what it does not; docs/tool-effects.md is the normative format and replay specification.

LLM tooling (MCP)

hird-mcp on Glama

hird-mcp is a Model Context Protocol server over the same compiler pipeline, speaking stdio. It gives LLM agents structured compiler queries instead of source-reading guesswork: check_file (every diagnostic of a program, warnings included), list_definitions (a module outline with per-symbol token costs), infer_type, lookup_definition, explain_effect_row, render_ir_fragment, explain_actor_protocol, emit_actor_effect_graph, get_context_for_symbol (token-budget-aware symbol summaries), and get_context_budget. Errors come back structured — undefined names list the available ones, parse and type errors carry coded diagnostics — so agents can self-correct from tool output alone. The server is self-describing: it serves the agent-facing writing guide, the parser code index, and the phrasebook as MCP resources, and one prompt (author_supervised_module) that scripts the write-then-verify loop.

The repository ships a project-scoped .mcp.json, so Claude Code sessions started here pick the server up automatically (it launches nix run .#hird-mcp; run nix build .#hird-mcp once so the first session start doesn't wait on a cold build). Any other MCP client can launch the hird-mcp binary directly, with no arguments.

Things worth asking an agent wired to it:

  • "What does the Planner actor in demo/agent_planner.hird do? Ask the compiler instead of reading the source."

  • "If the Executor in demo/agent_fleet crashes mid-round, who restarts it, who restarts with it, and what's the restart budget?"

  • "Give me a 50-token summary of the Planner actor. Now 400 tokens. What got dropped?"

  • "Write a new Hirð module with a supervised actor, and iterate with the hird tools until they confirm it's clean."

demo/counter_demo.hird is that last prompt's output: a supervised counter written by an LLM agent that verified itself against the MCP tools alone — it type-checks and runs on BEAM unmodified. And demo/heartbeat.hird is the smallest standing program: one actor, one clock, one beat a second until Ctrl-C. docs/writing-hird-llm.md is the agent-facing guide, and docs/context-packing.md shows the third prompt answered for real: the Planner at 50 and 400 tokens, and what each budget dropped.

Editor support

hird-lsp is a Language Server Protocol server over the compiler front end, speaking stdio: diagnostics on open and save, hover with inferred types and effect rows, go-to-definition for top-level declarations. Point any LSP client at the binary, with no arguments. tree-sitter-hird/ is a tree-sitter grammar for the v0.1 surface, with highlight, indent, and fold queries, built by the flake as a package output. docs/editor-setup.md has client configuration (including Neovim, with and without nix), the grammar development loop, and the v0.1 limitations.

Repository layout

  • crates/ — the Rust compiler workspace (lexer, parser, checker, IR, codegen, CLI, LSP and MCP servers).

  • tree-sitter-hird/ — the tree-sitter grammar and editor queries.

  • runtime/ — the hand-written Erlang runtime support library (tool dispatch, audit sink, handler registry).

  • demo/ — the v0.1 demo programs.

  • conformance/ — golden files for the audit-log wire format.

  • docs/ — normative specifications (grammar, error model, tool effects wire format, effect graph), the audit stream's guarantees, and editor setup.

  • phrasebook.md — dense surface-syntax reference.

  • DECISIONS.md — architecture decision records.

  • .beads/README.md — the issue tracker and roadmap, driven by bd.

Development

MSRV is Rust 1.97 (edition 2024). Before sending changes:

cargo fmt --all
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features

BEAM-dependent tests skip themselves when erlc is not on PATH.

CONTRIBUTING.md has the rest: the dev shell, the checks CI runs beyond those three, what "done" means, and how to report a bug or file an issue from outside the repository.

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Available Tools

10 tools
check_fileCheck programA
Read-onlyIdempotent

Check a .hird file's whole program and return every diagnostic, warnings included. Use it as the first and last step of an edit: nothing else reports a warning, and the other tools fail on the first error instead of listing them all. Returns ok (no diagnostic of severity Error anywhere in the program) and diagnostics across every file of the directory, each with file, a stable code (P… parse, C… check, both documented in docs/writing-hird-llm.md), severity, message, nullable help, a 1-based character position (line/column to an exclusive end_line/end_column), and related locations in the same file. Parse and type errors are results here, never isError. Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYes
fileYes
diagnosticsYes

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond annotations (readOnlyHint, idempotentHint, destructiveHint), the description discloses key behaviors: in-memory compilation with caching, that parse/type errors are returned as results rather than isError, stable error codes for failures, and that it writes/executes nothing. This is far richer than the annotation hints alone.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence earns its place—purpose, usage, return shape, and error handling are densely packed without fluff. It is front-loaded with the core purpose and usage, making it easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

It covers purpose, usage, behavioral details, return shape (ok, diagnostics with fields), and error handling (stable codes, isError behavior). Given the output schema exists, the description fully equips an agent to call the tool correctly and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with a clear description, but the description adds meaning: the file is compiled with every sibling in its directory, so imported names resolve, and answers may name sibling files. This contextual detail helps the agent understand the parameter's role beyond a simple path.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('check') and resource ('.hird file's whole program'), and explicitly distinguishes itself from siblings: 'nothing else reports a warning, and the other tools fail on the first error instead of listing them all.' This makes its purpose unmistakable and differentiates it clearly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly prescribes when to use it: 'Use it as the first and last step of an edit' and explains why (other tools fail early, nothing else reports warnings). It also implies exclusions by noting other tools' behavior, giving clear context for choosing this tool over alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

emit_actor_effect_graphEmit actor effect graphA
Read-onlyIdempotent

Emit the actor/effect graph rooted at one actor: every actor reachable through Send, Await, Spawn, and Schedule effects (matched by message type), every supervisor of an included actor together with its whole child set, and every tool an included actor's effect summary names. Use it for 'what does this actor transitively do or depend on'; effect rows are per-process, so no single signature shows this. Use explain_actor_protocol for one actor's own interface and get_context_budget to gauge the size before requesting it. The reach spans the whole program: a row's type names resolve through the naming module's imports, so a sibling module's actors, supervisors, and tools are included and same-named declarations in two modules stay distinct. Returns schema_version (1), module (the root's), root, and the included actors, supervisors, and tools, whose nodes share the shape of hird emit-effect-graph --json plus a module tag; declarations the root does not reach are omitted. Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.
actor_nameYesThe root actor's name, as the file would write it (`Planner`, `Fleet.Planner`): a local actor, or a sibling module's as `Qualifier.name` through `use Mod` (or `Module.name` for any module of the program). An unknown name fails with `not_found` and `error.data.available_actors`.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rootYes
toolsYes
actorsYes
moduleYes
supervisorsYes
schema_versionYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnly/idempotent/destructive annotations, the description discloses in-memory compilation with caching, no writes or execution, whole-program reach, module-qualified name resolution, and stable error.code values with diagnostic shapes. Nothing is hidden and nothing contradicts the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The definition is front-loaded with the action and scope, and each block (use case, alternatives, reach, output shape, safety, errors) earns its place. It is longer than typical descriptions, but justified by the tool's cross-module semantics and error-handling complexity; a slight trim of the output-shape and error enumeration would improve it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex graph tool with only two parameters, the description covers invocation trigger, output shape, cross-module behavior, read-only guarantees, and error modes. An agent has enough context to decide when to call it, invoke it correctly, and interpret failures.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already covers both parameters at 100%, including path resolution and actor qualification rules, so the baseline is 3. The description reinforces the meaning of the root/module and failure behavior but does not materially add new parameter-level semantics beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence names a specific verb/resource ('Emit the actor/effect graph rooted at one actor') and specifies exactly what nodes are included: reachable actors, supervisors with children, and tools. It also distinguishes itself from explain_actor_protocol and get_context_budget by naming alternatives, so an agent can tell the tools apart.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit trigger: use it for 'what does this actor transitively do or depend on' and explains why (per-process effect rows hide transitive reach). It also tells the agent to use explain_actor_protocol for a single actor's interface and get_context_budget to gauge size before requesting it. This is clear routing among siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

explain_actor_protocolExplain actor protocolA
Read-onlyIdempotent

Describe one actor's protocol: its message type and constructors, state type, init parameters and effects, per-handler effect rows, and the declared effect summary. Use it to learn how to talk to an actor or what each handler may do; use emit_actor_effect_graph for the actors, supervisors, and tools it reaches transitively, and lookup_definition if you only need its location. Returns actor with name, line, state, message (name, constructors), init (params, effects), handlers (message, effects), and effects; types and rows carry a display string. actor_name resolves like any symbol: a local actor, a selectively imported one, or Qualifier.name through a use; module and file name the defining module. Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.
actor_nameYesThe actor's name, as the file would write it (`Planner`, `Fleet.Planner`): a local actor, or a sibling module's as `Qualifier.name` through `use Mod` (or `Module.name` for any module of the program). An unknown name fails with `not_found` and `error.data.available_actors`.

Output Schema

ParametersJSON Schema
NameRequiredDescription
fileYes
actorYes
moduleYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes beyond annotations (which only state read-only, idempotent, non-destructive) to disclose that the tool compiles the file's directory in memory with caching, writes/executes nothing, and returns failures with stable error codes. It also details the error code taxonomy and the shape of diagnostics. This is rich additional behavioral context. It does not contradict annotations, and it adds value by specifying the compilation behavior and error handling.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is substantial but well-structured: it starts with the primary purpose and immediate alternatives, then provides return structure, parameter resolution, and error behavior. It earns its length by packing essential information. It is not excessively verbose; each sentence contributes to understanding. However, it could be slightly more front-loaded by placing the alternatives earlier, but the current order is logical.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (rich output structure, error handling, parameter resolution rules), the description covers everything an agent needs: return shape, resolution semantics, error codes, and side-effect-free behavior. The output schema exists, but the description still explains the meaning of the fields and the error data, making it complete for correct invocation and interpretation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds meaning by explaining how `actor_name` resolves (local, imported, qualified) and the effect of `file` (compiling the whole directory). It also details the return structure including the `display` string for types and rows, and failure conditions. This goes beyond a simple restatement of parameter definitions, but since the schema already describes each parameter thoroughly, the additional value is moderate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: to describe an actor's protocol, enumerating the specific components (message type, state type, init effects, per-handler effects, effect summary). It uses a specific verb ('Describe') and resource ('actor's protocol'), and distinguishes itself from siblings like 'emit_actor_effect_graph' and 'lookup_definition' by naming them and stating their alternative purposes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says when to use this tool: 'Use it to learn how to talk to an actor or what each handler may do'. It also gives clear alternatives: 'use `emit_actor_effect_graph` for transitive actors/supervisors/tools' and 'use `lookup_definition` if you only need its location'. This provides both when-to-use and when-not-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

explain_effect_rowExplain effect rowA
Read-onlyIdempotent

Explain a function's effect row: the canonical row plus a one-sentence explanation of each effect. Use it for 'what may this function do' or to interpret an unfamiliar effect. It covers one named function (a tool name explains the tool's generated function, Tool<Name> included); use infer_type for an arbitrary expression's row and emit_actor_effect_graph for what an actor transitively does. Returns name (the function explained), type, effect_row, open (the row ends in a row variable, so it may carry more effects than listed), pure (closed and empty), and effects, each with effect and explanation. A name that is not a function (a type, an actor, …) is not_a_function; an unknown name is not_found with the available names in error.data.available. Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.
fn_nameYesThe function's name, as the file's source would write it: a local definition, a member imported with `use Mod.{name}`, or `Qualifier.name` through `use Mod`.

Output Schema

ParametersJSON Schema
NameRequiredDescription
fileYes
nameYes
openYes
pureYes
typeYes
effectsYes
effect_rowYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=true, destructiveHint=false, idempotentHint=true. The description complements these with specific context: it compiles the file's directory in memory with a cache until a sibling changes, and writes or executes nothing. It also details failure modes, including stable error.codes and the consistent shape of error.data.diagnostics. This goes beyond the annotations and gives the agent an accurate model of what the tool does under the hood.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense and information-rich, but it is also lengthy and slightly run-on. It front-loads the core purpose and usage, then flows into behavioral details and error codes. Although every sentence adds value, the structure could be improved with clearer separation (e.g., bullets) to reduce cognitive load. It remains within acceptable bounds for a complex tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is exhaustive for its complexity. It covers the return value structure (name, type, effect_row, open, pure, effects), the edge cases (not_a_function, not_found), and all possible error codes with their data shape. Combined with the rich output schema and the annotations covering resource safety, an agent has everything needed to call the tool correctly and interpret results. Nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters. Still, the description adds meaning: it clarifies that `file` can be relative to the server's working directory and that sibling files are compiled together, so names may resolve to other files. For `fn_name`, it specifies the syntax for imports and qualifiers, which is not in the schema. Despite high coverage, this added context justifies a score above baseline 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb–object pair ('Explain a function's effect row') and immediately defines what it covers: the canonical row plus a one-sentence explanation of each effect. It clearly identifies the target resource (a single named function) and differentiates itself from siblings by naming alternatives (infer_type, emit_actor_effect_graph). It also specifies edge cases (not_a_function, not_found), leaving no ambiguity about what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit guidance on when to use this tool: for 'what may this function do' or interpreting unfamiliar effects. It explicitly names alternatives and the conditions that select them: use infer_type for an arbitrary expression's row, and emit_actor_effect_graph for an actor's transitive effects. This actionable routing leaves no inference to the agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_context_budgetEstimate context budgetA
Read-onlyIdempotent

Estimate the token cost of loading a file's declarations into an LLM context window, per category: types, effects, actors, supervisors, tools, and function signatures. Use it before pulling a module in wholesale, to choose between get_context_for_symbol calls and a full read, or to pick a budget; it names no individual symbols. Returns approx_tokens with types, effects, actors, supervisors, tools, functions, and total, estimated at ~4 characters per token from one-line signatures, and a note restating that. Fails only when the file is unreadable or has parse or type errors. Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.

Output Schema

ParametersJSON Schema
NameRequiredDescription
fileYes
noteYes
moduleYes
approx_tokensYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well beyond the annotations. It discloses that the tool is read-only (compiles in memory, cached until a sibling changes, writes/executes nothing), which reinforces the readOnlyHint. It also discloses failure modes with stable error codes (file_not_found, read_error, invalid_params, parse_error, check_error) and the shape of diagnostics. It even explains the estimation heuristic (~4 characters per token from one-line signatures). This is rich behavioral context that annotations alone do not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but well-organized: purpose first, then usage, then return shape, then failure modes. Every sentence carries information. It is longer than the typical description, but the length is justified by the behavioral and error details. It loses one point because the failure-mode enumeration is somewhat verbose and could be tightened without losing meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is complete for a read-only estimation tool. It covers what the tool returns (approx_tokens with per-category breakdown and total), the estimation method, the failure modes with error codes, and the read-only safety profile. The output schema exists, so return values are further documented. An agent has everything needed to decide when to call it and how to interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already provides 100% coverage of the single parameter, including path resolution semantics and the fact that sibling files are compiled together. The description adds value by explaining why that matters ('so imported names resolve; answers may name a sibling file') and by clarifying the tool's scope ('it names no individual symbols'). Since the schema covers the parameter well, the description's additional context earns a 4 rather than a 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Estimate'), a specific resource ('token cost of loading a file's declarations'), and enumerates the exact categories counted (types, effects, actors, supervisors, tools, function signatures). It also explicitly distinguishes itself from siblings by noting it 'names no individual symbols' and by referencing get_context_for_symbol as the alternative for symbol-level queries. This is a clear, differentiated purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use guidance: 'Use it before pulling a module in wholesale, to choose between get_context_for_symbol calls and a full read, or to pick a budget.' It also names the alternative tool (get_context_for_symbol) and the decision it informs. This is exactly the kind of routing guidance an agent needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_context_for_symbolSummarize symbol for contextA
Read-onlyIdempotent

Summarize one symbol for an LLM prompt within an approximate token budget: signature, effect row, doc comment, callers, and callees, added in that order while they fit. Use it as the default way to bring a symbol into context; use lookup_definition for just the location, explain_effect_row for effect explanations, and render_ir_fragment for the full body. Returns kind, the budget applied, summary (prompt-ready text), approx_tokens (its estimated cost at ~4 characters per token), and omitted (the sections that did not fit). The signature is always present, truncated when the budget is smaller than it. An unknown name is not_found with the available names in error.data.available. Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.
nameYesThe symbol's name, as the file's source would write it: a local definition, a member imported with `use Mod.{name}`, or `Qualifier.name` through `use Mod`.
budgetNoApproximate token budget for the summary, at ~4 characters per token; optional, 400 when omitted. A section that does not fit is dropped whole and named in `omitted`; only the signature is truncated.

Output Schema

ParametersJSON Schema
NameRequiredDescription
fileYes
kindYes
budgetYes
symbolYes
omittedYes
summaryYes
approx_tokensYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false, and the description reinforces this by stating it writes/executes nothing and only compiles in memory (cached). It also discloses failure behavior, error codes, and the not_found shape, which goes beyond annotations and gives the agent actionable detail.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but front-loaded with the main purpose and usage; all sentences earn their place. It is somewhat long, but it packs a lot of necessary operational detail into a compact structure, so it earns a 4 rather than a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the rich annotations, and the presence of an output schema, the description is complete: it covers purpose, ordering semantics, omissions, failure codes, and read-only behavior. It also references sibling tools sufficiently. Nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so parameters are fully described in the schema. The description adds a bit of context about token estimation and omission behavior but does not need to compensate for missing schema docs. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a clear verb ('Summarize') and resource ('one symbol'), and specifies the target use ('for an LLM prompt within an approximate token budget'). It explicitly distinguishes itself from siblings like lookup_definition, explain_effect_row, and render_ir_fragment, and describes return fields. The title also reinforces the intent.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides direct guidance: 'Use it as the default way to bring a symbol into context' and explicitly lists alternatives for other needs (lookup_definition for location, explain_effect_row for effect explanations, render_ir_fragment for full body). This is ideal routing information.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

infer_typeInfer expression typeA
Read-onlyIdempotent

Infer the type and effect row of the expression at a source location (1-based line and character column) in a .hird file. Use it for 'what is the type here', including local bindings, sub-expressions, and names inside use lists; for a named top-level definition prefer lookup_definition, and for a function's effects with explanations prefer explain_effect_row. Returns token (the source token found at the location), type (the normalized type as Hirð prints it), and effect_row (the row of a function-typed expression, {} otherwise). A location outside the file is invalid_params; one with no typed expression is not_found. Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.
lineYes1-based source line of the expression.
columnYes1-based character (not byte) column. Any position inside the expression's token works; when several tokens touch it an identifier is preferred.

Output Schema

ParametersJSON Schema
NameRequiredDescription
fileYes
lineYes
typeYes
tokenYes
columnYes
effect_rowYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint and idempotentHint annotations, the description discloses that the tool compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. It also details error handling, including stable error codes and the shape of diagnostics. These behavioral details go beyond annotations and are valuable for the agent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but well-structured, front-loading the main purpose and providing usage guidance early. Every sentence adds value, covering purpose, alternatives, return values, error cases, and side-effects, all in a compact paragraph without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity with three parameters and an output schema, the description covers all necessary aspects: what it returns, side effects, error handling, and read-only nature. The output schema likely explains return types, so the description need not repeat them. This is complete for an agent to call correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already provides descriptions for all three parameters, so schema description coverage is 100%. The description adds context by explaining that line and column are 1-based, and that column is character-based (not byte), plus that any position in the token works. However, this is marginal; the schema already covers the core semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with a specific verb ('Infer') and resource ('type and effect row of the expression at a source location'). It explicitly names sibling tools for alternatives, such as `lookup_definition` for named top-level definitions and `explain_effect_row` for effects with explanations, which distinguishes it from potentially confusing peers.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool: for 'what is the type here' on local bindings, sub-expressions, and names in `use` lists. It also provides clear alternatives and when to prefer them: `lookup_definition` for named top-level definitions and `explain_effect_row` for function effects with explanations. This fully covers usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_definitionsOutline moduleA
Read-onlyIdempotent

Outline a .hird module: every name its top-level declarations bind, in source order, plus its imports. Use it to orient in a module before choosing which symbols to pull in with get_context_for_symbol; it is cheap (no bodies, no effect graph) and the only tool that names a module's symbols without a failed lookup. Returns module, imports (each with the imported module as written, its defining file, the qualifier a whole-module import binds or null, and the members a selective import binds unqualified), and definitions, each with name, kind (the kinds lookup_definition reports: a type also lists each constructor, a tool its generated tool_function, an actor its message_type and each message_constructor), line, a one-line signature, nullable doc, and approx_tokens (the signature's estimated cost at ~4 characters per token, so a get_context_for_symbol budget of at least that keeps the signature whole). Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.

Output Schema

ParametersJSON Schema
NameRequiredDescription
fileYes
moduleYes
importsYes
definitionsYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already declare readOnly, idempotent, and non-destructive. The description goes beyond that by disclosing that the tool compiles the file's directory in memory (cached until a sibling changes) and runs no side effects, and by giving a full list of stable error codes and diagnostic shapes. No contradiction exists; the description adds valuable behavioral context the annotations do not cover.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is longer than average, but every sentence carries a distinct fact: purpose, use case, return structure, behavior, and error handling. It is front-loaded with the core purpose and usage guidance before diving into output details, with no filler or tautology.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool is a small, read-only helper, the description is complete. It explains the return shape, the semantics of each field, the caching behavior, and all possible error codes. An agent has all the information needed to invoke the tool correctly and interpret its results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema fully covers the sole `file` parameter (path resolution, relative/absolute, compilation of a directory, import resolution). The description adds the caching detail and reinforces the read-only nature, but it does not fundamentally alter the parameter's meaning. With 100% schema coverage, the baseline is 3; the cache and error behavior are a small yet useful bonus.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description begins with a specific verb and resource: 'Outline a .hird module: every name its top-level declarations bind, in source order, plus its imports.' It explicitly distinguishes itself from siblings by stating it is 'the only tool that names a module's symbols without a failed lookup' and referencing `get_context_for_symbol` as the follow-up, making it impossible to confuse with other tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides explicit guidance: 'Use it to orient in a module before choosing which symbols to pull in with get_context_for_symbol' and gives a clear contrast with alternatives (it is cheap because it has no bodies or effect graph, and it avoids failed lookups). This tells the agent exactly when to use it, when not to, and which alternative to select.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lookup_definitionLook up definitionA
Read-onlyIdempotent

Look up a top-level definition by name: defining file and line, kind, type, and doc comment. Use it first to locate or identify a symbol; use get_context_for_symbol when you also want effects, callers, and callees, explain_effect_row to interpret a function's effects, and render_ir_fragment for its body. Returns kind (function, type, type alias, constructor, effect, tool, tool_function, actor, message_type, message_constructor, supervisor, or extern), line, and nullable type (for a type alias, the type it expands to) and doc; file is the sibling module when the name is imported. An unknown name is not_found, with every name in the file's scope in error.data.available. Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.
nameYesThe definition's name, as the file's source would write it: a local definition, a member imported with `use Mod.{name}`, or `Qualifier.name` through `use Mod`.

Output Schema

ParametersJSON Schema
NameRequiredDescription
docNo
fileYes
kindYes
lineYes
nameYes
typeNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already mark the tool read-only and idempotent, and the description adds substantial behavioral context beyond that: it compiles the file's directory in memory, is cached until a sibling changes, writes or executes nothing, returns not_found for unknown names, and exposes stable error codes. This exceeds the coverage provided by annotations alone.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence earns its place: purpose first, then sibling routing, then return shape, unknown-name behavior, read-only guarantee, and error code taxonomy. It is dense, well-ordered, and free of filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the output schema, and the annotation context, the description is complete: it covers return values, naming semantics, imported-name behavior, failure modes, caching, and relationships to sibling tools. An agent has all the information needed to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the input schema's parameter descriptions are already rich, so the description does not need to add much. It contributes some framing by noting the lookup is by name and clarifying that the name is written as the source would write it, but the schema carries the main burden.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb-resource pair ('Look up a top-level definition by name') and enumerates exactly what the tool returns: defining file/line, kind, type, and doc comment. It further distinguishes itself from siblings such as get_context_for_symbol and render_ir_fragment, so an agent can select it without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says to use this tool first to locate or identify a symbol and names the alternatives for additional needs: get_context_for_symbol for effects/callers/callees, explain_effect_row for behavior, and render_ir_fragment for the body. This gives clear when-to-use and when-not-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

render_ir_fragmentRender IR fragmentA
Read-onlyIdempotent

Render the typed intermediate representation (IR) of one top-level definition as JSON. Use it when the exact lowered structure matters: desugared bodies, the resolved type on every node, a tool's generated function. The IR is verbose and follows the compiler's declaration serialization, so for a human-oriented view prefer get_context_for_symbol or lookup_definition. Returns module, name, and ir (the serialized declaration). An unknown name is not_found with the available names in error.data.available; a type alias is no_ir (aliases are expanded before lowering) with its expansion in error.data.expansion. Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing. Failures are isError results with a stable error.code: file_not_found, read_error, invalid_params, and, for every tool but check_file, parse_error or check_error carrying coded diagnostics in error.data.diagnostics (the shape check_file returns).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYesPath to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file.
nameYesThe definition's name, as the file's source would write it: a local definition, a member imported with `use Mod.{name}`, or `Qualifier.name` through `use Mod`.

Output Schema

ParametersJSON Schema
NameRequiredDescription
irYesThe declaration, as the compiler serializes its IR.
fileYes
nameYes
moduleYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, but the description adds valuable context: 'Read-only: compiles the file's directory in memory (cached until a sibling changes) and writes or executes nothing.' It also details error codes, error.data shapes, and the stable error.code list, all beyond the annotations. No contradiction exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Though long, every sentence carries new information: purpose, use case, alternative tools, output fields, error scenarios, read-only behavior, caching, and error codes. It is front-loaded with purpose and usage, then moves to outputs and failures. Nothing is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with a rich behavioral and error profile, the description covers when to use, what to expect, error shapes, side-effect guarantees, and alternatives. An output schema exists, so the return type doesn't need full elaboration here. The description is complete for an agent to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so each parameter has a clear description. The tool description does not add parameter-specific meaning beyond what the schema provides; it only references output and error behavior. With full schema coverage, a baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb-resource pair: 'Render the typed intermediate representation (IR) of one top-level definition as JSON.' It immediately distinguishes itself from human-oriented siblings by naming `get_context_for_symbol` and `lookup_definition` as preferred for human views, so an agent can tell exactly what this tool is for and what it is not.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states when to use it: 'Use it when the exact lowered structure matters: desugared bodies, the resolved type on every node, a tool's generated function.' It also gives an explicit exclusion: 'for a human-oriented view prefer `get_context_for_symbol` or `lookup_definition`.' This is textbook usage guidance with alternatives and conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.4.0
    • Addedcheck_file
    • Changedemit_actor_effect_graph1 field changed
      • changedInput schema / properties / actor_name / description
        Previous value: -"The root actor's name, as declared in this file. Actors are not resolved through imports; an unknown name fails with `not_found` and `error.data.available_actors`."New value: +"The root actor's name, as the file would write it (`Planner`, `Fleet.Planner`): a local actor, or a sibling module's as `Qualifier.name` through `use Mod` (or `Module.name` for any module of the program). An unknown name fails with `not_found` and `error.data.available_actors`."
    • Changedexplain_actor_protocol1 field changed
      • changedInput schema / properties / actor_name / description
        Previous value: -"The actor's name, as declared in this file. Actors are not resolved through imports; an unknown name fails with `not_found` and `error.data.available_actors`."New value: +"The actor's name, as the file would write it (`Planner`, `Fleet.Planner`): a local actor, or a sibling module's as `Qualifier.name` through `use Mod` (or `Module.name` for any module of the program). An unknown name fails with `not_found` and `error.data.available_actors`."
    • Changedget_context_for_symbol3 fields changed
      • addedInput schema / properties / budget / default
        Added value: +400
      • changedInput schema / properties / budget / description
        Previous value: -"Approximate token budget for the summary, at ~4 characters per token (default 400). A section that does not fit is dropped whole and named in `omitted`; only the signature is truncated."New value: +"Approximate token budget for the summary, at ~4 characters per token; optional, 400 when omitted. A section that does not fit is dropped whole and named in `omitted`; only the signature is truncated."
      • changedInput schema / required
        Previous value: -[
        -  "budget",
        -  "file",
        -  "name"
        -]New value: +[
        +  "file",
        +  "name"
        +]
    • Addedlist_definitions
  2. 8 tool updates
    • Changedemit_actor_effect_graph4 fields changed
      • changedInput schema / properties / actor_name / description
        Previous value: -"The root actor's name."New value: +"The root actor's name, as declared in this file. Actors are not resolved through imports; an unknown name fails with `not_found` and `error.data.available_actors`."
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."New value: +"Path to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file."
      • changedInput schema / required
        Previous value: -[
        -  "file",
        -  "actor_name"
        -]New value: +[
        +  "actor_name",
        +  "file"
        +]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "actors": {
        +      "items": {
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "module": {
        +      "type": "string"
        +    },
        +    "root": {
        +      "type": "string"
        +    },
        +    "schema_version": {
        +      "type": "integer"
        +    },
        +    "supervisors": {
        +      "items": {
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "tools": {
        +      "items": {
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "actors",
        +    "module",
        +    "root",
        +    "schema_version",
        +    "supervisors",
        +    "tools"
        +  ],
        +  "type": "object"
        +}
    • Changedexplain_actor_protocol4 fields changed
      • changedInput schema / properties / actor_name / description
        Previous value: -"The actor's name."New value: +"The actor's name, as declared in this file. Actors are not resolved through imports; an unknown name fails with `not_found` and `error.data.available_actors`."
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."New value: +"Path to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file."
      • changedInput schema / required
        Previous value: -[
        -  "file",
        -  "actor_name"
        -]New value: +[
        +  "actor_name",
        +  "file"
        +]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "actor": {
        +      "properties": {
        +        "effects": {
        +          "description": "An effect row: `effects` (each with `head`, `args`, `display`), an optional `tail` row variable, and its `display` string.",
        +          "type": "object"
        +        },
        +        "handlers": {
        +          "type": "array"
        +        },
        +        "init": {
        +          "type": "object"
        +        },
        +        "line": {
        +          "type": "integer"
        +        },
        +        "message": {
        +          "type": "object"
        +        },
        +        "name": {
        +          "type": "string"
        +        },
        +        "state": {
        +          "description": "A type, with its `display` string as Hirð prints it.",
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "name",
        +        "line",
        +        "state",
        +        "message",
        +        "init",
        +        "handlers",
        +        "effects"
        +      ],
        +      "type": "object"
        +    },
        +    "file": {
        +      "type": "string"
        +    },
        +    "module": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "actor",
        +    "file",
        +    "module"
        +  ],
        +  "type": "object"
        +}
    • Changedexplain_effect_row2 fields changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."New value: +"Path to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "effect_row": {
        +      "type": "string"
        +    },
        +    "effects": {
        +      "items": {
        +        "properties": {
        +          "effect": {
        +            "type": "string"
        +          },
        +          "explanation": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "effect",
        +          "explanation"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "file": {
        +      "type": "string"
        +    },
        +    "name": {
        +      "type": "string"
        +    },
        +    "open": {
        +      "type": "boolean"
        +    },
        +    "pure": {
        +      "type": "boolean"
        +    },
        +    "type": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "effect_row",
        +    "effects",
        +    "file",
        +    "name",
        +    "open",
        +    "pure",
        +    "type"
        +  ],
        +  "type": "object"
        +}
    • Changedget_context_budget2 fields changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."New value: +"Path to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "approx_tokens": {
        +      "properties": {
        +        "actors": {
        +          "type": "integer"
        +        },
        +        "effects": {
        +          "type": "integer"
        +        },
        +        "functions": {
        +          "type": "integer"
        +        },
        +        "supervisors": {
        +          "type": "integer"
        +        },
        +        "tools": {
        +          "type": "integer"
        +        },
        +        "total": {
        +          "type": "integer"
        +        },
        +        "types": {
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "types",
        +        "effects",
        +        "actors",
        +        "supervisors",
        +        "tools",
        +        "functions",
        +        "total"
        +      ],
        +      "type": "object"
        +    },
        +    "file": {
        +      "type": "string"
        +    },
        +    "module": {
        +      "type": "string"
        +    },
        +    "note": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "approx_tokens",
        +    "file",
        +    "module",
        +    "note"
        +  ],
        +  "type": "object"
        +}
    • Changedget_context_for_symbol4 fields changed
      • changedInput schema / properties / budget / description
        Previous value: -"Approximate token budget for the summary (default 400)."New value: +"Approximate token budget for the summary, at ~4 characters per token (default 400). A section that does not fit is dropped whole and named in `omitted`; only the signature is truncated."
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."New value: +"Path to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file."
      • changedInput schema / required
        Previous value: -[
        -  "file",
        -  "name"
        -]New value: +[
        +  "budget",
        +  "file",
        +  "name"
        +]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "approx_tokens": {
        +      "type": "integer"
        +    },
        +    "budget": {
        +      "type": "integer"
        +    },
        +    "file": {
        +      "type": "string"
        +    },
        +    "kind": {
        +      "type": "string"
        +    },
        +    "omitted": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "summary": {
        +      "type": "string"
        +    },
        +    "symbol": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "approx_tokens",
        +    "budget",
        +    "file",
        +    "kind",
        +    "omitted",
        +    "summary",
        +    "symbol"
        +  ],
        +  "type": "object"
        +}
    • Changedinfer_type5 fields changed
      • changedInput schema / properties / column / description
        Previous value: -"1-based character column."New value: +"1-based character (not byte) column. Any position inside the expression's token works; when several tokens touch it an identifier is preferred."
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."New value: +"Path to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file."
      • changedInput schema / properties / line / description
        Previous value: -"1-based source line."New value: +"1-based source line of the expression."
      • changedInput schema / required
        Previous value: -[
        -  "file",
        -  "line",
        -  "column"
        -]New value: +[
        +  "column",
        +  "file",
        +  "line"
        +]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "column": {
        +      "type": "integer"
        +    },
        +    "effect_row": {
        +      "type": "string"
        +    },
        +    "file": {
        +      "type": "string"
        +    },
        +    "line": {
        +      "type": "integer"
        +    },
        +    "token": {
        +      "type": "string"
        +    },
        +    "type": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "column",
        +    "effect_row",
        +    "file",
        +    "line",
        +    "token",
        +    "type"
        +  ],
        +  "type": "object"
        +}
    • Changedlookup_definition2 fields changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."New value: +"Path to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "doc": {
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "file": {
        +      "type": "string"
        +    },
        +    "kind": {
        +      "type": "string"
        +    },
        +    "line": {
        +      "type": "integer"
        +    },
        +    "name": {
        +      "type": "string"
        +    },
        +    "type": {
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    }
        +  },
        +  "required": [
        +    "file",
        +    "kind",
        +    "line",
        +    "name"
        +  ],
        +  "type": "object"
        +}
    • Changedrender_ir_fragment2 fields changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."New value: +"Path to a .hird source file, absolute or relative to the server's working directory. Every .hird file in its directory is compiled with it as one program, so imported names resolve; answers may name a sibling file."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "file": {
        +      "type": "string"
        +    },
        +    "ir": {
        +      "description": "The declaration, as the compiler serializes its IR.",
        +      "type": "object"
        +    },
        +    "module": {
        +      "type": "string"
        +    },
        +    "name": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "file",
        +    "ir",
        +    "module",
        +    "name"
        +  ],
        +  "type": "object"
        +}
  3. 8 tool updatesv0.1.1
    • Changedemit_actor_effect_graph1 field changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file."New value: +"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."
    • Changedexplain_actor_protocol1 field changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file."New value: +"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."
    • Changedexplain_effect_row2 fields changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file."New value: +"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."
      • changedInput schema / properties / fn_name / description
        Previous value: -"The function's name."New value: +"The function's name, as the file's source would write it: a local definition, a member imported with `use Mod.{name}`, or `Qualifier.name` through `use Mod`."
    • Changedget_context_budget1 field changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file."New value: +"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."
    • Changedget_context_for_symbol2 fields changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file."New value: +"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."
      • changedInput schema / properties / name / description
        Previous value: -"The symbol's name."New value: +"The symbol's name, as the file's source would write it: a local definition, a member imported with `use Mod.{name}`, or `Qualifier.name` through `use Mod`."
    • Changedinfer_type1 field changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file."New value: +"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."
    • Changedlookup_definition2 fields changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file."New value: +"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."
      • changedInput schema / properties / name / description
        Previous value: -"The definition's name."New value: +"The definition's name, as the file's source would write it: a local definition, a member imported with `use Mod.{name}`, or `Qualifier.name` through `use Mod`."
    • Changedrender_ir_fragment2 fields changed
      • changedInput schema / properties / file / description
        Previous value: -"Path to a .hird source file."New value: +"Path to a .hird source file. Its directory's .hird files are compiled together as one program, so imported names resolve."
      • changedInput schema / properties / name / description
        Previous value: -"The definition's name."New value: +"The definition's name, as the file's source would write it: a local definition, a member imported with `use Mod.{name}`, or `Qualifier.name` through `use Mod`."
  4. 8 tool updatesv0.1.0
    • First observedemit_actor_effect_graph
    • First observedexplain_actor_protocol
    • First observedexplain_effect_row
    • First observedget_context_budget
    • First observedget_context_for_symbol
    • First observedinfer_type
    • First observedlookup_definition
    • First observedrender_ir_fragment

TDQS

A4.7/5.0

Scored across 10 tools

Disambiguation5/5

Each tool targets a distinct resource and action: whole-program diagnostics, module outlines, expression types, definition lookup, effect explanations, IR rendering, actor protocols, actor graphs, and context budgeting. The cross-references in the descriptions make the boundaries explicit, so an agent can reliably choose the right tool.

Naming Consistency5/5

All tool names follow a strict verb_noun snake_case pattern (check_file, list_definitions, infer_type, lookup_definition, explain_effect_row, render_ir_fragment, explain_actor_protocol, emit_actor_effect_graph, get_context_for_symbol, get_context_budget). Verbs are specific and the object is consistently a noun, making the naming predictable and scannable.

Tool Count5/5

With 10 tools, the set is well within the typical 3–15 range and each tool earns its place by covering a distinct aspect of the language analysis domain. The count feels comprehensive without being bloated; no tool appears redundant.

Completeness4/5

The tool set covers a broad range of analysis operations: diagnostics, symbols, types, effects, IR, actors, and context estimation. The main gaps are raw source access and cross-project symbol search, which are not provided, but the server's read-only analysis focus is otherwise well covered.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables querying and analyzing code relationships by building a lightweight graph of TypeScript and Python symbols. Supports symbol lookup, reference tracking, impact analysis from diffs, and code snippet retrieval through natural language.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server for code comprehension indices and test maps. It answers what must be known to change a function and which tests pin its behavior, using compiler type information.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables querying Swift codebases as a knowledge graph, with hybrid semantic and structural search, type hierarchy, public API inspection, and code analysis through MCP tools.
    21
    Elastic 2.0