lightcone-cli 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/.gitignore +1 -1
  2. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/PKG-INFO +8 -5
  3. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/guides/astra-reference.md +66 -18
  4. lightcone_cli-0.3.0/claude/lightcone/guides/lightcone-cli-reference.md +61 -0
  5. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/skills/lc-build/assets/loop-prompt.md +13 -14
  6. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/skills/lc-feedback/SKILL.md +1 -1
  7. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/skills/lc-migrate/SKILL.md +20 -3
  8. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/skills/lc-new/SKILL.md +1 -1
  9. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/skills/lc-verify/SKILL.md +7 -2
  10. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/templates/CLAUDE.md +1 -3
  11. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/pyproject.toml +24 -6
  12. lightcone_cli-0.3.0/src/lightcone/cli/commands.py +780 -0
  13. lightcone_cli-0.3.0/src/lightcone/engine/__init__.py +6 -0
  14. lightcone_cli-0.3.0/src/lightcone/engine/container.py +781 -0
  15. lightcone_cli-0.3.0/src/lightcone/engine/dask_cluster.py +252 -0
  16. lightcone_cli-0.3.0/src/lightcone/engine/manifest.py +205 -0
  17. lightcone_cli-0.3.0/src/lightcone/engine/runner.py +110 -0
  18. lightcone_cli-0.3.0/src/lightcone/engine/scratch.py +212 -0
  19. lightcone_cli-0.3.0/src/lightcone/engine/site_registry.py +167 -0
  20. lightcone_cli-0.3.0/src/lightcone/engine/snakefile.py +444 -0
  21. lightcone_cli-0.3.0/src/lightcone/engine/status.py +155 -0
  22. lightcone_cli-0.3.0/src/lightcone/engine/tree.py +375 -0
  23. lightcone_cli-0.3.0/src/lightcone/engine/validation.py +180 -0
  24. lightcone_cli-0.3.0/src/lightcone/engine/verify.py +140 -0
  25. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/eval/graders.py +27 -22
  26. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/eval/sandbox.py +11 -5
  27. lightcone_cli-0.3.0/src/snakemake_executor_plugin_dask/__init__.py +25 -0
  28. lightcone_cli-0.3.0/src/snakemake_executor_plugin_dask/executor.py +173 -0
  29. lightcone_cli-0.2.0/claude/lightcone/guides/lightcone-cli-reference.md +0 -75
  30. lightcone_cli-0.2.0/src/lightcone/cli/commands.py +0 -2327
  31. lightcone_cli-0.2.0/src/lightcone/engine/__init__.py +0 -42
  32. lightcone_cli-0.2.0/src/lightcone/engine/assets.py +0 -418
  33. lightcone_cli-0.2.0/src/lightcone/engine/container.py +0 -370
  34. lightcone_cli-0.2.0/src/lightcone/engine/io_manager.py +0 -27
  35. lightcone_cli-0.2.0/src/lightcone/engine/runner.py +0 -1017
  36. lightcone_cli-0.2.0/src/lightcone/engine/site_registry.py +0 -142
  37. lightcone_cli-0.2.0/src/lightcone/engine/status.py +0 -135
  38. lightcone_cli-0.2.0/src/lightcone/engine/targets.py +0 -68
  39. lightcone_cli-0.2.0/src/lightcone/engine/tree.py +0 -245
  40. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/LICENSE +0 -0
  41. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/agents/lc-extractor.md +0 -0
  42. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/guides/ui-brand.md +0 -0
  43. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/hooks/langfuse_git_commit_hook.py +0 -0
  44. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/hooks/langfuse_hook.py +0 -0
  45. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/hooks/langfuse_prepare_commit_msg.py +0 -0
  46. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/hooks/langfuse_session_init_hook.py +0 -0
  47. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/hooks/langfuse_utils.py +0 -0
  48. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/scripts/activate-venv.sh +0 -0
  49. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/scripts/check-lc-run.sh +0 -0
  50. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/scripts/session-start.sh +0 -0
  51. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/scripts/validate-on-save.sh +0 -0
  52. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/skills/lc-build/SKILL.md +0 -0
  53. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/claude/lightcone/skills/lc-build/scripts/setup-lc-build.sh +0 -0
  54. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/cli/__init__.py +0 -0
  55. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/cli/plugin.py +0 -0
  56. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/eval/__init__.py +0 -0
  57. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/eval/build.py +0 -0
  58. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/eval/cli.py +0 -0
  59. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/eval/harness.py +0 -0
  60. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/eval/models.py +0 -0
  61. {lightcone_cli-0.2.0 → lightcone_cli-0.3.0}/src/lightcone/eval/report.py +0 -0
