lightcone
Skill2 starsChanged 18 days agoBSD-3-ClauseView on GitHub
Companion for a Lightcone project — an ASTRA analysis (astra.yaml) executed with the `lc` CLI. Use this skill whenever the user wants to scope, resume, plan, run, debug, publish or discuss such a project: "new analysis", "scope a project", "resume the project", "where were we", "run the pipeline", "lc status/materialize/run", "lc compute", "launch a cluster", "run on GPUs", "publish the analysis". Also use it whenever the working directory holds an astra.yaml and the user asks to run, re-run, fix or interpret an analysis, even if they never say "Lightcone", "ASTRA" or "lc". Do NOT use it for the astra.yaml format itself — schema, decisions, universes, evidence and quotes are the astra skill's.
- Installs packages
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 0 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 50
Before doing anything else, evaluate whether the lc CLI is present through this checklist:
- Very specific: line 55
Seen neither?: Run lc --version yourself — this skill assumes lightcone-cli==0.5.0 or newer.
- Very specific: line 150
Write scripts recipe-ready: one script per output, every decision a CLI flag (never a hardcoded option value), and the artifact written to …
- Very specific: line 197
Every verb that reports takes --json, and that is the form to drive from: one object on stdout, with the reasons already spelled out per …
- Very specific: line 238
Write to astra.yaml as each decision crystallizes rather than in bulk, and keep the conversation to a summary with ids — candidate …
- Very specific: line 251
A fresh clone needs
lc init: before any git command touches it — cloning carries neither the environment nor the settings the project … - Very specific: line 257
lc materializecommits the outputs it makes: Don't commit results yourself, and never write into results/ by hand: bytes arriving any … - Very specific: line 274
Everything runs through
lc: Never invoke the container runtime, the sandbox or a job scheduler yourself — podman run and friends bypass … - Very specific: line 281
After changing such a variable, or upgrading lc, down the cluster and launch a new one.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Lightcone projects
An analysis declared in astra.yaml, executed by lc, and documented in index.md: recipes turn
declared inputs and decisions into outputs under results/ — one output is
one file, named by the spec — each committed with the code that produced it,
the MyST report of the analysis is in index.md.
| What you are doing | Where to go |
| A research question, no spec yet | references/scoping.md — interview first, write no code |
| Picking up an existing project | lc status, then summarize before asking what is next |
Anything that executes — lc run, lc materialize | Get a cluster first |
| Writing or debugging a script | lc run <cluster> -- <argv> — the sandbox a recipe gets |
| Producing an output for real | Make an output |
A bigger or remote shape, GPUs, recipe resources: | references/compute.md |
| A refusal, a failing recipe, a surprising status | references/diagnosis.md |
| Papers, quotes, prior insights | references/literature.md, and references/extraction-brief.md per paper |
| Writing or updating the report | references/reporting.md — MyST + {astra} references |
| Sharing, archiving, citing | references/publishing.md |
Anything about astra.yaml itself | the astra skill — this one repeats none of it |
Before you drive lc
The user installs the CLI once, on their machine, and that is the whole
setup — it puts lc on their PATH:
uv tool install lightcone-cli==0.5.0Name that version so the install selects the release this skill was written against; an already-installed newer version is fine.
Before doing anything else, evaluate whether the lc CLI is present through this
checklist:
- Seen
Lightcone CLI ready: lc <version>this session? That is this check, already run by the session-start hook. Don't repeat it. You are all good. - Seen the hook report a problem? It names the remedy and who runs it.
- Seen neither? Run
lc --versionyourself — this skill assumeslightcone-cli==0.5.0or newer. Silence is not an all-clear: the skill also ships without that hook, and a subagent never sees session start. Remedies are inreferences/diagnosis.md.
Where a person can answer, offer and wait — never install or upgrade unasked, it is their machine. Where none can (headless, CI), act and say what you changed.
Get a cluster
lc run and lc materialize execute on an allocation named as their first
argument. Neither starts one, waits for one, or falls back to running
locally. lc status, lc materialize --check, lc init, lc build and
lc compute need none.
lc compute is an abstracted view of the user's compute, whatever
machines and schedulers lie behind it, and it is the only view to use:
lc compute resources lists every shape you can get.
- Reuse.
lc compute status --jsonlists this user's live allocations;pendingis still queued. For anactiveone,lc compute status <name> --waitreturns once its workers are connected. - Launch. On a workstation,
lc compute launch --waittakes the built-in local offer — every usable CPU and all RAM, one node, plus the GPUs exposed byCUDA_VISIBLE_DEVICESon Linux — names itlocal, and prints that name once it is ready. Only one local cluster runs per user per machine; a second launch is refused and names the live one: reuse it. The built-in offer has no hard walltime and stops after 30 minutes idle; active tasks keep it alive. Add--time 1h30monly when a hard limit is wanted: it kills the allocation at that age, even mid-write if a recipe is still running. - Pass the name.
lc run local -- python src/fit.py …,lc materialize local fit. Shell variables do not survive between your tool calls, so carry the name itself, not$CLUSTER. - Release it with
lc compute down <name>once the work is done and the user has not asked to keep it.
The local shortcut is routine: launch it when there is something to run,
and say that you did. Anything else lc compute resources lists — a
remote shape, several nodes, GPUs — can spend the user's allocation hours
and queue time: show the lc compute launch … --dry-run plan and wait for
a yes. Never down an allocation you did not launch unless the user asks
— lc compute status lists theirs too, some possibly serving another
session.
Make an output
- Probe.
lc run local -- python src/fit.py --outliers clip— everything after--is argv, likeuv run;lc run local -- bash -c '...'for shell syntax. What works here works as a recipe. A missing import is environment work:uv add <pkg>, which is the only way to change the environment. Have the probe write where a probe may write (below) — the project root is read-only, so a script savingchain.npzbeside itself dies at the last line, having done all the work. - Wire. Give the output a
format:and arecipe:inastra.yaml.format:is the extension its artifact is written with, no leading dot (png,pdf,csv,parquet,json,fits,hdf5,md,tar.gz); the ASTRA schema leaves it optional, butlcrefuses a plan where any executable output lacks one — it has nowhere to write them. In the recipe,{output}is the single file that output must write,{inputs.<id>}a declared input (an upstream output's file, when outputs chain),{decisions.<id>}the active option. Everything a command references must appear in that output'sinputs:/decisions:— that is also how dependencies are declared. A recipe needing more than one CPU, a set amount of RAM or a GPU declares it underrecipe.resources(references/compute.md). - Commit your edits, by path:
git add src/ astra.yaml && git commit. lc materialize <cluster> [targets]. Remakes what isstale, dependencies first, in parallel where the graph and the cluster allow, and commits each output as it lands. No target takes every output in every universe;fittakes that output across universes;robust/fittakes one universe's. Re-running is idempotent. You are done whenlc materialize --checkpasses andlc statuslists no output asno recipe. The check gates only outputs that have a recipe: one declared without one is skipped bylc materialize, so it can never fail the check, and onlylc statusshows it.
Outputs land at results/<universe>/<output_id>.<format> — lc composes
that path, no recipe chooses it, so the whole contents of results/ are a
pure function of the spec, and a consumer knows what every output is from
astra.yaml alone. Beside each one sits .<output_id>.manifest.json, named
from the id alone and kept in git rather than the annex so it reads on a
clone that has fetched no content. It is the run record: the rendered
recipe, the decisions it ran under, git_sha, lc_version/uv_version,
the hermeticity the sandbox enforced, timestamps, and the hashes
(definition_version, env_version, data_version, input_versions) that
lc status compares against the spec to reach its verdict. data_version is
a plain sha256: of the file's bytes, so it agrees with sha256sum. Read
the manifest to answer how an output came to be; never write one. Name one
output at a time while integrating, so each intermediate can be inspected
rather than debugged from the bottom of a long trace.
Write scripts recipe-ready: one script per output, every decision a CLI flag
(never a hardcoded option value), and the artifact written to the path
{output} hands them, opened exactly as given — fig.savefig(args.output),
Path(args.output).write_text(...), never a filename joined onto it. A
recipe that exits 0 having written a directory, some other name, or nothing
at that path is a failure, not an output.
What a command may touch
lc run and a recipe run under the same boundary, which is why a working
probe means a working recipe. The project tree is readable but read-only;
the writable set is small and nothing else in it is writable:
| Writable | lc run (probe) | A recipe |
results/ | the whole directory | only results/<universe>/, where its own output lands |
$TMPDIR, $HOME | a private per-run directory, gone afterwards | same |
/tmp, /var/tmp, /dev/shm | yes | yes |
So a probe's scratch belongs in tempfile / $TMPDIR (TMPDIR already
points inside the private HOME, so tempfile.mkdtemp() needs nothing
declared), or in results/ when you want to keep looking at it — remembering
that anything a probe leaves in results/ carries no run record and must go
before you commit.
Readable: the whole project tree, every source: declared in
astra.yaml — that is what declaring an input buys — the project
environment, and the OS. A denied read is the boundary naming an undeclared
input, not an obstacle to work around.
The tree being read-only includes .venv: uv add changes the environment
because it runs outside the boundary, and nothing inside one ever can.
Read lc status
| State | Means | What to do |
current | Matches the spec, made under this environment | Nothing |
behind | Still matches the spec; the environment moved | Nothing, unless the user wants it remade: --refresh |
stale | Contradicts the spec, or its bytes were last touched by something other than its own run record | The next lc materialize remakes it |
no recipe | Declared in the spec with no recipe, so lc materialize skips it | Write its recipe (step 2), then materialize |
Everything going behind after a uv add is normal, not damage. An lc
upgrade invalidates nothing.
Ask for --json
Every verb that reports takes --json, and that is the form to drive from:
one object on stdout, with the reasons already spelled out per output —
nothing to scrape out of a table. Read JSON; show the user the plain output
when they want to look at something themselves.
lc status --json # {mode, image, sandbox, crate,
# counts{current,behind,stale,"no recipe"},
# outputs[{output, status, why, git_sha, data_version, foreign_write}],
# warnings}
lc materialize --check --json # {up_to_date, planned{target: why}, made, current, behind,
# failed, blocked, warnings, notes}
lc init --json # {converged, created, repaired, unchanged, blocked, warnings}
lc build --json # the build's result
lc compute status --json # {clusters[{id, name, phase, allocation,
# dask{observation, ready, workers}, reason}], errors}
lc compute launch --wait --json # {plan, id, name, accepted, ready}why and planned carry the engine's own reason a thing is stale or is
about to run — quote it rather than inferring one. lc status always exits
0, so its JSON is the whole answer; lc materialize --check exits nonzero
when anything is out of date, which is the gate. The materialize report has
no overall success key: a check passed when up_to_date is true, and a run
succeeded when failed and blocked are empty. lc compute status exits
nonzero when part of its query failed (errors), even though the
allocations it did find are listed. A compute failure under --json still
prints one object, {error, id, submission_token}: keep that id.
Keep the spec and the code in step
Change one, change the other in the same breath: a decision added in code
goes into astra.yaml and every universe file, a renamed output updates its
recipe:, anything removed gets re-validated.
The recipe string and its decisions are an output's identity, so editing
src/ makes nothing stale by itself. Declare a script as an ASTRA input
when its changes should cascade.
Work with the user
Write to astra.yaml as each decision crystallizes rather than in bulk, and
keep the conversation to a summary with ids — candidate decisions and
findings belong in the file, where they can be reviewed. Confirm scope
before anything long (a full multiverse, a first container build). Keep
AGENTS.md's Project Notes current: it holds what the spec cannot, and it is
what a later session reads to pick the work up.
Gotchas
- A probe is not a result. Nothing is done until
lc materializeproduced it, however well the script ran underlc run. lcnever walks up. It acts on the current directory;cdto the project root.- A fresh clone needs
lc initbefore any git command touches it — cloning carries neither the environment nor the settings the project needs to store its files. - Large files are already handled.
data/andresults/are taken care of by config the project carries, so plaingit add/git commitis right anywhere in the tree — there is nothing else for you to run. lc materializecommits the outputs it makes. Don't commit results yourself, and never write intoresults/by hand: bytes arriving any other way carry no run record, and the next run notices and remakes them.- Never
git add -A. It sweeps a probe's strayresults/files into a commit without a run record, which lands the outputstale. - One output is one file. A recipe cannot emit a bundle: split it into
separate outputs, or declare a container format (
format: tar.gz,format: hdf5) and write the one archive. pip installreaches nothing. The lock is the environment:uv add.- Don't re-interview on resume. The spec and
AGENTS.mdalready answer the scoping questions; summarize state and ask what is next. - Six verbs, and
--helpis the authority.init,status,materialize,run,build, andcompute(resources,launch,status,down). Universes and outputs are selected by target (robust/fit), not by a flag: there is no--universe, no--force, no--verbose, no--jobs(concurrency is the cluster's), nolc verifyand nolc export. - Everything runs through
lc. Never invoke the container runtime, the sandbox or a job scheduler yourself —podman runand friends bypass the environment, the isolation and the run record, and whatever they write intoresults/is a foreign write the next run will remake. - An allocation keeps the environment it was launched with. A variable
exported afterwards never reaches its workers, nor does
NAME=value lc run …— writelc run local -- env NAME=value python …for a probe. After changing such a variable, or upgradinglc,downthe cluster and launch a new one. - An interrupted run is still running. Killing
lc runorlc materializedetaches the client only; the command goes on on the worker.lc compute down <full-id>— theidfrom--json, since a name can already belong to a newer allocation — before touchingresults/. - stdin never reaches a command. Pipe nothing into
lc run; a file the command reads is a declared input. - A denial is telling you the truth. Declare the input, the package or
the system tool it names; there is no
--forceand no sandbox opt-out.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

