Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Your first program

weft new hello
cd hello
weft run

weft new scaffolds a project. weft run compiles it, registers it with the daemon, fires one execution, and streams the events back until it finishes.

Bring your AI assistant

You are meant to build weft by talking. The assistant that builds with you is called Tangle: a persona weft installs into your project, who knows the language, the whole node catalog on disk, and the loop of build one stage, run it, read what came out. Tangle is part of weft, not a plugin you wire up.

The flow is two steps:

weft new hello --assistant kilo-code       # shorthand: --assistant kc

then open the hello folder in that assistant (Kilo Code, here). Tangle loads on its own, with its method, its node reference, and its commands already in place. You describe what you want, in plain words; it shapes the program, picks or writes the nodes, runs it, and shows you what happened.

The flag’s value is the assistant you use, so the same command covers every assistant weft supports as more arrive (repeat the flag to install for several at once). And the choice is remembered: your next weft new installs the same assistant with no flag at all, until you pass --assistant <name> to change it or --assistant none to stop. Claude Code is also available as --assistant claude-code (shorthand cc).

Tangle is copied into the project, so those files are yours like the rest of it: they get committed, and someone who clones your project gets Tangle without needing a weft checkout. The price is that a project holds the Tangle that created it. If you want one on a newer version after updating weft, run weft tangle update in it, which re-copies every file Tangle owns and leaves anything your assistant wrote beside them alone.

What got created

hello/
  weft.toml      the project's name and its permanent id
  src/
    main.weft    the program
  nodes/         every node this project can use
  .weft/         build output and caches (already gitignored for you)

nodes/ is the surprising one, because it changes where your nodes come from. When you run weft new, the entire standard library is copied into your project under nodes/base_catalog/, so the build never reaches back into the weft installation and upgrading weft cannot change what your program does. If you want the newer standard library later, weft catalog update re-syncs that mirror.

Your own nodes go anywhere else under nodes/, or beside the code under src/, never inside base_catalog/, because weft catalog update wipes and recopies that folder and anything you edited in there goes with it.

The program

src/main.weft is three lines:

greeting = Text { value: "hello world" }
out = Debug

out.data = greeting.value

Two node declarations and one connection.

The first line says: make a node called greeting, of type Text, configured with the string "hello world". The second makes a Debug node called out. The third wires them.

Read the connection right to left, the way an assignment reads: the value flows from greeting.value into out.data.

  greeting (Text)                 out (Debug)
  ┌──────────────────┐            ┌──────────────┐
  │ value: "hello…"  │            │              │
  │            value ●───────────▶● data         │
  └──────────────────┘            └──────────────┘

Before anything ran, the compiler checked that connection. Both ports exist, Text.value emits a String, Debug.data accepts one, and nothing required was left unwired.

What weft run printed

One line per node event, in order: the execution started, greeting ran and emitted, out ran, the execution completed. All of it is written to the journal as it happens, and you can read them back later with weft events <color>.

A color is one execution. Running the same project again mints a new one, so whenever anything in weft says “per color”, it means per execution.

Change something

Edit src/main.weft:

greeting = Text { value: "hello world" }
shout = ExecPython(text: String) -> (out: String) {
  code: "return {'out': text.upper() + '!'}"
}
out = Debug

shout.text = greeting.value
out.data = shout.out

weft run again. The chain is three nodes now.

ExecPython is worth noticing because of the arrow. Most nodes have fixed ports declared by their author; this one lets you declare them inline. (text: String) is its input, -> (out: String) is its output, and the Python body gets text as a variable and returns a dict keyed by output port name. The compiler type-checks those ports like any others.

The mental model

A node fires when all of its required inputs have arrived. When it fires it runs its code and emits values on its output ports, and each emission travels along a wire to exactly one input port and waits there. A node with no upstream fires immediately, and the execution ends when nothing is left in flight and nothing is waiting.

Everything else in the language, groups and loops and streams and human pauses, is built out of that one rule, including the wrinkle where branching comes from: what happens when a node produces nothing. That is How a weft program runs, the chapter to read once you want to build something real.

Next: reading the graph.