@@ -207,7 +207,7 @@ marimo/_lsp/
207
207
  __marimo__/
208
208
 
209
209
 
210
- dev
210
+ dev/
211
211
  eval-results
212
212
 
213
213
  # UV
@@ -1,16 +1,19 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightcone-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Lightcone command-line toolchain
5
5
  Author: Lightcone Research
6
6
  License: BSD-3-Clause
7
7
  License-File: LICENSE
8
8
  Requires-Python: >=3.11
9
- Requires-Dist: astra-tools>=0.2.2
9
+ Requires-Dist: astra-tools>=0.2.5
10
10
  Requires-Dist: click>=8.0
11
- Requires-Dist: dagster-docker>=0.25
12
- Requires-Dist: dagster-webserver>=1.9
13
- Requires-Dist: dagster>=1.9
11
+ Requires-Dist: dask>=2024.1
12
+ Requires-Dist: distributed>=2024.1
13
+ Requires-Dist: jinja2>=3.0
14
14
  Requires-Dist: langfuse>=2.0
15
15
  Requires-Dist: pyyaml>=6.0
16
16
  Requires-Dist: rich>=13.0
17
+ Requires-Dist: snakemake-interface-common>=1.14
18
+ Requires-Dist: snakemake-interface-executor-plugins>=9.0
19
+ Requires-Dist: snakemake>=9.0
@@ -14,7 +14,8 @@ Fields: `name`, `description`, `version`, `authors`, `tags`, `inputs`, `outputs`
14
14
  # Simple analysis -- everything at top level
15
15
  version: "1.0"
16
16
  name: "My Analysis"
17
- description: "What this analysis investigates."
17
+ narrative:
18
+ summary: "What this analysis investigates."
18
19
  inputs:
19
20
  - id: training_data
20
21
  type: data
@@ -43,8 +44,16 @@ decisions:
43
44
  outputs:
44
45
  - id: accuracy
45
46
  type: metric
47
+ inputs: [training_data] # upstream artifacts (Input or sibling Output)
48
+ decisions: [scaling, use_pca, n_components] # decisions that parameterize this output
46
49
  recipe:
47
- command: python scripts/evaluate.py
50
+ command: >
51
+ python scripts/evaluate.py
52
+ --data {inputs.training_data}
53
+ --scaling {decisions.scaling}
54
+ --use_pca {decisions.use_pca}
55
+ --n_components {decisions.n_components}
56
+ --output {output}
48
57
  container: Containerfile
49
58
  ```
50
59
 
@@ -68,9 +77,9 @@ A decision is a methodological choice where a different defensible option could
68
77
 
69
78
  ### Parameterization
70
79
 
71
- **Every decision must be parameterized in code** -- never hardcode a decision value. Accept all decisions as CLI args.
80
+ **Every decision must be parameterized in code** -- never hardcode a decision value. Decisions reach the script via the recipe template's `{decisions.<id>}` placeholders (see [Recipe Format](#recipe-format)). The recipe author chooses how to pass them — typically as CLI args (`--scaling {decisions.scaling}` paired with `parser.add_argument("--scaling")` in the script), but env vars or sidecar files work too. There is no magic auto-injection: if a decision isn't referenced in the recipe template, the script never sees it.
72
81
 
73
- **Underscore convention:** IDs use underscores in `astra.yaml` (`prior_range`). lightcone-cli passes `--prior_range wide`. Scripts must match: `parser.add_argument('--prior_range')`, **not** `--prior-range`.
82
+ **Decision provenance contract:** list every decision a script consumes under `Output.decisions: [...]`. Re-running the output with a different option for any listed decision must be expected to change the result. The validator enforces that every `{decisions.<id>}` placeholder appears in `Output.decisions`, and `code_version` (the cache key) hashes only those decisions — so changes to unrelated decisions don't invalidate cached results.
74
83
 
75
84
  ### Constraints
76
85
 
@@ -91,21 +100,44 @@ Convention path: `results/<universe_id>/<output_id>.<ext>` -- no `path` field ne
91
100
 
92
101
  ## Recipe Format
93
102
 
94
- Inline on outputs. Fields: `command` (required), `inputs`, `container`, `resources`.
103
+ In v0.0.7, **`inputs` and `decisions` live on the Output, not on the Recipe.** The recipe is pure *how*: a `command` template plus its execution context (`container`, `resources`).
95
104
 
96
105
  ```yaml
97
106
  outputs:
98
107
  - id: accuracy
99
108
  type: metric
