Hirð

Introspección del compilador para el lenguaje Hirð (actores tipados y filas de efectos en el BEAM): inferencia de tipos, búsqueda de definiciones, explicación de filas de efectos, fragmentos de IR, gráficos de protocolo de actores y supervisión, y resúmenes de símbolos conscientes del presupuesto de tokens.

Documentación

Hirð

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

Una hirð es la guardia personal de un rey nórdico: vasallos juramentados, cada uno con un deber nombrado, responsables ante un solo señor. Ilustración de Erik Werenskiold para la saga de Magnús Erlingsson en la Heimskringla de Snorri (dominio público, vía Wikimedia Commons).

Un lenguaje tipado para sistemas de agentes de larga duración en BEAM: seguimiento de filas de efectos, efectos de herramientas auditables, actores tipados y supervisión OTP. Los frameworks de agentes en Python ocultan los efectos secundarios en una sopa de corrutinas; Hirð hace que cada llamada a herramienta, cada mensaje de actor y cada límite de supervisor sean visibles en los tipos y consultables por herramientas.

Lo que eso aporta: reproducción determinista del tráfico real de agentes. Cada llamada a herramienta se registra incondicionalmente en un formato de cable canónico, de modo que una ejecución registrada es un archivo que puedes reproducir: las mismas llamadas, en el mismo orden, cada una servida con el resultado que obtuvo la ejecución registrada, sin contactar ningún servicio. Eso es una prueba de regresión sin oráculo que mantener, un informe de error que se reproduce y un entorno fijo para evaluar un cambio. Tampoco es algo que puedas adaptar retroactivamente a un framework que oculta sus efectos secundarios: necesita los efectos en los tipos y una única ruta de despacho debajo de ellos. hird demo es esa afirmación en un comando: registra una ejecución del planificador de demostración, reproduce esa única grabación contra tres variantes del programa e imprime dónde se separó cada una de ella.

Y sistemas que se mantienen en pie. Un programa Hirð no es un script que termina: fn main puede iniciar un árbol de supervisión y stand, dejando actores tipados sirviendo después de que su propio trabajo haya terminado — impulsando sus propias rondas periódicas mediante una capacidad de reloj, fallando y reiniciándose bajo un presupuesto declarado, cada ronda en el flujo de auditoría. hird run demo/agent_fleet es esa afirmación en ejecución: una hirð de tres vasallos que sigue funcionando a través de un fallo deliberado.

Estado: pre-1.0 y experimental. El pipeline del compilador v0.1 funciona de extremo a extremo (las demostraciones a continuación verifican tipos, compilan a Erlang y se ejecutan en BEAM), pero la superficie del lenguaje es inestable, nada está publicado en crates.io y los cambios disruptivos llegan sin ciclos de deprecación. La hoja de ruta vive en el rastreador de problemas del repositorio (ver .beads/README.md).

Instalación

Los binarios precompilados para Linux, macOS y Windows están adjuntos a cada release: extrae el archivo para tu plataforma y coloca hird (el compilador), hird-lsp y hird-mcp en tu PATH.

Desde el código fuente, con Rust 1.97 o más reciente:

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

