workbooks docs

Toolkits

A toolkit is the unit you author, build, and share: a :toolkit:-tagged Org node (the manifest, the front door) + a SKILL_DIR of progressive-disclosure recipes + zero-or-more WASM artifacts (the executable half). The manifest's #+EXEC field selects the artifact's invocation contract; #+TRUST the consent posture; #+CAPS the Dock capabilities the artifact may reach (gated by Policy).

Authoritative specs in-repo:

  • runtime/docs/TOOLKIT-MANIFEST.org — the manifest field list + per-shape requirements (the contract).

  • runtime/docs/TOOLKITS-V3.org — the system model (discovery, build, security, EXEC modes).

  • toolkits/AUTHORING.org — the operational skill-authoring depth/breadth standard.

Conceptual orientation (do not duplicate here): What is a toolkit, The six shapes, The Dock & capabilities.

Manifest fields

One manifest.org per toolkit at toolkits/<name>/manifest.org. Org keywords (#+KEY:) at file level; some are mirrored into the :toolkit: node's :PROPERTIES: drawer (the verifier asserts they match).

Parsed by parse_descriptor/1 (toolkits.ex:487). Discovery reads them via kw/2 (file keyword) and drawer/2 (drawer key) (toolkits.ex:531-545).

KeywordMeaningRequired for:SRC: (toolkits.ex)
#+TITLEhuman titleall
#+TOOLKITslug = dir name = :ID: (whitespace-free)allview/1 :ID:
#+VERSIONsemver of this wrapperall
#+STATUSstable / experimental / deprecatedallview/1 (h.props.STATUS)
#+TAGLINEone sentence — when to reach for itallinjectiontext/2
#+EXECinvocation contract (see §EXEC shapes)built shapes¹parsedescriptor :exec
#+TRUSTfirst-party (default) / third-partyall (defaults)parsedescriptor :trust
#+CAPSspace-list of Dock caps the artifact may usenon-pure²parsedescriptor :caps
#+BUILD_LANGrust / zig / c / go / js / ts / sveltebuilt shapesparsedescriptor :buildlang
#+BUILD_SRCsource spec (see §BUILDSRC)built shapesparsebuild_src/1
#+CLI_BINregistered command name (the run-command key)command / posixparsedescriptor :clibin
#+CLI_VERSION_RANGEupstream CLI version range the skills assumecommand/posix (opt)
#+REQUIRESextra CLIs/runtimes the skills assume (e.g. node>=20)opt
#+ARG_MODEargv (default) / stdin1 (first stdin line = arg)command (opt)argmode/1 (l.523)
#+SHA256content hash — for wasm: / archive: prebuilt fetcheswasm/archiveparsedescriptor :sha256
#+WASM_PATHwasm path inside an archive: bundlearchive (opt)parsedescriptor :wasmpath
#+PREOPENpreopened dir for an archive: toolkitarchive (opt)parsedescriptor :preopen
#+AUTHOR_DIDauthor did:key — provenance for signingthird-partyparsedescriptor :authordid
#+SIGNATUREEd25519 over the canonical manifest (registry tier)third-party (registry)parsedescriptor :signature

¹ A manifest with NO #+EXEC is a discovery-only toolkit: skills are discoverable and readable, but there is no built/invoked artifact. The verifier reports exec: none declared (discovery-only toolkit) (toolkits.ex:950). Most shipped toolkits today are discovery-only — their skills are good; their execution contract is the legacy native-CLI assumption, not yet re-pointed at the clean-room command shape.

² "non-pure" = anything reaching a host power. A pure compute+stdio command needs no #+CAPS.

Drawer mirroring

The :toolkit: node carries a :PROPERTIES: drawer whose :ID: / :CLI_BIN: / :STATUS: MUST equal the matching #+ keyword. CLI_BIN is read from EITHER the keyword OR the drawer (cli_bin: kw(body, "CLI_BIN") || drawer(body, "CLI_BIN")).

The discovered view (view/1, toolkits.ex:1073) exposes exactly: id, title, cli (CLI_BIN), status, skill_dir (SKILL_DIR).

EXEC shapes

The #+EXEC keyword selects the artifact's invocation contract. Six shapes. The verifier (exec_checks/1) recognizes exactly these; any other value fails exec: unknown mode (toolkits.ex:984).

#+EXECABIConsumerWIT?Build?Maturity
commandargv + stdin → stdout + exitagent, workflownoyesships-today
posixnative PATH binary in containeragent, workflownonoships-today
task:role bash recipe (toolkit run)agentnonopartial (see below)
federationOQL data-source + sync daemonhost, OQLnomixedships-today
componentWIT-typed, in-process callcomponent, workflowyesyespartial
kernelbytes→bytes, instantiate-once-loopthe fabricyesyespartial

command — the stdio leaf (default, most portable)

A CLI compiled to a WASM module. stdio IS the universal API: run-command(name, argv, stdin) -> stdout. Registered in CommandRegistry under #+CLI_BIN. Verify: bin registered, OR a buildable #+BUILD_SRC (crate: / path:) is present (toolkits.ex:952-962).

Built-in commands are reserved and cannot be shadowed by a dynamic registration: upper, jq, grep, wbox (@builtins, command_registry.ex:39-64; @reserved, l.70). register/3 rejects :reserved_name (command_registry.ex:167).

Name charset: ^[A-Za-z0-9_.-]+$ (@name_re, command_registry.ex:79). Max 4096 dynamic entries (@max_dynamic, l.84). ARG_MODE: :argv (default) or :stdin1 (first stdin line = arg; jq and grep use :stdin1).

posix — the native escape hatch

For ffmpeg-class CLIs that don't compile clean to WASI. Runs as a native PATH binary under the posix Policy profile inside the one deploy container. Verify: #+CLI_BIN resolves via System.find_executable/1 (toolkits.ex:964-970).

task — recipe-only, no single CLI

Ships :role task bash blocks run via wbx toolkit run <tk> <task> -- …. Verify always passes structurally (toolkits.ex:972). HOWEVER: native :role bash execution is BANNED (wb-9ja). exec_allowed? is hardcoded false; run_bash is a no-op (toolkits.ex:1062). So a task block does not execute today — ship the CLI as a WASM command instead.

federation — the three-face SaaS connector

Three wired faces: READ (SELECT … FROM <entity> routes to a data-source plugin), SOURCE (any REST/JSON via Workbooks.DataSource.Http with #+URL/#+ROWS_PATH/#+ENV_KEYS, or a custom query/3 module — Linear/Asana specialize), SYNC (Workbooks.Plugin.Sync mirrors a source into :task: nodes on an interval). Verify passes structurally (toolkits.ex:973).

component — the typed in-process artifact (absorbs "extension")

A WIT-typed component on engine.wit (or a declared #+WIT world). Typed I/O instead of stringly stdio; called in-process via Instance, not spawned per call. A third-party component carries #+TRUST: third-party and the deployer grants its #+CAPS. Verify always reports OK structurally (toolkits.ex:981).

kernel — the hot-loop artifact (the media/render class)

bytes → bytes, instantiated ONCE and called many times in a tight loop. The fabric owns the loop; the kernel is a pure transform. Verify checks the kernel is registered in Workbooks.KernelRegistry (toolkits.ex:975-979). Build supports ONLY #+BUILD_LANG: c today (package_manager via toolkits.ex:836-837): "=#+EXEC: kernel — only #+BUILDLANG: c is supported today=".

TRUST postures

Orthogonal to #+EXEC. Governs whether the deployer must consent before the toolkit's #+CAPS are granted. Default first-party (parse_descriptor, toolkits.ex:489).

#+TRUSTConsentExtra fields required
first-partyyours; runs under the profile's default caps; no extra consentnone
third-partydeployer reviews + grants #+CAPS explicitly; sha-pin install#+AUTHOR_DID + #+SIGNATURE (registry tier)

Invariant (both postures, all tiers): an ungranted cap is un-importable, so un-callable. Trust changes the consent UX, never the enforcement — enforcement is always the Policy-gated Dock.

Capability grants (#+CAPS)

#+CAPS is a space-list of Dock capabilities. Each maps to a host import bound by Instance.Imports.for_caps/4 (imports.ex:26), gated by Policy. A component importing a cap not in the granted profile fails to instantiate.

The cap → Dock import bindings actually wired in imports.ex (everything else in the Policy cap set is granted-but-not-bound-here, or bound elsewhere):

#+CAPS nameDock import boundhost:SRC: (imports.ex)
(none)session-info only (always on)read-only idsl.27
vfsvfs-queryInstance SQLite VFSl.33-34
commandsrun-commandCommandRegistry (composition)l.37-38
llmllm-completeWorkbooks.Llm (host holds key)l.41-42
browsebrowse-fetchWorkbooks.Browse (Route B)l.47-48
parallelrun-command-manyWorkbooks.Fabric (fan-out)l.56-57

An unrecognized cap is a no-op in for_caps (add(_other, …) returns the acc unchanged, imports.ex:59) — it binds no import, so it grants nothing.

The full Policy cap set & profiles

#+CAPS are validated against the Policy cap universe by cap_checks/1 (toolkits.ex:930-948): every declared cap must be grantable by SOME profile, and at least one profile must grant the WHOLE set.

Profiles (@profiles, policy.ex:28-37):

ProfileMemoryTimeoutCaps granted
compute64 MiB5 svfs
minimal64 MiB5 svfs commands exec kv secrets queue tcp udp tls
network128 MiB30 sminimal + net llm browse
posix256 MiB60 snetwork + posix parallel

Fail-closed: an unknown/typo'd profile resolves to compute (vfs-only), NEVER the broader minimal (fetch/1, policy.ex:71). High-level network (wasi:http + inherit_network + DNS) is gated by allow_http?/1 — true ONLY if the profile grants net or browse (policy.ex:64-67). minimal grants raw-socket caps (tcp/udp/tls) but NOT wasi:http egress.

Caps in the Policy universe but NOT bound as a Dock import in imports.ex (exec kv secrets queue tcp udp tls posix): granted by profile, enforced/bound by other host seams — not the for_caps component-import surface. frames (the shared-frame arena for kernels) is proposed, not in the Policy set today.

BUILDSRC — source specs

#+BUILD_SRC is parsed into a tagged tuple by parse_build_src/1 (toolkits.ex:507-521). Recognized prefixes:

PrefixTupleBuild support:SRC: (toolkits.ex)
crate:<n>{:crate, n}yes — buildand_registercrate (mrustc→clang in sandbox)dobuild_clause l.786
path:<dir>{:path, dir}yes — PackageManager.builddir + registerdobuild_clause l.790
wasm:<url>{:wasm, url}yes — fetch prebuilt wasm (#+SHA256 pinned)dobuild_clause l.842
archive:<url>{:archive, url}yes — fetch + extract bundle (#+WASM_PATH/#+PREOPEN)dobuild_clause l.858
git+<url>{:git, url}NOT yet — "use crate: or path:"dobuild_clause l.891
gobuild:<pkg>{:gobuild, p}go build lanedobuild_clause l.876
zigbuild:<rel>{:zigbuild, r}zig build lanedobuild_clause l.880
script:<rel>{:script, rel}REMOVED (wb-9ja) — native build scripts banneddobuild_clause l.884
(other){:unknown, s}error — unrecognizeddobuild_clause l.894

Build lanes (#+BUILD_LANG)

Every untrusted-source lane compiles ENTIRELY in the WASM sandbox — zero native execution. PackageManager.build_dir/2 dispatches by lang.

#+BUILD_LANGIn-sandbox lane:SRC: (packagemanager.ex)
rustmrustc.wasm → clang.wasm + std (pure-crate deps via dep pipeline)builddir l.152
cclang.wasm (all *.c under dir)builddir l.223 / buildc_dir l.230
zigzig1.wasm (.zig→C) → clang.wasmbuilddir l.277
go / tinygogo-yaegi / tinygo lanebuilddir l.197
js / tsbundlejob → Javy → wasm (ts: tsc-in-QuickJS first)builddir l.177
sveltesvelte compile in qjs-run.wasm → bundle → JS lanebuilddir l.296
(other){:error, {:unsupported_dir_lang, lang}}builddir l.306

Lane canon: untrusted source NEVER compiles or runs natively. Native tools that remain (wac for component compose) only operate on already-built, validated TRUSTED components — not on untrusted source (package_manager.ex:6-31).

The wbx toolkit CLI

The learn/build/run surface. Verbs (ToolkitVerb, cli/src/main.rs:145-172); dispatch at main.rs:326-336.

CommandDoes:SRC: (main.rs)
wbx toolkit listlist discoverable toolkits (id · status · tagline)l.327
wbx toolkit show <id> [skill]manifest + skill index, or one skill's recipel.328
wbx toolkit search <q>substring match across toolkits + skillsl.329
wbx toolkit verify <id>structural + cap + trust + exec contract checksl.330
wbx toolkit sign <id>sign a manifest (did:key provenance; req. third-party)l.331
wbx toolkit build <id> [which]in-sandbox compile + register the declared artifactl.332
wbx toolkit push <id> <dir>ship a toolkit dir onto the engine (deploy-the-toolkit)l.333
wbx toolkit import <source> [--as] [-o]package a Claude skill / markdown / folder as a toolkitl.334
wbx toolkit audit <dir> [--fix]re-run the wasm-compatibility audit on an imported dirl.335
wbx toolkit run <id> <task> -- <args>run a :role task recipe (gated: WB_TOOLKIT_EXEC=1)l.336

The engine-side implementations these verbs call: Workbooks.Toolkits.list_text, show_text, show_skill_text, search_text, verify_text, build_text, eval_text, run (toolkits.ex:116-360).

What verify checks

verify_text/2 (toolkits.ex:228) emits ✓/✗ for: manifest.org present; skills/overview.org present; per-EXEC contract (exec_checks); cap grantability (cap_checks); third-party trust fields (trust_checks). :role pre blocks are reported DISABLED, never run (native exec banned, toolkits.ex:252-254).

Discovery

One query: (tags :toolkit:) over the Context Tree (toolkits.ex:41-43). On disk: read every <root>/<name>/manifest.org (discover_dir, toolkits.ex:47-58). An :agent: node's :TOOLKITS: property lists the toolkits it may use, resolved in declared order; unknown names are dropped, never a crash (for_agent, toolkits.ex:68-74).

Discovery root: $WB_TOOLKITS_ROOT (honored ONLY if it names an existing dir — wb-sec finding #6), else first of toolkits/ | ../toolkits that exists (default_root, toolkits.ex:108-113). The root is an UNAUTHENTICATED, writable dir → toolkits there are untrusted supply-chain input; read surfaces are open, EXECUTION (verify pre / run / build) is separately gated (toolkits.ex:19-37).

Skills (progressive disclosure)

The manifest is the INDEX; skills are read on demand. The auto-injected agent prompt carries one line per declared toolkit + up to 8 skill names (injection_text/2, toolkits.ex:138-175). The agent then reads a skill body via wbx toolkit show <id> <skill>. The runtime resolves which toolkit; it never inlines the manual.

Slug safety: a skill slug must match ^[A-Za-z0-9._-]+$ and resolve strictly inside <dir>/skills/ — no .., no separators, no symlink escape (@slug_re + contained?, toolkits.ex:1018-1030).

The depth/breadth authoring standard (mandatory skill sections, the task tier, the overview.org requirement, the consistency contract) lives in toolkits/AUTHORING.org — see Author a toolkit for the how-to.

Limits (the structural walls)

What a toolkit artifact CANNOT be, per the runtime limits (TOOLKIT-MANIFEST.org §"What can and cannot be a toolkit"):

The pattern: stateless, bounded, single-shot or hot-loop transforms thrive; long-lived / threaded / huge-memory / GPU workloads do not.

See also