109
+ inputs: [trained_model] # upstream artifacts
110
+ decisions: [scaling, n_components] # decisions that parameterize this output
100
111
  recipe:
101
- command: python scripts/evaluate.py
102
- inputs: [trained_model] # Dependency on other output
103
- container: ghcr.io/proj/ml:latest # Overrides analysis-level default
112
+ command: >
113
+ python scripts/evaluate.py
114
+ --model {inputs.trained_model}
115
+ --scaling {decisions.scaling}
116
+ --n_components {decisions.n_components}
117
+ --output {output}
118
+ container: ghcr.io/proj/ml:latest # Overrides analysis-level default
104
119
  resources: { cpus: 4, memory: "32GB", gpus: 1, time_limit: "2h" }
120
+ # `gpus` is per-node. Multi-node recipes get nodes × gpus total GPUs.
105
121
  ```
106
122
 
123
+ `Output.inputs` references resolve to either a sibling Output's directory or an analysis-level Input's source string (e.g. a path or `sklearn.datasets.load_iris`). The runner walks any `from:` aliases in the surrounding scope to find the source.
124
+
107
125
  Set `container:` at analysis level (all recipes inherit); per-recipe `container:` overrides. Pass either a container image name (e.g., `python:3.12-slim`, `ghcr.io/org/img:latest`) or a path to a Containerfile (e.g., `Containerfile`, `containers/Dockerfile`). The runtime figures out whether to pull or build.
108
126
 
127
+ ### Recipe Command Template
128
+
129
+ The `command` is a template with these placeholders:
130
+
131
+ | Placeholder | Substitutes to |
132
+ |---|---|
133
+ | `{output}` | The directory the artifact is written to (e.g. `results/baseline/accuracy/`) |
134
+ | `{inputs.<id>}` | The named upstream input's resolved path or source string. `<id>` must be in `Output.inputs`. |
135
+ | `{inputs}` | Space-joined values of every entry in `Output.inputs` (declaration order). |
136
+ | `{decisions.<id>}` | The active option ID for the named decision in this universe. `<id>` must be in `Output.decisions`. |
137
+ | `{{` / `}}` | Literal `{` / `}` (e.g. `awk '{{print $1}}'`). |
138
+
139
+ Static constants belong inline in the command (`--max-iter 1000`); only varying values are decisions, only path/source values are inputs. Any other placeholder is rejected by `astra validate` and by the runner.
140
+
109
141
  ### Conditional Outputs
110
142
 
111
143
  Outputs can have `when` conditions -- the output only exists when the condition is met for a given universe. Uses the same syntax as decision `when` (negation with `~`, lists AND'd).
@@ -242,21 +274,29 @@ analyses:
242
274
  path: ./analyses/train_network
243
275
  ```
244
276
 
245
- Inside each sub-analysis's own `astra.yaml`, `from:` wires inputs and decisions to the parent or siblings:
277
+ Inside each sub-analysis's own `astra.yaml`, `from:` wires inputs and decisions to the parent or siblings using the **unified `../` path grammar**:
246
278
 
247
279
  ```yaml
248
280
  # analyses/train_network/astra.yaml
249
281
  inputs:
250
282
  - id: training_data
251
- type: data
252
- from: build_mocks.mock_catalog # Sibling output
283
+ from: ../build_mocks.mock_catalog # Sibling sub-analysis's output (escape one scope, descend)
253
284
  outputs:
254
285
  - id: trained_model
255
286
  type: data
256
- recipe: { command: python src/train.py, resources: { gpus: 1, memory: "32GB" } }
287
+ inputs: [training_data]
288
+ decisions: [cosmology_model, noise_model]
289
+ recipe:
290
+ command: >
291
+ python src/train.py
292
+ --data {inputs.training_data}
293
+ --cosmology {decisions.cosmology_model}
294
+ --noise {decisions.noise_model}
295
+ --output {output}
296
+ resources: { gpus: 1, memory: "32GB" }
257
297
  decisions:
258
298
  cosmology_model:
259
- from: ../cosmology_model # Inherit parent decision
299
+ from: ../cosmology_model # Inherit parent decision (one scope up)
260
300
  noise_model:
261
301
  label: "Noise Model"
262
302
  default: heteroscedastic
@@ -265,11 +305,19 @@ decisions:
265
305
  heteroscedastic: { label: "Heteroscedastic" }
266
306
  ```
267
307
 
