Running and debugging

SkillMonitoring & ops

Lets your agent run, activate and debug Weft programs correctly, with the CLI commands, timeouts and debugging steps.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Running and debugging skill

About this capability

Read when running, activating or debugging a program: the CLI command map, the daemon (never yours to restart), the build and run flow, trigger activation and the three deactivate modes, the infra lifecycle, sizing a command's timeout, journal inspection (executions, events, logs), and the debugging

What this skill tells your AI

The instructions your AI receives, as published by weavemindai/weft in tangle/claude-code/.claude/skills/weft-running/SKILL.md and read by ahel’s review.

The weft CLI is a thin client of [the daemon] plus the front end for building. [the daemon] is the dispatcher process that owns projects, executions, triggers, and infra, and listens on http://localhost:9999 (override: --dispatcher <url>, WEFT_DISPATCHER_URL, or [dispatcher] url in weft.toml). The user's install (setup.sh) starts it, once per machine. weft daemon start, stop and restart re-run that install, and a restart from a project has wiped shared keys before, so you never run them. A .env near the project auto-loads.

A [color] is one execution: a UUID minted when a run starts. Everything in it is journaled, node by node, with the values on the wires. Every command that takes a [color] also takes its first characters (weft events 3f2a), at least four, as long as they name a single run.

How you run a command

Every command you run cannot ask you anything. These are the ones that ask, with the answer already in them. While you are building, you type them as they are written here:

Instead oftype
weft resyncweft resync --mode wipe
weft deactivateweft deactivate --mode wipe
weft rm, weft clean, weft prunethe same with --yes, once the user has said yes
a long weft runweft run --detach

--mode wipe is the answer while you are building; hibernate and park are for a program people are using, and the three modes below say when. A command that asks anyway gets killed and reported with what it asked.

A command that changes the deployed code and the thing you do next go in ONE line, joined by &&: weft resync --mode wipe && curl .... Run them separately and a resync that failed leaves the next command talking to the old code, which reads as a bug in what you just wrote.

You never sit on a quiet command. Thirty seconds is the most you wait without looking. At thirty seconds you check whether the thing is still moving: new lines from a build, a changed weft status --json, a new line in weft daemon logs. Moving, you give it thirty more and look again; quiet for thirty seconds, it is stuck, and you get out and find out why. Nothing weft does while you are building is silent for that long when it is healthy, so silence is the finding. Reads (status, executions, events, logs, connect, describe-nodes) get fifteen seconds; weft run gets --detach and a check later; a dev server starts in the background. Check what unit your own tool wants before you type a number. Some take seconds and some take milliseconds, and getting it backwards is the difference between thirty seconds and two days.

Getting out is cheap and safe: Ctrl+C on the CLI (nothing weft was doing is left half done; the daemon finishes or rolls back on its own). Then read weft daemon logs, which is where a wait says what it is waiting on. The one wait that used to hide here was activate sitting behind a worker from an older build; it no longer waits by default (see --running-policy under the command map), and weft status reports an activation still in flight, which weft cancel-activate ends before you run the verb again. You raise a number only once you can say what is taking the extra time, and a run that is longer than its shape says is something you report.

If you want to wait on something long (a build, [the daemon] coming up, a run settling), start it detached and check its state between other steps instead of writing a loop: weft run --detach hands you the [color], then weft executions --json for a run and weft status --json for a build or [the daemon].

If you do write a loop, it is this line and no other, cap included:

timeout 30 bash -c 'until <check>; do sleep 5; done'

When the cap trips you read the state and say what it is waiting on. A run parked on a timer or a person never finishes on its own, so an uncapped until on one hangs until somebody kills it, which has happened.

Before you write any loop, say what will make the check true, and check that it is not you. A loop waiting for the open runs to reach zero while you keep starting runs never ends. When nothing outside the loop can make the check true, do not write the loop: do the thing that ends the wait.

A pkill or pgrep pattern matches your own command line too. The shell running it has the pattern in its arguments, so a broad pattern kills the shell mid-command: you get an exit code in the 140s, no output, and nothing saying what happened. Match on the executable instead of a substring of the whole line (pkill -x <name>, or pgrep -f on a path that cannot appear in your own invocation), and prefer the tool that owns the process: weft's own commands stop what weft started, and [the daemon] is never yours to kill anyway.

