Modules and overlays
Programs load other programs with use("name@vN"). Resolution is host-mediated, version-exact and recorded, so the trace pins the exact bytes that ran — even after the program in the space has moved on. A repo is a folder of programs published to a space; other spaces join it read-only and address it by an overlay alias — use("agent:llm@v1") names a program in the repo aliased agent.
use() versus import
import x |
use("x@vN") |
|
|---|---|---|
| source | bundled into the kernel | an object in a space |
| version | fixed at kernel build | resolved per call, cached by marker |
| determinism | by construction | recorded (module.resolve effect) |
| dependency resolution | global | bound to the owning space |
import is the kernel's frozen world — the stdlib allowlist, same bytes on every machine, nothing for the trace to record. use() loads code whose answer can change between runs, machines and deploys; under the isolation principle that is an effect. There is deliberately no import-hook sugar: an innocent-looking import must never perform a failable, space-dependent effect.
ws = use("webSearch@v1") # the working space
ws = use("agent:webSearch@v1") # overlay alias from anybao.toml, strict
ws = use("<spaceId>:webSearch@v1") # explicit space, strict
Resolution order
name@vN→ the working space. Never an overlay. A copy in your own space deliberately shadows shipped code.alias:name@vN→ that overlay's space, strict. Aliases come from[overlays]inanybao.toml.spaceId:name@vN→ strict.- Transitive: an unqualified
use()inside a loaded module resolves in the module's defining space. Every loaded module gets ausebound to its owner, so an overlay program is self-contained and never silently pulls dependencies from the consumer's space.
Cross-repo dependencies are therefore alias-qualified in the source (use("agent:llm@v1")). An unqualified miss answers with a hint naming the configured aliases.
What a load records
{"effect": "module.resolve", "cell": "toolu_01a",
"input": {"spec": "webSearch@v1", "frm": null},
"output": {"spaceId": "…", "objectId": "…",
"marker": 4711,
"sourceHash": "sha256:…",
"source": "\"\"\"Grounded web search …",
"cache": "miss"},
"meta": {"class": "read", "mocked": false, "durMs": 12}}
The output carries the source itself (spilled to a blob reference when it exceeds the 64 KB threshold), not only its hash: strict replay returns recorded outputs instead of fetching, so a trace replays bit-exact after the program was edited or deleted. frm names the requesting module — null for cell code — so the whole import tree reconstructs from the log.
The marker is the change counter of the program's source record. Every use() resolves the spec again; the kernel keeps loaded modules keyed (objectId, marker), so an unchanged program reuses its module and an edit is live on the very next use().
Repos and anybao.toml
addr = "http://127.0.0.1:7001" # the any server
[agent]
space = "bao" # working space: chat, memory, your own programs
name = "bao"
control_port = 7010
[overlays] # alias → space id (never a name)
agent = { space = "bafy…", invite = "<guest token>" } # invite ⇒ join on boot
std = "bafy…"
[paths]
traces = "traces"
[config] # agent config seeds, flat dotted keys
"llm.tier.codegen" = { provider = "anthropic", model = "…" }
Host precedence: built-in defaults < anybao.toml < CLI flag. Unknown keys are a parse error. The [config] table is written through to the working space's agent config at serve start, where config.get reads it. Secrets never appear here — see Credentials.
A repo folder is self-describing: <src>/programs/, <src>/skills/, and a README.md whose content becomes the overlay's description. The agent overlay is special only in that the agent loop injects its programs and skills into the initial context; it is deployed like any other. With no agent entry the alias binds the working space itself, so a config-less setup behaves like a single-space install.
Publishing
anyrt deploy --source repos/_agent --target agent # alias from [overlays]
anyrt deploy --source repos/_agent --target <spaceId> --addr http://127.0.0.1:7002
Deploy is hash-gated and upsert-only: unchanged programs are skipped, and it ensures the program type in the target space, then writes each program's source record plus the derived name / version / any_tool / summary properties after validating the docstring budget and tool shape. --target is strict — it never creates a space. A running agent picks the change up on its next use(); no restart.
Joining an overlay
An overlay published by another account must be joined before it syncs to this device. With an invite in the config, the runtime sends POST /v1/spaces/join {inviteToken} on boot and proceeds; only program resolution waits for the sync. The token is normally the space's guest key — any invite guest-key <spaceId> — which grants read-only membership with no approval step, so a repo's authenticity is enforced by the CRDT ACL: only the publisher can write.
# on the publishing account's server
REPO=$(curl -s -X POST http://127.0.0.1:7002/v1/spaces -d '{"name":"my-repo"}' | jq -r .id)
anyrt deploy --addr http://127.0.0.1:7002 --source repos/_agent --target $REPO
TOKEN=$(any --addr 127.0.0.1:7002 invite guest-key $REPO | jq -r .inviteToken)
Why it matters. An overlay is a package registry with no registry service. Publisher identity is the space's ACL, distribution is sync, and a consumer's device holds the full source of everything it can run — offline, encrypted, and pinned per run by content hash.
Two run surfaces, never mixed
| Surface | Resolves from |
|---|---|
anyrt serve and its control API's POST /run |
spaces only — working space + overlay aliases; it structurally cannot read program source from disk |
anyrt run <spec> |
the --programs directory only — default in the anyrt CLI reference (flat file wins over folder on a tie) |
anyrt run --from-space <space> |
serve's resolver, one-shot — tests the deployed source |
A broker has either the local directory or the space resolver, so every module.resolve record in one trace answers from one world.