268
- **Wiring patterns:**
269
- - **Input `from:`** -- `from: parent_input_id` (parent input) or `from: sibling_id.output_id` (sibling output).
270
- - **Decision `from: ../parent_id`** -- inherits a parent decision. The sub-analysis uses the parent's value; do not set it in the sub-analysis universe.
271
- - **Output `from: sub.output`** at root level creates an alias to a sub-analysis output.
272
- - **`universe:` field** in universe files selects which sub-analysis universe to load: `build_mocks: { universe: baseline }` loads `./analyses/build_mocks/universes/baseline.yaml`.
308
+ **Wiring patterns (v0.0.7 unified `from:` grammar):**
309
+
310
+ | Where | Form | Meaning |
311
+ |---|---|---|
312
+ | Input | `from: ../id` | An ancestor input |
313
+ | Input | `from: ../../id` | A grandparent input |
314
+ | Input | `from: ../sibling.out_id` | A sibling sub-analysis's output |
315
+ | Output | `from: child.out_id` | Re-export of an own child sub's output (no `../` — outputs only flow *up* via re-export, never reach laterally) |
316
+ | Decision | `from: ../id`, `../../id` | An ancestor decision (downward and lateral references aren't allowed; lift shared decisions to a common ancestor) |
317
+
318
+ An aliased node carries only `id`, `from`, and (where applicable) `when` — type, description, recipe, etc. are inherited from the source.
319
+
320
+ The `universe:` field in universe files selects which sub-analysis universe to load: `build_mocks: { universe: baseline }` loads `./analyses/build_mocks/universes/baseline.yaml`. Decisions inherited via `from: ../...` use the ancestor's value automatically; do not set them in the sub-analysis universe file.
273
321
 
274
322
  ## CLI Reference (astra)
275
323
 
@@ -0,0 +1,61 @@
1
+ # lightcone-cli Reference
2
+
3
+ Reference for lightcone-cli execution: CLI commands, development workflow, status interpretation, and failure diagnosis. For `astra.yaml` spec syntax, see `astra-reference.md`.
4
+
5
+ ## CLI Reference
6
+
7
+ ```bash
8
+ lc init [DIR] # Scaffold a new ASTRA project
9
+ lc run [OUTPUT] [--universe NAME] # Materialize outputs
10
+ lc build [--force] [--runtime docker] # Build container images from specs
11
+ lc status [--universe NAME] [--json] # Materialization status (text or JSON)
12
+ lc verify [--universe NAME] # Recompute hashes and walk the provenance chain
13
+ ```
14
+
15
+ The first `lc` invocation auto-creates `~/.lightcone/config.yaml` with defaults; edit it directly to pin a container runtime or set the extraction model.
16
+
17
+ **Always run via `lc`.** Recipes must execute through `lc run` so that container builds, option resolution, resource limits, and result paths are applied. Treat the underlying execution engine as a black box — never invoke schedulers or container runtimes directly, that will bypass reproducibility guarantees.
18
+
19
+ ## Creating Sub-Analyses
20
+
21
+ Sub-analyses are scaffolded by hand, since each one is just another `astra.yaml` nested in a directory. To add one:
22
+
23
+ 1. Create `analyses/<name>/` with its own `astra.yaml` (and optionally `scripts/`, `universes/baseline.yaml`, `results/`).
24
+ 2. Add a `path:` entry to the parent `astra.yaml` under `analyses:` (e.g. `analyses: { my_sub: { path: ./analyses/my_sub } }`).
25
+ 3. Add a `<name>: { universe: baseline }` entry to each existing parent universe file.
26
+
27
+ Populate the sub-analysis's `astra.yaml` with inputs, outputs, and decisions. Use `from:` references to wire inputs and decisions to the parent or siblings — see `astra-reference.md` under "Composition Mechanics."
28
+
29
+ ## Development Workflow
30
+
31
+ Three overlapping phases:
32
+
33
+ 1. **Write & Debug** — Run scripts directly (`python scripts/compute.py`) to iterate. Write them recipe-ready from the start: parameterize decisions, write to convention paths, one script per output.
34
+ 2. **Integrate** — Add `recipe:` blocks to outputs in `astra.yaml`. Track with `lc status` (`alias` / `missing` / `stale` / `ok`). Set `container:` at analysis level or per-recipe — pass an image name (e.g., `python:3.12-slim`) or a path to a Containerfile (e.g., `Containerfile`).
35
+ 3. **Materialize** — `lc run` executes recipes inside their declared containers and writes a content-addressed manifest next to each output. Done when `lc status` shows all `ok`.
36
+
37
+ **An output is not done until `lc run` produces it.** Running scripts directly is for debugging only — final results must always come from `lc run` so they are reproducible.
38
+
39
+ ### Spec-Code Invariant
40
+
41
+ **`astra.yaml` must always reflect the code and vice versa.** When you change one, update the other immediately:
42
+ - Add a decision to code? Add it to `astra.yaml` and all universe files.
43
+ - Add an output or change a script? Update the `recipe:` block in `astra.yaml`.
44
+ - Remove or rename something? Update both sides and run `astra validate astra.yaml`.
45
+
46
+ ## Status Interpretation
47
+
48
+ `lc status` shows each declared output's materialization state per universe. Pass `--json` for machine-readable output.
49
+
50
+ - `ok` — Recipe exists, results on disk, manifest matches the current spec. Done.
51
+ - `stale` — Recipe or decisions changed since the last run. Re-run `lc run`.
52
+ - `missing` — Recipe exists but no manifest (never run, or output deleted). Run `lc run`.
53
+ - `alias` — Output has no recipe of its own; produced as a side effect of an upstream output (or a `from:` reference into a sub-analysis). Not independently materializable.
54
+
55
+ ## Failure Diagnosis
56
+
57
+ - **Script arg not recognized** — The recipe's `command` template controls how decisions reach the script. Make sure each `{decisions.<id>}` is paired with a flag the script's argparse defines (e.g. `--<id> {decisions.<id>}` ↔ `parser.add_argument('--<id>')`).
58
+ - **Recipe input not found** — Materialize upstream outputs first.
59
+ - **Undeclared placeholder error** — A `{decisions.<id>}` or `{inputs.<id>}` in the recipe references something not listed in `Output.decisions` / `Output.inputs`. Add it to the Output's declaration, or remove the placeholder.
60
+
61
+ After failure: fix, then `lc run <output_id> --universe <name>`.
@@ -4,7 +4,7 @@ You are inside a lc-build loop (universe: {{UNIVERSE}}). Each iteration: survey,
4
4
 
5
5
  Run these commands and read their output:
6
6
 
7
- 1. `lc status --universe {{UNIVERSE}}` -- what's materialized, what's pending, what has no recipe
7
+ 1. `lc status --universe {{UNIVERSE}}` -- what's materialized (`ok`), missing, stale, or an alias of an upstream output
8
8
  2. `git log --oneline -10` -- what happened recently
9
9
  3. `astra validate astra.yaml` -- is the spec valid
10
10
  4. Read `.lightcone/plans/build-plan-{{UNIVERSE}}.md` -- your implementation plan (cross off completed items as you go)
@@ -24,9 +24,9 @@ Always fix validation errors before doing anything else. Commit. Exit.
24
24
  All outputs are materialized. Time to verify.
25
25
 
26
26
  1. **Inline checks:**
27
- - `astra validate astra.yaml` passes
27
+ - `astra validate astra.yaml` passes (this also catches undeclared `{decisions.X}` / `{inputs.X}` placeholders in recipes)
28
28
  - `lc status --universe {{UNIVERSE}}` shows all `ok`
29
- - Decision-code alignment: `grep -r "add_argument" scripts/` and compare against `astra info --decisions` — every decision must be a parameter, no hardcoded values
29
+ - Decision-code alignment: every decision listed in any `Output.decisions` must be (a) referenced by a `{decisions.<id>}` placeholder in the same Output's recipe command, AND (b) accepted as a parameter by the script the recipe invokes. `astra info --decisions` lists the decisions; `grep -r "add_argument" scripts/` (or whatever the script's parsing convention is) confirms the script side. No hardcoded values.
30
30
  2. **If any issues found:** fix them, re-materialize if needed, commit. Exit (loop continues).