--json is a global flag with two meanings. The long commands (build, bake, run, activate, deactivate, resync, infra, rm, the cancels) stream progress as one JSON object per line. The readers (status, ps, executions, events, logs, files, listener inspect, token, stop, connect, tree, examples, diff, checkpoint, branch, freeze, prune, wake) print what [the daemon] answered, which you read with jq instead of parsing the human columns; test-node prints its reports as one JSON array. new, follow, daemon, catalog, clean and update ignore it.

Naming a node

Every command that takes a node takes its whole path from the entry file, dot-joined, the way the source reads: classify for a node written in src/main.weft, review.classify for one inside the group review, triage.classify for one inside the file the site triage includes (triage = @include("triage.weft")), and triage.review.classify when it sits in a group inside that file. Sites, groups and nodes are one tree, and a name is the walk down it from the top. There is no short form: a bare classify names nothing once the node sits inside a group or an included file, and the refusal spells the name that works. The same file included twice is two places with two names (triage.classify and again.classify), each with its own runs, waits and display. This is what --from, --emit, --target, --before, --seed-until, --seed-before, --group, --fire, weft events --node, weft wake, weft freeze --expect, weft infra node-stop, weft infra node-terminate, weft infra logs and weft token mint --display take, and what weft events, weft executions, weft infra status and the graph print back.

The command map

