Repository navigation
Changes
Added
hird effect-diff, a gate on effect reach. A newhird-policy
crate diffs a committed effect graph against a freshly emitted one
under the graph's identity contract and classifies every change: a row
gaining an effect or opening, a protocol constructor added, removed, or
reshaped, and a new actor or tool widen; a removal narrows; a
signature change, a supervision change, and a pure addition are
neutral.hird effect-diff <baseline> <input>re-emits the graph,
prints the report as text or--json, and exits nonzero when reach
widened, so a change cannot quietly grant a program more than its
baseline allowed.--exactexits nonzero on any drift, so a narrowing
cannot leave the baseline over-granting either. A baseline written
under anotherschema_versionis refused; fields added after schema 1
default when absent.- The approved-baseline workflow, dogfooded.
demo/effect-baselines/
holds one committed graph per demo program,demo/effect-baselines.sh
checks or regenerates them, a CI job runs the check, and a test guards
them locally, so a change that moves a graph must update its baseline
in the same reviewable diff.docs/effect-graph.mdstates the JSON
shape, the identity contract — which fields a diff compares, and which
(everyline, and source order) it ignores — and the copyable recipe. - Functions in the effect graph. The graph gains a
functions
section: name, line, parameters, result, and the declared row, so a
reviewer reads off the graph that a plain function is honestly
! {Tool<X>}and nothing more. The text form prints onefnline per
declaration. Additive under schema version 1. pubtools, actors, and supervisors cross module boundaries. A
pub toolexports its marker — forTool<Name>rows andhandle
arms — and its generated function; apub actorexports the actor
entry forspawnand child specs and its message type transparently,
so another module namesPid<Msg>and builds messages; apub supervisorexports the name forsuperviseandchild. Each imports
selectively by name, and a qualifier import reaches the tool function.
Supervisor children carry their actor's message type, sochild()on
an imported supervisor types without importing the actor. The IR
records an imported tool under the qualified spelling its calls carry,
and codegen routes those calls through the tool dispatcher rather than
emitting a remote call into a function the declaring module never
emits.docs/context-packing.md. Realget_context_for_symbol
transcripts for the demoPlanneractor at 50 and 400 tokens, what
each budget dropped and why, with a README pointer.- MCP resources and an authoring prompt.
hird-mcpadvertises
resourcesandpromptsalongsidetools. Three resources serve the
documents an agent otherwise had to be handed out of band, embedded in
the binary:hird://docs/writing-hird-llm(the agent-facing guide),
hird://docs/parser-diagnostics(theP…code index), and
hird://phrasebook. One prompt,author_supervised_module(file, purpose), scripts the authoring loop: write a supervised actor module,
then verify it withcheck_fileand the introspection tools until the
compiler confirms it. list_definitionsMCP tool.hird-mcpgains a module outline:
every name a file's top-level declarations bind, in source order, with
kind, line, a one-line signature, doc, and the signature's approximate
token cost, plus the file's imports (module, defining file, qualifier or
selected members). A type lists its constructors, a tool its generated
function, an actor its message type and constructors. Until now a
module's names were discoverable only by failing a lookup and reading
error.data.available, andget_context_budgetnames no symbols; the
per-entry cost sizes aget_context_for_symbolbudget that keeps the
signature whole.check_fileMCP tool.hird-mcpgains a ninth tool that checks a
file's whole program and returns every diagnostic as a result, warnings
included, withoksaying whether any is an error. Until now diagnostics
only surfaced when another tool failed on the queried file, so a warning
such as C0056 on a clean module was invisible and an agent had to probe
tool by tool to find the first error. Each diagnostic names itsfile,
so a sibling that fails to parse is reported under its own path.
Fixed
explain_effect_rowrejected names it listed as available. A tool
name (ReadRepo) wasnot_foundalthoughlookup_definitionaccepted
it; it now explains the tool's generated function,Tool<ReadRepo>
included, and reports that function asname. A defined name that is not
a function (a type, an alias, an actor) isnot_a_functionrather than
not_found.get_context_for_symboldeclaredbudgetrequired. The tool's
input schema listed every property as required, so a strict MCP client
could not omitbudgetalthough the server defaults it to 400. The
property now carries"default": 400and is optional; an input property
is required exactly when it declares no default.- Type aliases were half-visible to the MCP tools.
lookup_definition
on an alias answered kindtype aliaswith no type, while
get_context_for_symbolandrender_ir_fragmenton the same name said
not_foundand listed it under the available names. The checker now
records each module's aliases as the types they expand to
(CheckedFile::aliases, a parametric alias quantified), so
lookup_definitionreports the expansion,get_context_for_symbol
summarizes the alias as its signature and doc,get_context_budget
counts aliases undertypes, andrender_ir_fragmentfails withno_ir
and the expansion inerror.data.expansion— aliases are erased before
lowering, so there is no IR to render. The descriptor's kind list names
type alias.
Changed
emit-effect-graph --jsonis keyed by module. The flag emitted a
single module's graph; it now emits one document for a file and a
directory alike —schema_versionplusmodules, the program's module
graphs keyed by module name and sorted by it, each still carrying its
ownschema_versionandmoduleso one graph stays self-describing
when consumed alone. A consumer of the 0.3 shape now reads through
modules. The text form locates declarations by the module's own file
name (agent_fleet.hird:12), never a checkout path, so it is stable
across machines.- MCP actor tools follow imports.
explain_actor_protocoland
emit_actor_effect_graphtakeactor_nameas the file would write it:
local, selectively imported,Qualifier.namethrough ause, or
Module.namefor any module in the program. Actor reach is now built
program-wide, resolving each row's type names through the naming
module's own scope so same-named declarations in two modules stay
distinct, and every emitted node carries a module tag. - MCP diagnostics carry codes, hints, and positions. Parse diagnostics
inerror.data.diagnosticsnow include theirP…code andhelphint
(they carried message and line only); parse and check diagnostics alike
gain a 1-based charactercolumnand an exclusiveend_line/
end_column, and check diagnostics list their same-filerelated
locations.check_filereturns the same shape. - MCP tool descriptors disclose more. Every
hird-mcptool now carries
MCP annotations (readOnlyHint,idempotentHint, no destructive or
open-world behaviour), atitle, and anoutputSchemamatching its
structuredContent. Descriptions state the result fields, the error codes
a call can fail with, and which sibling tool to prefer for related
questions; parameter descriptions spell out path resolution, character
columns, actor-name scope, and howbudgetdrops sections.
Install
Download the archive for your platform below and extract it; each one
carries three binaries:
hird— the compiler CLI (check,build,run,emit-ast,emit-effect-graph)hird-lsp— the language server (diagnostics, hover, go-to-definition)hird-mcp— the MCP server exposing compiler introspection to LLM agents
Or build from source with Rust 1.97+:
cargo install --git https://github.com/no-materials/hird hird-cliCompiling and running Hirð programs (hird build, hird run) needs
Erlang/OTP on PATH; hird check works without it.
See the README to get started.