Hirð
This server gives LLM agents structured, read-only access to the Hirð compiler pipeline for querying types, definitions, effects, actors, and symbol context in .hird programs.
Infer expression types: get the type and effect row of any expression at a given file/line/column.
Look up definitions: find a top-level symbol's file, line, kind, type, and doc comment, including imported names.
Explain effect rows: see what effects a function may perform, whether the row is open or pure, and a plain-language explanation of each effect.
Render IR fragments: inspect the typed intermediate representation of a definition as JSON.
Explain actor protocols: learn an actor's message type, constructors, state, init parameters/effects, per-handler effects, and declared effect summary.
Emit actor effect graphs: get the transitive graph of actors, supervisors, and tools reachable from a root actor.
Summarize symbols for context: produce token-budget-aware summaries with signature, effects, doc, callers, and callees, reporting what was omitted.
Estimate context budgets: estimate token costs for loading a file's declarations by category (types, effects, actors, supervisors, tools, functions).
Fail with structured, stable errors: errors carry coded diagnostics and helpful available-name lists, making it easy for agents to self-correct.
Operate read-only: compiles in memory, writes nothing, and executes no code.
Hirð

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 # optionalWith 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 |
| type- and effect-check; coded diagnostics |
| emit readable Erlang, compile it to |
| build, then execute |
| record one run of the built-in demo, replay it against variants of the program |
| the typed IR of every definition |
| actors, mailboxes, handler rows, supervisors, tools, function rows |
| 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_fleetRecord 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 # replayThe 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 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 bybd.
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-featuresBEAM-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
Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
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 toolscheck_fileCheck programARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | |
| file | Yes | |
| diagnostics | Yes |
TDQS
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.
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.
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.
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.
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.
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 graphARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. | |
| actor_name | Yes | 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`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| root | Yes | |
| tools | Yes | |
| actors | Yes | |
| module | Yes | |
| supervisors | Yes | |
| schema_version | Yes |
TDQS
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.
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.
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.
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.
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.
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 protocolARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. | |
| actor_name | Yes | 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`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| file | Yes | |
| actor | Yes | |
| module | Yes |
TDQS
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.
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.
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.
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.
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.
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 rowARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. | |
| fn_name | Yes | 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`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| file | Yes | |
| name | Yes | |
| open | Yes | |
| pure | Yes | |
| type | Yes | |
| effects | Yes | |
| effect_row | Yes |
TDQS
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.
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.
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.
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.
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.
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 budgetARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. |
Output Schema
| Name | Required | Description |
|---|---|---|
| file | Yes | |
| note | Yes | |
| module | Yes | |
| approx_tokens | Yes |
TDQS
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.
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.
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.
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.
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.
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 contextARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. | |
| name | Yes | 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`. | |
| budget | No | 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. |
Output Schema
| Name | Required | Description |
|---|---|---|
| file | Yes | |
| kind | Yes | |
| budget | Yes | |
| symbol | Yes | |
| omitted | Yes | |
| summary | Yes | |
| approx_tokens | Yes |
TDQS
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.
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.
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.
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.
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.
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 typeARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. | |
| line | Yes | 1-based source line of the expression. | |
| column | Yes | 1-based character (not byte) column. Any position inside the expression's token works; when several tokens touch it an identifier is preferred. |
Output Schema
| Name | Required | Description |
|---|---|---|
| file | Yes | |
| line | Yes | |
| type | Yes | |
| token | Yes | |
| column | Yes | |
| effect_row | Yes |
TDQS
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.
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.
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.
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.
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.
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 moduleARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. |
Output Schema
| Name | Required | Description |
|---|---|---|
| file | Yes | |
| module | Yes | |
| imports | Yes | |
| definitions | Yes |
TDQS
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.
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.
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.
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.
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.
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 definitionARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. | |
| name | Yes | 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`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| doc | No | |
| file | Yes | |
| kind | Yes | |
| line | Yes | |
| name | Yes | |
| type | No |
TDQS
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.
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.
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.
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.
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.
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 fragmentARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | 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. | |
| name | Yes | 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`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ir | Yes | The declaration, as the compiler serializes its IR. |
| file | Yes | |
| name | Yes | |
| module | Yes |
TDQS
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.
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.
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.
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.
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.
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.
5 tool updates
v0.4.0- Added
check_file - Changed
emit_actor_effect_graph1 field changed- changed
Input schema / properties / actor_name / descriptionPrevious 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`."
- Changed
explain_actor_protocol1 field changed- changed
Input schema / properties / actor_name / descriptionPrevious 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`."
- Changed
get_context_for_symbol3 fields changed- added
Input schema / properties / budget / defaultAdded value: +400 - changed
Input schema / properties / budget / descriptionPrevious 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." - changed
Input schema / requiredPrevious value: -[ - "budget", - "file", - "name" -]New value: +[ + "file", + "name" +]
- Added
list_definitions
8 tool updates
- Changed
emit_actor_effect_graph4 fields changed- changed
Input schema / properties / actor_name / descriptionPrevious 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`." - changed
Input schema / properties / file / descriptionPrevious 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." - changed
Input schema / requiredPrevious value: -[ - "file", - "actor_name" -]New value: +[ + "actor_name", + "file" +] - changed
Output 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" +}
- Changed
explain_actor_protocol4 fields changed- changed
Input schema / properties / actor_name / descriptionPrevious 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`." - changed
Input schema / properties / file / descriptionPrevious 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." - changed
Input schema / requiredPrevious value: -[ - "file", - "actor_name" -]New value: +[ + "actor_name", + "file" +] - changed
Output 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" +}
- Changed
explain_effect_row2 fields changed- changed
Input schema / properties / file / descriptionPrevious 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." - changed
Output 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" +}
- Changed
get_context_budget2 fields changed- changed
Input schema / properties / file / descriptionPrevious 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." - changed
Output 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" +}
- Changed
get_context_for_symbol4 fields changed- changed
Input schema / properties / budget / descriptionPrevious 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." - changed
Input schema / properties / file / descriptionPrevious 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." - changed
Input schema / requiredPrevious value: -[ - "file", - "name" -]New value: +[ + "budget", + "file", + "name" +] - changed
Output 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" +}
- Changed
infer_type5 fields changed- changed
Input schema / properties / column / descriptionPrevious 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." - changed
Input schema / properties / file / descriptionPrevious 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." - changed
Input schema / properties / line / descriptionPrevious value: -"1-based source line."New value: +"1-based source line of the expression." - changed
Input schema / requiredPrevious value: -[ - "file", - "line", - "column" -]New value: +[ + "column", + "file", + "line" +] - changed
Output 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" +}
- Changed
lookup_definition2 fields changed- changed
Input schema / properties / file / descriptionPrevious 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." - changed
Output 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" +}
- Changed
render_ir_fragment2 fields changed- changed
Input schema / properties / file / descriptionPrevious 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." - changed
Output 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" +}
8 tool updates
v0.1.1- Changed
emit_actor_effect_graph1 field changed- changed
Input schema / properties / file / descriptionPrevious 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."
- Changed
explain_actor_protocol1 field changed- changed
Input schema / properties / file / descriptionPrevious 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."
- Changed
explain_effect_row2 fields changed- changed
Input schema / properties / file / descriptionPrevious 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." - changed
Input schema / properties / fn_name / descriptionPrevious 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`."
- Changed
get_context_budget1 field changed- changed
Input schema / properties / file / descriptionPrevious 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."
- Changed
get_context_for_symbol2 fields changed- changed
Input schema / properties / file / descriptionPrevious 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." - changed
Input schema / properties / name / descriptionPrevious 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`."
- Changed
infer_type1 field changed- changed
Input schema / properties / file / descriptionPrevious 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."
- Changed
lookup_definition2 fields changed- changed
Input schema / properties / file / descriptionPrevious 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." - changed
Input schema / properties / name / descriptionPrevious 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`."
- Changed
render_ir_fragment2 fields changed- changed
Input schema / properties / file / descriptionPrevious 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." - changed
Input schema / properties / name / descriptionPrevious 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`."
8 tool updates
v0.1.0- First observed
emit_actor_effect_graph - First observed
explain_actor_protocol - First observed
explain_effect_row - First observed
get_context_budget - First observed
get_context_for_symbol - First observed
infer_type - First observed
lookup_definition - First observed
render_ir_fragment
TDQS
Scored across 10 tools
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.
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.
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.
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
Related MCP Connectors
Ask a codebase what calls what: search, blast radius, paths between symbols, and diffs.
Codebase graphs, caller impact analysis, and recorded project context for AI coding agents.
Deterministic context layer for your codebase: change impact, blast radius, answers with receipts.
Stateless TS/JS compiler facts for agents: references, imports, impact. No repo index or OAuth.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables 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.-
- AlicenseNot gradedqualityBmaintenanceMCP 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
- FlicenseNot gradedqualityAmaintenanceProvides efficient code navigation and graph-based analysis for AI agents, enabling symbol resolution, callers, implementations, and type schemas with minimal token usage.-
- AlicenseAqualityBmaintenanceEnables querying Swift codebases as a knowledge graph, with hybrid semantic and structural search, type hierarchy, public API inspection, and code analysis through MCP tools.21Elastic 2.0