CommandWhat it does
weft buildcompile, resolve assets, build the worker image (content-addressed) and register the project with [the daemon]. Starts nothing. You never need it while building a program: weft run, weft activate and weft resync build on their own, so running weft build before them only builds twice. Its one use is the final deployment, weft build --referenced, which ships only the node types the program uses. Also the repair when [the daemon] no longer holds the code a past run ran: registering records the compiled program under its own hash, the one the run names, so unchanged files make the run readable again. Adds nothing to the version tree; weft checkpoint does that
weft validate --file src/main.weft < src/main.weftstrict compile + validate, diagnostics as JSON, nothing runs
weft run [<example>] [--referenced] [--seed] [--root] [--from <node>=<ports-json>]... [--emit <node>=<ports-json>]... [--target <id>]... [--before <id>]... [--group <id>=<ports-json>] [--fire <trigger>=<wake-json>] [--save <name>]build and start one execution; --detach returns its [color]. --from supplies backup inputs at a start, --emit supplies outputs without executing that node. Real producers take precedence over backups, and under --seed the earlier run's result IS a real producer: a --from value at a node history already feeds goes unused (the run warns). To hand a new value in, run without --seed, or --emit the upstream output. --target includes the endpoint; --before excludes it. --group selects a whole group or included file, with its input payload. --fire runs exactly one trigger using a matching bake. Ordinary groups can be cut precisely; loops stay whole. --seed-before / --seed-until bound compatible reuse. A named example supplies saved starting parameters; current code runs. Clear and replacement rules are in weft-sdp
weft checkpoint [<label>]record the files as a version under head, no run, no build; already at <id> when identical
weft branch <version|label|color>restore that version's files and move head (a checkpoint label names its version; a [color] makes that run the next seed). Refuses on a dirty tree naming the files; --discard overrides
weft treethe version tree: versions with what changed, their runs beneath, head marked (--json adds disk_version)
weft diff <ref> <ref> [--full]compare observed outputs for human or AI review, including frozen focus nodes. A ref is a [color], its unambiguous prefix or example:<name>. Differences are evidence and do not fail the command
weft freeze <name> [<run>] [--expect <node>]...save that run's starting parameters and observed outputs in examples/<name>.json; default is head's run. --expect marks nodes to focus on during review. Run and diff leave the accepted file intact; freeze again after accepting its replacement
weft exampleslist saved parameters and frozen examples; inspect them, rerun one with weft run <name>, then compare with weft diff
weft bake [--referenced]prepare trigger inputs without arming listeners. A manual --fire requires a matching bake; use --referenced here when the run uses --referenced. Activation also prepares and records a bake before arming. Takes --running-policy like activate (the setup runs on a worker, and a stale one is replaced first)
weft wake <color> <node>resolve a pure time wait now; refused for a wait expecting a value, naming its kind
weft prune <version>delete a version, everything under it, and their runs; asks first, --yes for scripts. Refuses on head's version, under a frozen example's origin, and while a run in the subtree is running
weft stop <color>cancel an execution
weft statusregistration, build state, listener, infra, drift
weft psevery registered project
weft executions [--limit N] [--project <id>] [--phase fire]past executions, newest first (see Reading a run)
weft events <color> [--node <id>] [--kind <kind>] [--full] [--json]a run's events in order, one compact line each (see Reading a run)
weft logs [color]a run's log (no argument: the latest execution of the project in the current directory; see Reading a run)
weft follow <project>live events for a project
weft activate / weft deactivateturn triggers on / off. deactivate on an active project needs --mode <wipe|hibernate|park> (a [mode], defined under The three modes). Both take --running-policy <cancel|wait>, default cancel: on activate it says what happens to a worker still up from an older build (cancel what it runs and replace it now, or wait for its executions to land, up to --drain-timeout seconds); on deactivate the same for the project's running executions
weft resyncdeactivate + activate against a fresh build, after editing a trigger subgraph. Only for an ACTIVE project (a parked or hibernated one refuses: weft activate first), and it needs the same --mode answer as deactivate; without it, it stops and asks
weft infra start / status / stop / upgrade / terminate / cancel / logsthe project's [infra] (see The infra verbs)
weft token mint / ls / revokesignal tokens: scoped access for an outside listener such as the browser extension. mint prints the connect URL, then the bare token on its own line for a script
weft daemon start / status / logs[the daemon]; only status and logs are yours
weft catalog updatere-sync nodes/base_catalog/ to the installed weft's stdlib
weft describe-nodes --listone line per node type; how you find one
weft describe-nodes --node <Type> --compactone node's wiring view; read it before wiring. With no flags you get the whole catalog as JSON, which is large
weft test-node <target>run node self-tests (--tier live spends money, asks first)
weft connectthe editor's Connect panel as a CLI verb: --list the stored connections, --node <id> --grant <id> to pick one for a node, connect new accounts through both doors, --upgrade, --forget, --disconnect
weft rm [--journal] [--local] [--all] --yesunregister the project, terminate [infra], reclaim data. --journal also drops its run history, --local its build artifacts, --all implies every flag. Asks first; pass --yes, and only after the user confirmed
weft clean --yesjournal and image cleanup, per subject, and naming a subject takes all of it: a [color] takes that one run, --project <id> takes a whole project's history (removing a project leaves its runs behind, so this is how you erase them), no subject takes everything older than --keep-days (30 by default), --all takes the lot. A version the deletion left bare (no runs, nothing under it, no checkpoint name, not head) goes with the runs; a named checkpoint never does. --images and --build-cache touch no journal rows. Deleting runs asks first; pass --yes, and only after the user confirmed

How a run picks which nodes execute is in the weft-language skill. A trigger fires on its own event only once the project is activated; to try a trigger's program before activating, weft bake, then weft run --fire '<trigger>=<wake-json>' (the weft-sdp skill).

The build and run flow

weft run compiles, registers the project with [the daemon], builds the worker image if sources changed (Cargo runs inside Docker, never on the host), then fires. Compile failures print compile failed: then line:column message lines. HTTP errors surface [the daemon]'s own message verbatim.

weft build skips [the runtime tier] (a connection not yet picked), so a half-wired program still builds and a CLI-started run still starts; the node fails at execution, in the journal, naming the service and what to do ("no telegram connection picked; connect one on the node"). What weft validate and the editor print before a run is the same fact in fuller words, naming the node too. The editor's Run, Activate and Resync buttons refuse until every connection is picked; the CLI does not, so before a run you check with weft validate --file src/main.weft < src/main.weft, which reports the rule-runtime findings in seconds. The fix is a picked connection (weft connect --node <id> --grant <id>, or the user on the node's Connect button), never a source edit.