31
31
  3. **If all clean:** Spawn a verification sub-agent with explicit steps (do not rely on skill dispatch — the sub-agent cannot invoke `/lc-verify` directly):
32
32
  ```
@@ -34,7 +34,7 @@ All outputs are materialized. Time to verify.
34
34
  Prompt: "Verify the spec, code, and results all agree for universe {{UNIVERSE}}. Run these checks in order:
35
35
  1. Spec validation: run `astra validate astra.yaml` — must pass with no errors.
36
36
  2. Materialization status: run `lc status --universe {{UNIVERSE}}` — every output must show `ok`.
37
- 3. Decision-code alignment (most important): run `astra info --decisions` and `grep -r 'add_argument' scripts/`. Every decision in the spec must be accepted as a CLI parameter in the code, with no hardcoded values.
37
+ 3. Decision-code alignment (most important): run `astra info --decisions`. For every output that declares decisions in `Output.decisions`, confirm (a) the recipe's `command` references each one via a `{decisions.<id>}` placeholder, and (b) the invoked script accepts each as a parameter (typically via `add_argument`). No hardcoded option values. `astra validate` flags placeholder/declaration mismatches; this check covers the script side.
38
38
  4. Results match spec: for every output in astra.yaml, confirm `results/{{UNIVERSE}}/<output_id>.<ext>` exists and looks well-formed. For `type: metric` outputs, check for valid `{'value': ...}` JSON.
39
39
  Report all findings with file paths and line numbers. If all checks pass, end your report with exactly: VERIFIED"
40
40
  ```