Con Nix, los mismos tres binarios son salidas de flake (nix run github:no-materials/hird#hird-mcp).

Compilar y ejecutar programas necesita Erlang/OTP en PATH (apt install erlang, brew install erlang, …); hird check funciona sin él.

Inicio rápido

Hirð no tiene print ambiental. Cualquier cosa que un programa le diga al mundo exterior pasa por una herramienta — una operación externa declarada, tipada y auditada — así que el programa observable más pequeño es una llamada a herramienta. Guarda esto como 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"}

Ocurrieron tres cosas. Declarar tool Say creó el efecto Tool<Say> y un say invocable. El bloque handle proporcionó una implementación y descargó ese efecto, así que main es honestamente ! {}. Y la llamada se registró en el flujo de auditoría — incondicionalmente, porque las llamadas a herramientas simuladas y reales se auditan de manera idéntica. Las grafías de operadores ASCII (->) se normalizan a sus formas Unicode () en el momento del análisis léxico, así que cualquiera de las dos es entrada legal.

ComandoQué hace
hird check <file-or-dir>verificación de tipos y efectos; diagnósticos codificados
hird build <file-or-dir>emitir Erlang legible, compilarlo a .beam
hird run <file-or-dir>compilar y luego ejecutar fn main en BEAM
hird demoregistrar una ejecución de la demostración integrada, reproducirla contra variantes del programa
hird emit-ast <file> --jsonel IR tipado de cada definición
hird emit-effect-graph <file-or-dir> --jsonactores, buzones, filas de manejadores, supervisores, herramientas, filas de funciones
hird effect-diff --exact <baseline.json> <file-or-dir>fallar cuando el grafo de efectos se desvía de una línea base confirmada

docs/writing-hird-human.md es el recorrido guiado, y phrasebook.md la referencia de sintaxis densa.

La demostración insignia: una hirð permanente de agentes

Una hirð son vasallos con deberes nombrados; demo/agent_fleet/ es la metáfora hecha literal. Tres actores supervisados sirven mientras el programa se mantenga en pie: un Planner se activa a sí mismo con un reloj y forja la orden de cada ronda (planificación pura importada de un segundo módulo — el código fuente cruza un límite real de use), un Executor lleva a cabo la orden a través de Tool<RunErrand> e informa hacia adelante, un Auditor cronica cada resultado a través de Tool<Chronicle>. La ronda 3 hace fallar al ejecutor a propósito: FleetSup reinicia rest_for_one, así que el auditor — aguas abajo del fallo — se reinicia con él, el planificador mantiene su contador de rondas y las rondas siguen llegando. El estado del actor muere con su proceso; el flujo de auditoría es el registro duradero.

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"}

La ronda 3 nunca suena — el fallo consumió su orden — y las dos reinicializaciones reenviadas son el trabajo del supervisor, visibles en el mismo flujo que todo lo demás. El árbol en sí es consultable; su grafo de efectos es el organigrama vivo del sistema, cada vasallo con su deber y sus efectos:

hird emit-effect-graph demo/agent_fleet

Registrar y reproducir una ejecución

demo/agent_planner.hird impulsa una ronda de planificación contra un Planner supervisado: estado del repositorio hacia adentro a través de Tool<ReadRepo>, análisis puro, tickets hacia afuera a través de Tool<CreateTicket>, progreso a través de Tool<Log>. Cada invocación de herramienta — simulada o real — aterriza en el flujo de auditoría, una línea JSON canónica por llamada:

{"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"}

Como el flujo es completo — cada llamada, argumentos completos, resultado etiquetado — una ejecución registrada es un entorno reproducible:

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

El cursor de reproducción tiene prioridad sobre cada bloque handle y install, así que ninguna herramienta se ejecuta y ningún servicio se contacta; cada llamada recibe su resultado registrado, incluidos los fallos. La coincidencia es estricta: la llamada en cada posición debe ser la que el registro grabó allí, o la ejecución falla con un replay_divergence que nombra la posición, la llamada registrada y la ofrecida — y un registro que la ejecución no leyó hasta el final también falla.

Así que una grabación confirmada es una prueba de regresión sin oráculo que mantener: demo/agent_planner.golden.jsonl es una ejecución del planificador, reproducida por la suite de demostración en CI, y la compilación falla en el momento en que las decisiones del programa se desvían de ella. Y como el registro sirve cada resultado, una grabación es un entorno fijo para comparar variantes de un programa — cada brazo se encuentra con un mundo byte-idéntico, así que lo que difiere es atribuible a los programas:

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

Esa evaluación es hird demo: sin argumentos, nada que instalar más allá de Erlang, y nada confirmado que deba ser confiable — escribe el planificador y las dos variantes editadas en _build/hird-demo, registra el episodio en sí y lo reproduce contra las tres.

docs/audit-evidence.md establece qué garantiza el flujo y qué no; docs/tool-effects.md es el formato normativo y la especificación de reproducción.

Herramientas LLM (MCP)

hird-mcp on Glama

hird-mcp es un servidor de Protocolo de Contexto de Modelo sobre el mismo pipeline del compilador, hablando stdio. Da a los agentes LLM consultas estructuradas al compilador en lugar de adivinanzas leyendo el código fuente: check_file (cada diagnóstico de un programa, incluidas las advertencias), list_definitions (un esquema de módulo con costos de tokens por símbolo), infer_type, lookup_definition, explain_effect_row, render_ir_fragment, explain_actor_protocol, emit_actor_effect_graph, get_context_for_symbol (resúmenes de símbolos conscientes del presupuesto de tokens) y get_context_budget. Los errores vuelven estructurados — los nombres no definidos listan los disponibles, los errores de análisis y tipos llevan diagnósticos codificados — así que los agentes pueden autocorregirse solo con la salida de la herramienta. El servidor se autodescribe: sirve la guía de escritura orientada a agentes, el índice de código del analizador y el libro de frases como recursos MCP, y un prompt (author_supervised_module) que guioniza el bucle de escribir-y-verificar.

El repositorio incluye un .mcp.json de ámbito de proyecto, así que las sesiones de Claude Code iniciadas aquí recogen el servidor automáticamente (lanza nix run .#hird-mcp; ejecuta nix build .#hird-mcp una vez para que el primer inicio de sesión no espere una compilación en frío). Cualquier otro cliente MCP puede lanzar el binario hird-mcp directamente, sin argumentos.

Cosas que vale la pena preguntar a un agente conectado a él:

  • "¿Qué hace el actor Planner en demo/agent_planner.hird? Pregunta al compilador en lugar de leer el código fuente."
  • "Si el Executor en demo/agent_fleet falla a mitad de ronda, ¿quién lo reinicia, quién se reinicia con él y cuál es el presupuesto de reinicio?"
  • "Dame un resumen de 50 tokens del actor Planner. Ahora 400 tokens. ¿Qué se perdió?"
  • "Escribe un nuevo módulo Hirð con un actor supervisado e itera con las herramientas hird hasta que confirmen que está limpio."

demo/counter_demo.hird es la salida de ese último prompt: un contador supervisado escrito por un agente LLM que se verificó a sí mismo solo contra las herramientas MCP — verifica tipos y se ejecuta en BEAM sin modificar. Y demo/heartbeat.hird es el programa permanente más pequeño: un actor, un reloj, un latido por segundo hasta Ctrl-C. docs/writing-hird-llm.md es la guía orientada a agentes, y docs/context-packing.md muestra el tercer prompt respondido de verdad: el Planner a 50 y 400 tokens, y qué presupuesto perdió cada uno.

Soporte de editor

hird-lsp es un servidor de Protocolo de Servidor de Lenguaje sobre el front end del compilador, hablando stdio: diagnósticos al abrir y guardar, hover con tipos inferidos y filas de efectos, ir-a-definición para declaraciones de nivel superior. Apunta cualquier cliente LSP al binario, sin argumentos. tree-sitter-hird/ es una gramática tree-sitter para la superficie v0.1, con consultas de resaltado, sangría y plegado, construida por el flake como salida de paquete. docs/editor-setup.md tiene la configuración de cliente (incluido Neovim, con y sin nix), el bucle de desarrollo de la gramática y las limitaciones de v0.1.

Estructura del repositorio

  • crates/ — el espacio de trabajo del compilador Rust (lexer, parser, verificador, IR, generación de código, CLI, servidores LSP y MCP).
  • tree-sitter-hird/ — la gramática tree-sitter y las consultas de editor.
  • runtime/ — la biblioteca de soporte de runtime Erlang escrita a mano (despacho de herramientas, sumidero de auditoría, registro de manejadores).
  • demo/ — los programas de demostración v0.1.
  • conformance/ — archivos dorados para el formato de cable del registro de auditoría.
  • docs/ — especificaciones normativas (gramática, modelo de errores, formato de cable de efectos de herramientas, grafo de efectos), las garantías del flujo de auditoría y la configuración del editor.
  • phrasebook.md — referencia densa de sintaxis de superficie.
  • DECISIONS.md — registros de decisiones de arquitectura.
  • .beads/README.md — el rastreador de problemas y la hoja de ruta, impulsados por bd.

Desarrollo

MSRV es Rust 1.97 (edición 2024). Antes de enviar cambios:

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

Las pruebas dependientes de BEAM se omiten a sí mismas cuando erlc no está en PATH.

CONTRIBUTING.md tiene el resto: el shell de desarrollo, las verificaciones que CI ejecuta más allá de esas tres, qué significa "terminado" y cómo informar un error o presentar un problema desde fuera del repositorio.

Licencia

Licenciado bajo cualquiera de

a tu elección.

A menos que declares explícitamente lo contrario, cualquier contribución enviada intencionalmente para su inclusión en el trabajo por ti, según lo definido en la licencia Apache-2.0, se licenciará doblemente como se indicó anteriormente, sin términos o condiciones adicionales.