This tutorial walks you through writing and running your first Jaiph program. By the end of it you will have written a single .jh file, run it with the jaiph CLI, watched the live progress tree, and looked at the run artifacts the runtime writes under .jaiph/runs/.
This tutorial uses only script steps, so you do not need an agent backend or API keys. The follow-up tutorial Your first agent run adds a prompt step on top of what you build here.
You will write a file with one script step that prints a greeting, and a return that passes the script’s output back as the run’s return value. The whole file is five lines.
sh, bash, zsh) with curl and either shasum or sha256sum available.Node and API keys are not required for this tutorial. jaiph run executes on the host.
Install the standalone binary:
curl -fsSL https://jaiph.org/install | bash
The installer downloads a per-platform binary, verifies its signature and checksum, and writes it to ~/.local/bin/jaiph. See Install and switch versions for other options, such as JAIPH_BIN_DIR and version switching.
Confirm the install:
jaiph --version
If the command is not found, prepend the install directory to PATH:
export PATH="$HOME/.local/bin:$PATH"
Create a fresh directory and write a file named hello.jh:
script greet = `echo "Hello, ${1:-world}!"`
export def main(who) {
return run greet(who)
}
Here is what each line does:
script greet declares a managed script. The body in the example is a single-line bash command. It uses shell positional arguments such as $1 and $2, not Jaiph ${name} interpolation. ${1:-world} is bash default expansion, which supplies world when run greet(...) passes no value. For a multi-line body or another interpreter, see the Script RHS section of the grammar reference.export def main(who) is the run entry. Every .jh file invoked with jaiph run enters at export def main. The who parameter is bound by position from the CLI arguments after the file path.return run greet(who) calls the script with who as ${1}, captures its stdout as the step value, and returns it as the run’s return value.jaiph run ./hello.jh "Adam"
Before any step runs, the CLI prepares the file in two steps:
ModuleGraph once. This file has no imports, so the closure is one module.script body as an executable file under a temporary scripts/ directory that $JAIPH_SCRIPTS points to. Def steps stay as interpreted AST, so there is no transpiled main.sh.You should see this (timings will differ):
Jaiph: Running hello.jh
def main (who="Adam")
▸ script greet (1="Adam")
✓ script greet (0s)
✓ PASS def main (0.2s)
Hello, Adam!
The first line is the run banner. The def main row and the indented ▸ and ✓ rows are the live progress tree. A ▸ marks a step that has started, a ✓ marks a step that has finished, and (0s) is the elapsed time for that step. The root row is static, and only nested steps print ▸ and ✓ lines. The Hello, Adam! line after PASS is the return value of export def main, which jaiph run prints on stdout after a successful run.
Every run writes durable files under .jaiph/runs/<YYYY-MM-DD>/<HH-MM-SS>-<entry>/ in UTC. List the most recent run:
ls -la .jaiph/runs/*/*/
The layout you should see:
000001-def__main.out and .err hold the captured stdout and stderr for the entry def.000002-script__greet.out and .err hold the captured stdout and stderr for the greet script step.return_value.txt holds the value def main returned, and it is written only on success.run_summary.jsonl is the durable event timeline, with records such as RUN_START, STEP_START, STEP_END, and RUN_END.heartbeat is a liveness file that holds an epoch-milliseconds timestamp, refreshed about every 10 seconds while the run is active.Read the captured script output and the return value:
cat .jaiph/runs/*/*/000002-script__greet.out
cat .jaiph/runs/*/*/return_value.txt
Both should match the line printed after PASS. The full artifact layout is documented in the durable artifact layout section of the architecture page. The event types in run_summary.jsonl are documented in the run artifacts section of the CLI reference.
Replace the script body with one that exits non-zero:
script greet = `echo "Hello, ${1:-world}!" && exit 7`
export def main(who) {
return run greet(who)
}
Re-run with the same arguments:
jaiph run ./hello.jh "Adam"
The CLI prints a ✗ FAIL line on stderr, then a block with Logs:, Summary:, out:, and err: lines that point to the run directory, followed by an Output of failed step: excerpt. The process exits non-zero. return_value.txt is not written on failure, only on success.
Revert the failing script body so the run passes again, then pick a direction:
prompt step that calls an agent backend.use and --env.jaiph subcommand and flag.