@@ -47,27 +47,26 @@ These are the kinds of work you'll do, guided by the plan. Not a rigid sequence
47
47
 
48
48
  ### Writing scripts
49
49
 
50
- 1. **Write the script.** Parameterize all decisions from `astra.yaml` as command-line arguments (underscore convention: `stellar_mass_cut` → `--stellar_mass_cut`).
50
+ 1. **Write the script.** Make every decision the recipe will pass into it a real CLI parameter (typically `argparse`). The recipe's `command` template is what wires decisions to the script — e.g. `python scripts/fit.py --stellar_mass_cut {decisions.stellar_mass_cut} --output {output}` paired with `parser.add_argument('--stellar_mass_cut')`. Pick the script-side flag names so they match the recipe template; the spec doesn't dictate them.
51
51
  The script must contain real, functional logic that produces genuine results from actual input data. No `# TODO` stubs, no hardcoded dummy values standing in for computation, no `pass` in place of real logic, no synthetic/mock data generation when real data is specified. If you cannot implement the full logic (e.g., missing a library or unclear algorithm), document the blocker in the build plan and move on — do not ship a fake version.
52
- 2. **Test locally:** `python scripts/<name>.py --decision1 value1 --decision2 value2` using values from `universes/{{UNIVERSE}}.yaml`.
52
+ 2. **Test locally:** invoke the script with concrete values, e.g. `python scripts/<name>.py --decision1 value1 --decision2 value2 --output /tmp/check`, using values from `universes/{{UNIVERSE}}.yaml`.
53
53
  Note: manual script runs may write to `results/` but do NOT register as materialized.
54
- Only `lc run` creates the Dagster events that `lc status` recognizes.
55
- 3. **Debug until it works.** Read tracebacks, check imports (`python -c "import module"`), verify decision parameter names match `astra.yaml`.
54
+ Only `lc run` produces the per-output manifests that `lc status` reads.
55
+ 3. **Debug until it works.** Read tracebacks, check imports (`python -c "import module"`), verify the recipe template's `{decisions.<id>}` / `{inputs.<id>}` references match `Output.decisions` / `Output.inputs` (decision/input id, not script flag name).
56
56
  4. **Commit** with a message describing what the script does.
57
57
 
58
58
  ### Adding recipes & materializing
59
59
 
60
- 1. **Add the recipe block** to `astra.yaml` under the output's `recipe:` key.
60
+ 1. **Wire the output.** On the `Output` itself, list its dependencies in `inputs: [...]` and the decisions it consumes in `decisions: [...]`. Then add the `recipe:` block with a `command` template referencing each via `{inputs.<id>}` and `{decisions.<id>}` (and `{output}` for the artifact directory). `astra validate` rejects placeholders that don't match what the Output declares.
61
61
  2. **Validate:** `astra validate astra.yaml`
62
- 3. **Check execution environment:** If the target is SLURM, check `echo $SLURM_JOB_ID`. If empty, you are on a login node — warn the user to start an interactive allocation (`salloc`) before running. Do not submit batch jobs during the build loop; interactive execution is required for fast iteration.
63
- 4. **Run it:** `lc run <OUTPUT> --universe {{UNIVERSE}}`
64
- 5. **If it fails:** Read the error output carefully and diagnose the root cause before retrying. Never re-run the same command without changing something first. Common causes:
62
+ 3. **Run it:** `lc run <OUTPUT> --universe {{UNIVERSE}}`. `lc run` is the only supported way to execute recipes — it handles container resolution, scheduling, and provenance. Run the loop on a machine that can actually execute recipes (the user's laptop with a container runtime, or a compute session they've already opened).
63
+ 4. **If it fails:** Read the error output carefully and diagnose the root cause before retrying. Never re-run the same command without changing something first. Common causes:
65
64
  - Container not built → `lc build`
66
65
  - Upstream not materialized → materialize dependency first
67
66
  - Script error inside container → fix the script, then re-run
68
67
  If a second attempt also fails, note the failure in your commit message and in the build plan, then move on to other work. Come back to it in a later iteration with fresh context.
