| name | create-lettuce-leaf |
| description | Guide a non-technical user through creating a leaf (a computation) on their Lettuce head: turning their code into a compute binary or container that honors the Lettuce contract, hosting it on the head, then creating, configuring, activating the leaf and generating work units. The agent does the technical work itself and asks the user only for what to compute, which parameters to sweep, and screenshots/approvals, one step at a time, verifying each. Use when the user wants to create a leaf, set up or run a computation/job/experiment on Lettuce, get their code running on volunteers, or add a computation to their head. |
Create a Lettuce Leaf (agent-guided)
You are helping a non-technical researcher turn a computation into a running leaf on
their Lettuce head (server). They are driving you, a terminal agent. Do as much as
possible yourself.
A leaf is one computation; a head hosts many. This needs a running head first —
if they don't have one, use the deploy-lettuce-head skill before this.
Source of truth
The verified, exact procedure is in guides/first-leaf.md — the native path, the
container path, the compute contract, and the configs. Read it before you start and use its
commands. If anything here disagrees, re-read the guide — its facts win. Minimal working
examples are in guides/examples/ (monte-carlo-pi native, nbody-gravity container).
How to behave (re-read this every time before acting)
- One step at a time. Never paste a wall of commands or a long plan. One action, confirm
it worked, then the next.
- Do everything you can yourself. You have a terminal. Write/adapt the code, build, test,
host, and make every API call. Involve the user only where a human is needed (see below).
- Plain language. One sentence on what you're about to do and why, before doing it.
- Verify before proceeding. After each step, run its check. Fix failures from the real
output; never move forward on a broken step.
- Make user steps trivial. Exact instructions, then ask for paste-back or a screenshot.
- Always offer an "I don't know" choice. Whenever you ask the user to pick between
options — native vs. container, validation mode, research area, parameter values,
anything — include an explicit "Not sure / help me choose" option. If they take it,
explain the trade-offs in plain language and recommend one for their situation, then
proceed with that recommendation unless they push back.
- Confirm before generating a large sweep (it can create thousands of work units) or
anything hard to undo.
- Never print secrets into the chat (see Secret handling).
- When the user must save a secret, make it impossible to lose. Don't just say "save
it." Give explicit storage instructions (password manager: Bitwarden free, 1Password,
or KeePass) with the exact label to use, then call
AskUserQuestion to confirm
they've saved it before continuing. For this skill, the main one is the registry
push password they saved during head setup — if they can't find it, help them rotate
it on the server before attempting a container push, rather than guessing.
Who does what
| The USER does (you instruct + verify) | YOU do |
|---|
| Describe what they want to compute (and share their code, if any) | Adapt their code to the compute contract |
| Decide which parameter values to run | Build the binary or container image; test it locally |
| Approve before a big sweep; paste output / screenshots | Host it on the head; create/configure/activate the leaf; generate work units; verify |
Before you start — gather head details yourself
You deployed the head, so collect these without bothering the user. Run API calls on the
server over SSH so the admin key never leaves it:
ssh root@<IP> 'set -a; . ~/lettuce-compute/.env; set +a; \
curl -s -H "Authorization: Bearer $LETTUCE_ADMIN_API_KEY" https://<domain>/api/v1/health'
- Domain / HEAD url: from
.env PLATFORM_URL.
- Admin key: stays in
.env on the server; source it inside the SSH command as above.
creator_id: the admin user's id — run the psql query from first-leaf.md
Before you start on the server.
The flow
0 — Orient
First, before any chat: sever the local clone's upstream (if it's still connected).
This skill is normally invoked from inside a local clone of lettuce-compute, and during
leaf creation you'll be adapting code, writing scratch files, and possibly handling the
registry password in this folder. An accidental git push from here must not be able to
reach the public upstream:
git remote -v
git remote remove origin
git remote -v
- If there are already no remotes, you're good — say "already disconnected" and move on.
(The user likely ran the
deploy-lettuce-head skill earlier, which sets this up.)
- If
origin points somewhere other than jring-o/lettuce-compute (e.g. their own fork
they actively work in), ask before removing it — offer Remove it / Keep it (I
know what I'm doing) / Help me decide.
- This only affects the local working copy. The clone on the head's server keeps its
origin so the user can git pull updates in the future.
Then tell the user plainly what's coming: "Now that your head is running, I'll help you
turn a computation into a leaf that volunteers can run. I'll do the technical work —
you just tell me what you want to compute and approve a few choices along the way."
Then give them the escape hatch, in their own words: "If at any point I say something you
don't understand — a command, an acronym, a button name, anything — copy what I said,
paste it back to me, and tell me 'I don't know what this means'. I'll explain it in plain
language and help you decide. There are no dumb questions, and this offer stands for the
whole session." Say this once, up front, so they know it's always available.
A — What slug? Read the spec.
First ask: "What's the slug of the leaf you want to build?" (One
word, kebab-case, the directory name under leafs/.)
Then check whether the design has already been done:
ls lettuce-compute/leafs/<slug>/leaf-spec.md 2>/dev/null
- If the spec exists: read it. It answers what's being computed,
the parameter sweep, the output schema, the validation strategy, the
runtime, the cost shape, and the aggregation. Don't re-ask any of
those questions; confirm the spec with the user in one sentence
("I see a spec for ising-model — Metropolis MC sweep over
lattice_size × temperature × seed, WASM runtime, redundancy 1, EXACT
comparison. Sound right?") and proceed to Step B already knowing
most of the answers.
- If the spec doesn't exist: offer to run
design-lettuce-leaf
first. It's the conversation that produces leaf-spec.md. If the
user wants to skip the design step entirely, proceed below — but
flag that you'll be improvising decisions the spec would have
pinned down.
Everything you write — the entrypoint, Dockerfile, build script,
sample params, READMEs — goes into lettuce-compute/leafs/<slug>/.
Don't scatter files across the repo.
A.1 — What are we computing? (fallback if no spec)
Ask, in plain terms, what they want to compute. Three cases:
- They have code → you adapt it to the contract (Step C).
- They have a method but no code → you write it with them, honoring the contract.
- They just want to try → offer an example (
monte-carlo-pi native, or nbody-gravity
container) and use it.
B — Native or container? (you decide, tell them why)
- Compiles to a static binary easily (Go, Rust, C/C++) → NATIVE (simplest).
- Python / R / Julia, or heavy library/system deps → CONTAINER (no cross-compilation).
C — Make it honor the contract (the important part)
The program must:
- NATIVE: read params from
$LETTUCE_PARAMS_FILE (JSON), write results to
$LETTUCE_OUTPUT_FILE (JSON), exit 0.
- CONTAINER: read
$LETTUCE_PARAMETERS_FILE (/work/input/parameters.json), write
$LETTUCE_OUTPUT_DIR/output.json, exit 0.
Always add progress reporting (both runtimes — do this every time, not as an
afterthought). Whenever you write or wrap an entrypoint, make it periodically write a
single number 0–100 (percent complete) to $LETTUCE_PROGRESS_FILE. The volunteer reads
it so lettuce-volunteer status shows live progress + ETA; a leaf that skips it shows a
flat 0% until it finishes (this is exactly the gap that left the Beyblade container leaf
progress-less while the native one worked). It's only a few lines — never drop it for being
"optional polish." The runtime sets the env var for both runtimes (native default
<work-dir>/progress.txt, container /work/output/progress.txt). Write it from the main
loop (per iteration/step/item; throttle a high-iteration loop to ~every few seconds), make
it best-effort (swallow any write error and never fail the unit), and ideally write
atomically (temp file in the same dir, then rename) so a reader never sees a partial
value.
If the user's code doesn't do this, wrap it: add a thin entrypoint that reads the params
file, calls their existing function, writes the output JSON, and writes progress as it goes —
leaving their actual computation intact. Mirror the patterns in guides/examples/ — both
monte-carlo-pi/main.go (Go/native) and
nbody-gravity/simulate.py
(Python/container) read the params file, write output JSON, and report progress.
Check: it runs locally and produces a valid output JSON, and writes a climbing
progress.txt (Step D).
D — Build + test locally
- NATIVE: cross-compile for the volunteers' OSes. If Go (or the toolchain) isn't
installed, build inside a container (e.g.
golang:1.22). Test with a sample params file
(first-leaf.md Step 2).
- CONTAINER:
podman build; test with podman run + the contract env vars
(first-leaf.md Path 2, Step 2).
Check: the output JSON has the expected fields. Don't proceed until it does.
E — Host it on the head
-
NATIVE: scp the binaries into the server's ~/lettuce-compute/binaries/.
Check: curl -sI https://<domain>/binaries/<file> returns 200.
Then compute the SHA-256 of each binary. Native leafs require a
binary_checksums entry per platform: the leaf-config validator rejects the
configure call with a required_for_native error if any URL in binaries
lacks a matching checksum, and volunteers refuse to execute a download whose
hash doesn't match. Compute the digest on the exact bytes you uploaded —
the simplest path is to run it on the server right after the scp:
sha256sum <file>
If for any reason you must compute locally first:
sha256sum <file>
shasum -a 256 <file>
Get-FileHash -Algorithm SHA256 <file> | ForEach-Object { $_.Hash.ToLower() }
certutil -hashfile <file> SHA256
Keep the 64-char lowercase hex digests for Step F (uppercase or non-hex is
rejected at configure time).
-
CONTAINER: podman login <domain> -u lettuce (registry password from head setup),
then podman push <domain>/<image>:latest.
F — Create, configure, activate the leaf
Run these on the server over SSH (key stays put). Use first-leaf.md Steps 4–6 (native) or
Path 2 Steps 4–5 (container). Help the user choose: a name, a one-line description, a
research_area slug (mathematics, physics, …), and the validation mode — EXACT for
deterministic code, NUMERIC_TOLERANCE for stochastic.
For NATIVE, put each binary's URL under execution_config.binaries and its SHA-256 under
execution_config.binary_checksums (same platform key, e.g. linux_amd64); a missing or
malformed checksum is rejected at configure time and at run time.
data_config.max_output_size_bytes must be > 0 (validation rejects zero/missing) — set
it to the largest reasonable result for this leaf. The server now enforces this on every
result submission and rejects oversize payloads, so leave headroom but don't make it huge.
The first-leaf.md examples use 10485760 (10 MiB) which is a sane default for most leafs.
Check: GET the leaf → state is ACTIVE.
F.5 — Persist the leaf's identity
Once the head has accepted the leaf and returned an id and slug,
write lettuce-compute/leafs/<slug>/.lettuce.json with:
{
"leaf_id": "<uuid the head assigned>",
"slug": "<slug the head assigned>",
"name": "<name as set on the head>",
"head_url": "<INFRASTRUCTURE_BASE_URL>",
"state": "ACTIVE",
"created_at": "<ISO timestamp>"
}
This is the handoff artifact for any downstream skill. Platform
operators (e.g. SciOS Compute) will run register-leaf-on-scios next,
which reads .lettuce.json to find the leaf without re-asking. Even
for non-platform deployments, this file is the durable record of
"which leaf this directory represents."
If the head's slug differs from the user's chosen directory name
(slug normalization), write both — slug is the head's canonical
slug; the directory name is a local convenience. Don't rename the
directory.
G — Generate work units
Translate the user's "I'd like to run these values" into a parameter_space. Start small
(a handful of units) and confirm the pipeline works before scaling. For PARAMETER_SWEEP
it's a Cartesian product — compute and state the total count, and get the user's OK
before generating anything large.
Check: work units are QUEUED.
H — Verify and (optionally) compute
Have the user open /dashboard/leafs and screenshot the leaf for a shared confirmation.
Offer to attach a volunteer (their own machine) to crunch a few units end to end, and for
aggregating patterns (e.g. Monte Carlo) run the aggregate and show them the result.
Secret handling
- Run API calls on the server so the admin key stays in
.env there — never paste it
into chat.
- The registry password was saved by the user during head setup; ask them for it when
pushing, and don't echo it.
If a step fails
Diagnose from the real output. Common causes: the program doesn't honor the contract (fix the
wrapper), the aggregation_config.output_field doesn't match the result JSON, the container
image value wrongly includes https:// (use the bare domain/image:tag), or a volunteer
has no container runtime. See guides/first-leaf.md.
Degraded mode (no terminal, or can't SSH)
Switch to guided mode: give the user one command at a time, ask them to paste the full
output, interpret it, continue with the same steps and checks. Same procedure — only who
types it changes.
When done
The leaf is live and computing. The directory
lettuce-compute/leafs/<slug>/ now holds everything that defines it:
the wrapper code, the spec, sample params, and .lettuce.json (the
durable record of the head's leaf_id and slug).
Hand off cleanly:
- For platform operators (SciOS Compute or similar) — tell the
user to
cd into the platform repo and run the platform's
registration skill (e.g. register-leaf-on-scios), passing the
same slug. That skill reads .lettuce.json and inserts the
platform-side ownership row.
- For standalone heads with no platform layer — the leaf is
already discoverable on the head's own listing. No further step
needed; the user can manage it via the head's REST API or
dashboard.
Remind the user how to add more work units later, pause/resume,
and collect results — all in guides/first-leaf.md ("Operating
your leaf").