This guide starts from a fresh machine and ends with a running λ-間 world that you can enter from zion, edit, fork, and reset.
λ-間 is a seed world for ma-runtime. It is similar in spirit to
lambdamoo-core: a small functional base that gives a new world rooms, exits,
avatars, and a root actor. You are expected to change it. The shipped world is
not sacred; it is a starting point for your own runtime.
For normative world protocol behaviour (routing split, room/enter contract, actor verbs, ownership and parent-authority rules), use REFERENCE.md.
You need three things:
- Kubo / IPFS stores actor source, Wasm, runtime manifests, DID documents, and room state as content-addressed IPFS/IPLD data.
maruns your actor runtime, loads the λ-間 bootstrap, and serves zion locally.- λ-間 is the seed actor set and bootstrap YAML for a simple programmable world.
Download Kubo/IPFS:
- IPFS Desktop, easiest for new principals: https://docs.ipfs.tech/install/ipfs-desktop/
- Kubo command-line install: https://docs.ipfs.tech/install/command-line/
Download ma:
ma-runtimereleases: https://github.com/bahner/ma-runtime/releases
Kubo must be running before you build or bootstrap λ-間. The make process uses
ipfs add to publish actor files and Wasm to your local node. The ma runtime
also uses Kubo to publish the runtime manifest and DID documents. If Kubo is not
running, the build may fail, and ma --bootstrap / ma --gen-root-cid cannot
publish the world.
Install Rust and the Wasm target. The λ-間 bootstrap includes a generic ma-scheme actor compiled to WebAssembly.
rustup target add wasm32-unknown-unknownMake sure these commands exist:
ma --help
ipfs --versionIf you installed IPFS Desktop, the ipfs command may already be available. If
not, install Kubo from the command-line link above or add the bundled ipfs
binary to your PATH.
From the lambda-ma repository:
make clean
makeThis does four things:
- Builds
scheme-actor/actor.wasm. - Publishes
scheme-actor/actor.wasmto IPFS. - Publishes
scheme-actor/stdlib.maandactors/*.mato IPFS. - Writes the generated bootstrap file to
dist/lambda-ma.yaml.
Check that the generated YAML has no unresolved placeholders:
make checkYou can inspect the generated file:
less dist/lambda-ma.yamlDo not edit dist/lambda-ma.yaml by hand. Edit lambda-ma.template.yaml,
actors/*.ma, or scheme-actor/, then run make again.
This is the shortest path when you just want to run the world now:
ma --bootstrap dist/lambda-ma.yaml--bootstrap publishes the manifest described by dist/lambda-ma.yaml, uses
the resulting root CID for this runtime start, then continues running ma.
If you copy dist/lambda-ma.yaml somewhere else, pass that path instead:
ma --bootstrap lambda-ma.yamlThe flag is --bootstrap, not --bootstrapo.
When ma is running, open zion from the local runtime:
Create or unlock your zion identity, then claim your local runtime if you have not already done so:
.my.ma:claim
Then enter the λ-間 world:
.ma!discover
@ma/config/root: @ma#root
.enter @ma
Use this when you want a stable starting point that can be reused without copying the YAML file around.
make root-cidThis is a convenience wrapper around:
ma --gen-root-cid dist/lambda-ma.yamlIt publishes the λ-間 bootstrap to IPFS, prints one CID, and exits. That CID is the root of the generated runtime manifest.
Example:
bafyreib...
Start ma from that root CID:
ma --root-cid bafyreib...This resets the runtime head to that world root for the start. The runtime then
persists the selected root in its config as it runs. If you later want to reset
back to the same fresh λ-間 starting point, stop ma and start it again with the
same root CID:
ma --root-cid bafyreib...This is useful for experiments. You can build rooms, break things, learn, and then return to a known clean seed world.
Keep the printed root CID somewhere convenient, for example in a notes file:
lambda-ma fresh root: bafyreib...
After zion has entered the world, you are in #construct, the initial room.
Start with:
look
help
help here
Claim the room so you can edit it:
claim
Set a name and description. Colon-prefixed commands go directly to the focused room instead of through your avatar:
:prop name The Workshop
:prop description A bright room with cables on the floor and a half-built door in the north wall.
look
Create a new room to the north:
dig north to The Gallery
go north
look
The new room is owned by you automatically. Change it too:
:prop description A long quiet gallery. Every wall is waiting for a better idea.
Move back:
go south
Quick regression check for room presence rendering:
look
Expected shape in output:
- An
Occupants:line that includes live room occupants, including avatar presence and agents such asrmsafter those agents have entered the room. - A separate
Things:line for room-local things and containers.
Use who? for avatars only. Use occupants? for avatars and agents.
If look only shows Things: none. or misses expected occupants while actors
are clearly active in the room, the room behaviour is stale. Rebuild and
re-bootstrap (see troubleshooting below).
Useful room-building commands:
help show avatar/avatar-mediated commands
help here show what this room says is possible
here? show where you are
claim claim an unowned room
:owner? show the current room owner
owner did:ma:<target> transfer the room to another bare DID through your avatar
make thing <.my.things.lamp
create a thing through your avatar; the init string sets owner and location
did? [kind] lamp show the DID for a visible occupant, thing, or exit
:owner? lamp show who owns a visible occupant, thing, or exit
dig north to Name create an exit and a new room
fill north remove the north exit
look north inspect the north exit
lock north lock the north exit
unlock north unlock the north exit
:exit north :message traveller You step through the green door.
:exit north :message blocked The green door is locked.
:exit north :message source Someone slips through the green door.
:dids? owner lists visible occupants, things, and exits with DIDs
:remove Alice owner removes Alice if that nick is unique here
:remove did:ma:...#x owner removes an exact occupant DID-URL
:prop name Name set the focused room name
:prop description ... set the focused room description
:prop description reset the focused room description
prop lamp name Lantern set a visible or carried actor property through your avatar
name lamp as Lantern with nick lantern
set a visible or carried actor name and optional nick
describe lamp as A bright brass lantern.
set a visible or carried actor description
make is avatar-owned. Zion only expands the command text, and the current room
is not the factory. The init text is the new actor's creation payload; set
owner and parent there. Use parent equal to the avatar DID-URL when the
avatar is holding the thing, or the room DID-URL when it should appear in the
room immediately. A convenient flow is to edit the init text with
.my.things.lamp!edit, then run make thing <.my.things.lamp.
If a newly created actor ends up with no owner and is not visible in your current room, claim it directly by DID-URL:
@runtime#thing:claim
In focus shorthand, the equivalent is:
claim did:ma:...#thing:
The trailing : marks the actor target token; any following argument is passed
as the claim secret. The room command claim (without a target token) still
claims the current room only.
Digging an existing direction replaces that exit. Filling a direction removes
the exit from the current room and asks the exit actor to terminate itself; it
does not delete the target room. Exits are inspectable objects: look north
shows the exit name, description, owner, source room, target room, direction,
and lock state. Locked exits stay visible, but go north prints the blocked
message instead of traversing. To link to an existing room instead of creating a
new one, use a room DID-URL:
dig mirror to @sky#FQWJA5V3
Runtime-local fragments may exist in old state, but actor messages use full DID
or DID-URL values. A full did:ma:...#room target may point at another runtime.
The target room must run compatible room code and confirm that you own it before
the source room creates the exit. The source room first sends :ping to the target
room; after :pong, it asks for ownership authorisation.
The important rule is simple:
- Commands without a leading colon, such as
look,go north,say hello,dig north,prop lamp name Lantern,name lamp as Lantern, anddescribe lamp as ..., are avatar-mediated commands. The property commands resolve a carried or visible actor and forward its ordinary:propmethod; that actor still enforces owner authority. - Commands with a leading colon, such as
:prop name ...and:help, are sent directly to the focused room.
When you create your own /ma/scheme/agent/0.0.1 entity, keep init explicit in
the creation-time init code you send when creating the entity (init: in
bootstrap YAML, or inline with (...) code for creation flows). Do not rely on
hidden helper init functions.
Recommended pattern:
(begin
(set-init-prop! "name" "My Agent")
(set-init-prop! "nick" "myagent")
(set-init-prop! "description" "A custom movable agent.")
(enter (string-append (ma-get-config-key "runtime") "#construct"))
(ma-save-state!))Why this works well:
-
set-init-prop!explicitly seeds initial state without publishing an intermediate parent-facing ctx. Use ordinaryset-prop!after init so ctx attributes propagate to the current parent by default. -
enterrecords the target as pending, sends room:enter, and commits the newparentonly after a valid room:ctxack. -
:enterwith the agent's node ctx informs the room how to present the occupant. -
:moveuses ordinary room exits. It asks the current room to choose one of the exits it knows with practical, non-security random choice. The room and exit use implementation-local movement traffic; the moving actor then performs the target-room:enteritself, so the target room remains responsible for admission. -
Full init logic stays in the code principals provide at creation time, so principals can adjust it safely when creating entities. Agents can use exits, but they do not expose
:dig.
New actor-to-actor workflows should handle the current ctx and send the next ctx message directly. Do not persist a command, resolver result, transfer payload, or other intermediate step for later replay. Existing movement-specific state is not a pattern for parent/child, inventory, take, drop, or similar workflows; those flows use their explicit ctx handshakes.
Assume that any actor message or reply may have been lost. A caller may repeat a request because it cannot know what arrived. Handle every valid retry idempotently and return the same current authoritative ctx even when the requested parent or value is already committed. Do not answer retries with "already done", and do not add delivery logs, deduplication state, pending commands, or retry counters. Recompute the response from current actor state.
Resolve visible names, nicks, directions, and aliases only at the command boundary. After a successful lookup, discard the lookup term and use the canonical full actor DID or DID-URL for messages, authority checks, membership changes, cleanup, and persistent keys.
Sometimes you want one room to have a special method or two without changing
actors/room.ma, without using CRUD, and without registering a new kind. λ-間
supports that for any Scheme-backed actor; the example below customises a room
from inside that room.
The normal room behaviour still comes from /ma/room/0.0.1. Your extra code is
added only to that room.
Go to the room, claim it if you have not already, and open the behaviour editor:
claim
:behaviour!edit
That opens the room's current per-entity behaviour source if one is already set,
or a blank editor if the room has no custom code yet. Publishing requires the
actor's owner prop to match you. Add Scheme code and press Publish.
For example:
(set-cmd-method! :duck
(lambda (args msg)
(ma-send! (msg-from msg)
(list :print "A duck waddles through the room. It looks busy."))))Use set-cmd-method! for command verbs that present something in-world,
such as looking, speaking, emoting, moving, or noticing a local duck. Use
set-rpc-method! for dry metadata/query/config verbs that should answer with
ma-reply!. Actors also provide standard introspection such as :rpcs?,
:cmds?, :api?, and :metas? for explicit runtime maintenance helpers.
Zion publishes the editor contents as text/plain, sends the returned
/ipfs/<cid> to the actor's :behaviour method, and the runtime reloads only
that actor. Call the new method directly:
:duck
This does not create a new kind. The room is still a normal λ-間 room with
look, say, go, claim, owner, dig, :prop, and the rest of the room
behaviour. The /ipfs/<cid> is just an extra behaviour layer for that one room,
loaded after the base room code.
Because that extra layer is evaluated after the base room code, it can extend
the room's own help text without changing actors/room.ma. Save the original
room-help-text, redefine it, and keep using the existing :help method:
(define base-room-help-text room-help-text)
(define (room-help-text)
(string-append
(base-room-help-text)
"\n :duck notice the local duck"))Now both :help and help here include the local room affordance.
To see the current per-actor behaviour reference:
:behaviour
You can do the same thing manually: write a duck.ma file, publish it with
ipfs add --quieter duck.ma, then run :behaviour /ipfs/<cid> from the focused
actor.
The editor command is just the friendly path over that same mechanism.
You can also compose code files. If duck-room.ma contains this:
(ma-include-ipfs #/ipfs/bafkreiduck...)
(set-cmd-method! :pond
(lambda (args msg)
(ma-send! (msg-from msg)
(list :print "The pond is still. Something under it is considering you."))))then publish duck-room.ma and use its CID:
ipfs add --quieter duck-room.ma:behaviour /ipfs/<duck-room-cid>
The include form is named ma-include-ipfs, and in ordinary custom room code
the method helper is usually set-cmd-method! or set-rpc-method!.
For tiny experiments, inline code also works:
dig west to Tiny Duck Room with (set-cmd-method! :duck (lambda (args msg) (ma-send! (msg-from msg) (list :print "quack"))))
Inline with (...) code is delivered as creation-time init code. It is useful
for quick live experiments, but for room code you want to keep, publish a file
and use :behaviour /ipfs/<cid> after the room exists.
You can also attach code while digging a new room:
dig east to Duck Room with /ipfs/bafkreiduck...
go east
:duck
That stores the /ipfs/<cid> as the new room's behaviour reference immediately.
One important limitation: with ... only applies when a new room is created. If
the target after to is an existing room, λ-間 links to that room and ignores
custom code. To add code to an already-existing room, enter it and use
:behaviour!edit.
The kind definitions live in kinds/, and the actor behaviours live in
actors/. Shared Scheme layers live in scheme-actor/:
actors/root.ma
actors/avatar.ma
actors/room.ma
actors/exit.ma
scheme-actor/actor.ma
scheme-actor/state.ma
scheme-actor/node.ma
Use /ma/node/0.0.1 as the base for a stateful actor that participates in the
parent hierarchy. Parenting is not automatically physical containment; the
concrete behaviour decides whether and how child ctx is displayed. Extend
/ma/scheme/state/0.0.1 directly for a stateful utility actor that should stay
outside the hierarchy.
To change the default room behaviour, edit actors/room.ma. To change movement
or avatar-mediated commands, start with actors/avatar.ma and actors/room.ma. To change
world entry and zion context, start with actors/root.ma.
To change a kind descriptor, edit the matching kinds/*.yaml file. make
generates the runtime.kinds bootstrap map from those files; keep example
descriptors with placeholder CIDs out of BOOTSTRAP_KIND_FILES until they are
ready to publish.
After editing, regenerate and test the bootstrap:
make clean
make
make checkThen choose a bootstrap workflow again:
ma --bootstrap dist/lambda-ma.yamlor:
make root-cid
ma --root-cid <printed-cid>If you are only updating kinds or behaviours for an existing runtime, generate
and apply a kinds CID instead. This patches the runtime's kind registry without
replacing existing entities or their state. Apply the complete tree when a new
base kind such as /ma/node/0.0.1 and its derived kinds are introduced:
make kinds-cidApply it live from zion or another CRUD client:
@runtime/kinds: <printed-kinds-cid>
or apply it on the next daemon start:
ma --kinds-cid <printed-kinds-cid>The source files are the important artefacts:
lambda-ma.template.yaml
actors/*.ma
scheme-actor/src/*.rs
scheme-actor/stdlib.ma
Makefile
Do not commit generated output such as dist/, scheme-actor/target/, or
scheme-actor/actor.wasm.
To share a reproducible seed world, share the source branch and the generated
root CID. Someone with Kubo and ma can start from that CID directly:
ma --root-cid <your-root-cid>To let them rebuild it themselves, tell them to run:
make
make root-cidInstall Kubo from https://docs.ipfs.tech/install/command-line/ or make sure
IPFS Desktop's ipfs binary is on your PATH.
Start IPFS Desktop or the Kubo daemon, then try again. ma needs the local Kubo
RPC API, normally at http://127.0.0.1:5001, to publish the bootstrap manifest.
Check that Kubo is running:
ipfs idIf ipfs id fails, fix Kubo first. λ-間 cannot generate a complete bootstrap
without publishing actor files to IPFS.
Run:
.ma!discover
@ma/config/root: @ma#root
.enter @ma
The /config/root value tells zion which runtime actor is the world root. For
λ-間 it should be @ma#root.
Regenerate and re-bootstrap:
make clean
make
ma --bootstrap dist/lambda-ma.yamlIf you are using the root CID workflow, remember that old CIDs are immutable. After source changes, generate a new root CID:
make root-cid
ma --root-cid <new-cid>