69
- 6. **If it succeeds:** Verify the result file exists at `results/{{UNIVERSE}}/<output_id>.<ext>` and looks well-formed.
70
- 7. **Commit** with a message noting what was materialized.
68
+ 5. **If it succeeds:** Verify the result file exists at `results/{{UNIVERSE}}/<output_id>/` and looks well-formed.
69
+ 6. **Commit** with a message noting what was materialized.
71
70
 
72
71
  ## Rules
73
72
 
@@ -35,7 +35,7 @@ The user should have provided a description inline (e.g., `/lc-feedback pipeline
35
35
 
36
36
  Triage the repo from context:
37
37
  - **ASTRA** — `astra` CLI, schema validation, YAML parsing, helpers
38
- - **lightcone-cli** — `lc` CLI, Dagster execution, recipes, container builds, scaffolding, skills, telemetry hooks
38
+ - **lightcone-cli** — `lc` CLI, recipe execution, container builds, scaffolding, skills, telemetry hooks
39
39
 
40
40
  Default to **lightcone-cli** if ambiguous.
41
41
 
@@ -53,7 +53,24 @@ For reference, here are the decision criteria for classifying candidates:
53
53
 
54
54
  Write the scan results to `CLAUDE.md` under Analysis Context as a script inventory, then draft `astra.yaml` from the scan results following the spec structure documented in `.claude/guides/astra-reference.md`. Use the decision criteria from [ASTRA Reference](../../guides/astra-reference.md) to filter the subagent's candidate decisions down to only true analytical choices — most hardcoded values are implementation details, not decisions. Use current hardcoded values as defaults.
55
55
 
56
- Include `recipe:` blocks on each output pointing to the script that produces it. Also generate `universes/baseline.yaml` with all defaults matching the current hardcoded values (so the first run reproduces existing behavior).
56
+ For each output, list the upstream artifacts it depends on under `Output.inputs: [...]` and the decisions it consumes under `Output.decisions: [...]`. Then add a `recipe.command` template that references each via `{inputs.<id>}` / `{decisions.<id>}` and writes to `{output}`. Example:
57
+
58
+ ```yaml
59
+ outputs:
60
+ - id: galaxy_catalog
61
+ type: data
62
+ inputs: [survey_data]
63
+ decisions: [magnitude_cut, redshift_min]
64
+ recipe:
65
+ command: >
66
+ python scripts/build_catalog.py
67
+ --survey {inputs.survey_data}
68
+ --magnitude_cut {decisions.magnitude_cut}
69
+ --redshift_min {decisions.redshift_min}
70
+ --output {output}
71
+ ```
72
+
73
+ Also generate `universes/baseline.yaml` with all defaults matching the current hardcoded values (so the first run reproduces existing behavior).
57
74
 
58
75
  Write to `astra.yaml` and `universes/baseline.yaml`, then validate: `astra validate astra.yaml`. Fix any errors.
59
76
 
@@ -74,8 +91,8 @@ Parameterize the code so decisions can be varied across universes. The goal is m
74
91
  Whatever approach you use:
75
92
 
76
93
  - **Don't refactor, restructure, or improve the code.** Just add the parameter plumbing.
77
- - **Underscore convention:** Decision IDs use underscores in `astra.yaml` (`outlier_sigma`). lightcone-cli passes `--outlier_sigma`. Argument parsing must match.
78
- - **Update output paths** to write to `results/{universe}/{output_id}.ext` following the convention in `CLAUDE.md`.
94
+ - **The recipe template is what wires decisions to scripts.** Each `{decisions.<id>}` placeholder in `recipe.command` substitutes the active option ID at runtime, and the recipe author chooses the script-side flag name. Underscore IDs in the spec → match-them-yourself flags in the script (e.g. `--outlier_sigma {decisions.outlier_sigma}` paired with `parser.add_argument('--outlier_sigma')`). There is no auto-injection.
95
+ - **Output paths.** The recipe receives the output directory as `{output}` — pass that through to the script (`--output {output}`) and have the script write its artifact inside that directory (e.g. `{output}/data.parquet`).
79
96
  - **Update recipes** in `astra.yaml` if the entry point or command changed.
80
97
 
81
98
  ## Phase 3: Run & Debug
@@ -68,7 +68,7 @@ For each approved paper: `astra paper add <doi>`, `astra paper path <doi>`, then
68
68
  - **Target decisions**: each decision ID, label, and options with descriptions
69
69
  - **Timestamp**: current time in ISO 8601
70
70
 
71
- The agent type is pre-configured with the user's preferred extraction model (set via `lc setup`). Spawn all in a single message (parallel). Show progress as results come in:
71
+ The agent type is pre-configured with the user's preferred extraction model (the `extraction_model` field in `~/.lightcone/config.yaml`). Spawn all in a single message (parallel). Show progress as results come in:
72
72
 
73
73
  ```
74
74
  ✓ Ba et al. 2016 -- 3 prior insights
@@ -22,11 +22,16 @@ astra validate astra.yaml
22
22
  lc status --universe <universe_id>
23
23
  ```
24
24
 
25
- Every output should show `ok`. Flag anything pending, missing, or without a recipe.
25
+ Every output should show `ok`. Flag anything `missing`, `stale`, or only present as an `alias`.
26
26
 
27
27
  ### 3. Decision-code alignment
28
28
 
29
- **The most important check.** For every decision in `astra.yaml`, confirm the code accepts it as a parameter and does not hardcode its value. Compare `astra info --decisions` against `grep -r "add_argument" scripts/`.
29
+ **The most important check.** For every output, every decision listed in `Output.decisions` must:
30
+
31
+ 1. Appear as a `{decisions.<id>}` placeholder in the same Output's recipe `command`. (`astra validate` enforces this; rerun if the spec changed.)
32
+ 2. Be accepted as a parameter by the script the recipe invokes, with no hardcoded value.
33
+
34
+ Compare `astra info --decisions` against `grep -r "add_argument" scripts/` (or whatever the script's parsing convention is). Cross-check the recipe text to see how each decision is passed.
30
35
 
31
36
  ### 4. Results match spec
32
37
 
@@ -18,9 +18,7 @@ astra.yaml # Specification: decisions, inputs, outputs
18
18
  CLAUDE.md # This file
19
19
  Containerfile # Container image for execution
20
20
  requirements.txt # Python deps (keep in sync with scripts)
21
- .lightcone/ # lightcone-cli internals
22
- lightcone.yaml # lightcone-cli config (default target, etc.)
23
- dagster.yaml # Dagster instance config
21
+ .lightcone/ # lightcone-cli internals (managed by `lc`; do not edit)
24
22
  universes/
25
23
  baseline.yaml # Default decision selections
26
24
  scripts/ # Implementation scripts
@@ -10,13 +10,16 @@ authors = [
10
10
  ]
11
11
 
12
12
  dependencies = [
13
- "astra-tools>=0.2.2",
13
+ "astra-tools>=0.2.5",
14
14
  "click>=8.0",
15
15
  "pyyaml>=6.0",
16
16
  "rich>=13.0",
17
- "dagster>=1.9",
18
- "dagster-webserver>=1.9",
19
- "dagster-docker>=0.25",
17
+ "jinja2>=3.0",
18
+ "snakemake>=9.0",
19
+ "snakemake-interface-executor-plugins>=9.0",
20
+ "snakemake-interface-common>=1.14",
21
+ "dask>=2024.1",
22
+ "distributed>=2024.1",
20
23
  "langfuse>=2.0",
21
24
  ]
22
25
 
@@ -51,13 +54,20 @@ build-backend = "hatchling.build"
51
54
  source = "vcs"
52
55
 
53
56
  [tool.hatch.build.targets.wheel]
54
- packages = ["src/lightcone"]
57
+ packages = [
58
+ "src/lightcone",
59
+ "src/snakemake_executor_plugin_dask",
60
+ ]
55
61
 
56
62
  [tool.hatch.build.targets.wheel.force-include]
57
63
  "claude/lightcone" = "lightcone/cli/claude/lightcone"
58
64
 
59
65
  [tool.hatch.build.targets.sdist]
60
- include = ["src/lightcone", "claude/lightcone"]
66
+ include = [
67
+ "src/lightcone",
68
+ "src/snakemake_executor_plugin_dask",
69
+ "claude/lightcone",
70
+ ]
61
71
 
62
72
  [tool.ruff]
63
73
  target-version = "py311"
@@ -73,5 +83,13 @@ namespace_packages = true
73
83
  explicit_package_bases = true
74
84
  mypy_path = "src"
75
85
 
86
+ [[tool.mypy.overrides]]
87
+ module = ["dask.*", "distributed.*"]
88
+ ignore_missing_imports = true
89
+ follow_untyped_imports = true
90
+
76
91
  [tool.pytest.ini_options]
77
92
  testpaths = ["tests"]
93
+ markers = [
94
+ "slow: tests that spin up real subsystems (dask cluster, etc.) — opt-in via -m slow",
95
+ ]