A program with triggers listens only after weft activate. An edit to a trigger's subgraph takes effect only after weft resync.

project files changed while building; run the command again means a file of the project was written while the build was reading it, which is almost always a helper of yours writing into nodes/ at the same time. Nothing is broken: run the command again. What matters is that the deployment did NOT change, so the live program is still the previous one. Anything you call before a resync succeeds is exercising the OLD code, and results from it tell you nothing about the edit you just made.

A run that touches [infra] is refused, from the CLI and from the editor's Run button, until that [infra] is running: weft infra status, then weft infra start. Activating is refused the same way, so on a program with [infra] the very first command is weft infra start, not weft activate: it is what builds the node's images, and nothing else does.

The three modes

A [mode] is what happens to the runs parked on a person or a timer when the triggers go down (deactivate, resync on an active project, the [infra] verbs that deactivate on the way):

  • wipe: their forms and timers are dropped and the runs end cancelled. You pass it only when nothing is in flight (weft executions shows no suspended run of the project) or the user said to drop the waiting work.
  • hibernate: the runs stay alive for a grace window (--grace <minutes>, 15 unless set); a fire arriving inside it is held and delivered when the project comes back. Past the window new fires are refused; the waiting runs and the project survive (wipe is the [mode] that drops them).
  • park: the runs stay alive with no time limit; every fire is held until the project is reactivated. Your pick when the user is editing and people are mid-conversation.

wipe is what you pass while you are building. Nothing waiting on the program is anyone's conversation yet, so dropping it costs nothing and the command lands at once. hibernate and park exist for a program people are actually using: pass one when the user says so, or when weft executions shows a run parked on a person you would be throwing away.

--running-policy cancel (the default) stops the executions already running now; wait lets them finish first, new fires held meanwhile, and you pass it only when the user asked for the running work to land. A wait waits under hibernate and park alike (the mode says what happens to the parked work, the policy what happens to the running work: two separate answers), and it ends by cancelling whatever is still running at its cap (--drain-timeout, 60 seconds unless set); under wipe waiting is refused. With no --mode and no terminal (which is every command you run) the mode is wipe. That is the right answer while you are building, so you rarely type it; you type --mode hibernate or --mode park when the program is one people are using and the work in flight has to survive.

The infra verbs

[infra] is a container the runtime keeps running for the program (an infra node: PostgresDatabase, BaileyBridge), with a disk that survives restarts. You pick the verb by what you want to keep:

  • If the [infra] is not running yet, or was stopped: weft infra start. It brings every unit up to its spec and waits until ready. Running it again does nothing for units already up. It leaves activation alone.
  • If you changed an infra node's spec (image, env, volumes) and want it live: weft infra upgrade. Each unit whose spec changed goes down and comes back on the new spec, the others are left alone, every disk is kept. Once the [infra] reports ready, weft activate.
  • If you want it off for a while and the data kept (a paired WhatsApp session, a database's rows): weft infra stop. The containers go, the disks stay, and weft infra start brings it back with everything in it. Stop and terminate cancel the project's running executions first, because they may be using this infra; --running-policy wait lets them land instead, up to --drain-timeout seconds, on an active project and an inactive one alike.
  • If you want it gone (the database and its rows, the bridge and its pairing): weft infra terminate. Every resource is deleted, disks included unless the node's own spec preserves them. There is no undo: a database comes back empty on the next start, and a bridge needs its QR scanned again.
  • If a verb is stuck mid-way: weft infra cancel stops it between steps; whatever it already did stays done.

stop, terminate and upgrade take the project's triggers down first (nothing can fire at [infra] that is going away) and leave it deactivated, so each ends with weft activate when you want it listening again.

weft infra status says per node whether it is running and its endpoint. weft infra logs <node> (or no node, for all) prints what the containers wrote, --tail N and -f as for a run: a failure inside a service is read there, with no kubectl.

Reading a run

Each verb below prints one compact line per run or per event and has a flag that opens the part you want, so a forty-node run reads without loading the whole journal. You read in this order and stop when you hold the failing node and the wrong value.

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
2k
Forks
221
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
weft-running
Source
github.com/weavemindai/weft