nervネルフ

Swapping a program

A role is what a program is for; which program fills it is one line of configuration. balthasar, casper and melchior are the defaults, not the only choices.

Three roles

magi does not know that its memory is called balthasar. It knows the memory role is filled by whatever magi.memory names, and that whatever fills it answers the role’s verbs. Change the name and the program changes — nothing else does.

rolenamed bydefaultcore verbsunfilled
memorymagi.memorybalthasarobserve replay sessionsmagi refuses to start: this is the store
toolsmagi.toolscaspertools runan ordinary session with the three builtin tools
modelmagi.melchiormelchiormodels askno model to ask
magi.memory = "remembrance"   -- a program on $PATH
magi.tools  = "workbench"
magi.model is the model, not the program. It named the model before roles existed, so the model role is named by magi.melchior, which has always meant the program. A table there is still that program’s settings; only a string names it.

Core, and everything else

if a program refuses it
coreit cannot fill the role. The gate fails, and magi doctor says so.
extensionmagi carries on with less — a model tool not declared, no compaction, an outcome loop that records nothing.

An extension is refused, not omitted: a program that does not do plan answers plan with a refusal in the reply shape, like any other. Silence is the one thing that is not allowed. The line was drawn from what magi actually calls, not from what any implementation happens to offer — see the memory verbs.

sessions is core because it was measured to be. It was an extension in the first draft. A memory layer written to that draft was pointed at magi with sessions refused, and --resume came back empty at exit 0, saying nothing — magi asks it first, to find the newest run, before it can replay one. Running a second program against the contract is how the line got drawn in the right place.

Before you name one

Ask the binary, not its source. The role gate lists the core verbs a program advertises and fails on any it does not:

$ scripts/gate-role.sh tools ./workbench
gate-role: workbench as tools
  tools          core       answered
  run            core       answered
  extensions                2 of 2

gate-role: workbench fills tools.

And magi doctor asks the same of whatever your configuration names, before a session depends on it. With magi.memory = "casper", which cannot:

roles
  memory   casper — ~/.local/bin/casper — cannot fill memory: it answers no observe, replay, sessions
  tools    casper — ~/.local/bin/casper — 13 tools
  model    melchior — ~/.local/bin/melchior — 449 models

Two that exist to prove it

magi’s repository carries a second implementation of two of the roles, each one file of Rust with no dependencies, written from the contract rather than from magi’s source. Both run in magi’s own test suite against real sessions: one records a conversation and resumes it, the other is offered to the model, called, and answers.

fillsanswers
examples/remembrancememorythe family floor and the three core verbs, on a socket; refuses every extension
examples/workbenchtoolstools and run, one exec per call, and one tool that reverses a string
rustc examples/workbench/workbench.rs -O -o ~/.local/bin/workbench

What a tools program is told

Its settings come from the table named after the program itself, and ride on every spawn — there is no process alive between calls to send them to once.

magi.tools            = "workbench"
magi.workbench        = { quiet = true }   -- arrives as JSON in MAGI_TOOLS_CONFIGURE
magi.workbench_sha256 = "…"               -- the bytes it must hash to, from magi doctor

For casper that is magi.casper and magi.casper_sha256, which is why a configuration written before roles existed still works: the default program’s table has the name it always had. The same value is also set as CASPER_CONFIGURE, which is what casper reads.

Only your own configuration names one

A role’s program is started every turn with the session’s authority, which is more than a declared tool — and a project file is already refused a tool. So a .magi.lua that names a program for any role stops the session with a reason, rather than running what the checkout shipped beside itself. A project may still tune a sibling through its settings table; what is privileged is the name.

Writing one

Answer the family floor — verbs and client, in the reply shape — and the role’s core. Refuse the rest by name. There is no registration and no library to link: a library shared between these programs is the dependency the whole arrangement exists to prevent. The contract is ROLES.md, beside FAMILY.md in each of the four repositories.