Skip to content

Run and read a trace

science-adk run

The runner validates the workflow, creates a run directory, executes the nodes in topological order, and writes everything that happened to trace.json.

config:   period range: 0.1–100000.0 days, up to 5000 planets
fetch:    fetched 3565 confirmed exoplanets from NASA
analyze:  fit 3565 planets: T ∝ a^1.4555, r² = 0.989997
evaluate: recovered exponent 1.4555 vs theoretical 1.5000, error 0.0445

Those lines are the nodes' await self.log(...) calls, printed live and also recorded. --quiet suppresses the printing, not the recording.

Options

Command Effect
science-adk run Run every node.
science-adk run 001-my-hypothesis Run a specific experiment.
science-adk run --only analyze evaluate Run those nodes and their dependents.
science-adk run --reuse Reuse unchanged nodes from the latest run.
science-adk run --reuse 20260820-053012 Reuse from a named run.
science-adk run -q Suppress live log output.

--only

The fast inner loop while you are writing one node. It runs the named nodes and everything downstream of them, so the evaluation still reflects the change.

--reuse

Each node has a fingerprint: a hash of its source, its resolved inputs and its params. --reuse skips any node whose fingerprint is unchanged, marks it cached, and passes its recorded outputs downstream.

config:   cached
fetch:    cached
analyze:  fit 3565 planets: T ∝ a^1.4483, r² = 0.989812
evaluate: recovered exponent 1.4483 vs theoretical 1.5000, error 0.0517

Editing analyze.py re-runs analyze and evaluate, but not the network fetch. Cached nodes still satisfy the all_nodes_ran gate, because their outputs came from a real execution of identical code.

--reuse and non-determinism

A fingerprint covers code, inputs and params — not the outside world. If a data node hits a live API whose contents changed, the fingerprint is unchanged and the stale result is reused. Run without --reuse before auditing.

The run directory

research/001-kepler-exoplanets/runs/20260820-053012/
├── trace.json     # everything that happened
├── score.json     # gates, and pillars once audited
├── REPORT.md      # written by `science-adk report`
└── data/          # spilled datasets, figures, artifacts

Run ids are UTC YYYYMMDD-HHMMSS, suffixed -2, -3 … if two runs start in the same second. Nothing is ever overwritten, so failed runs stay in the record where they belong.

Reading trace.json

Top level:

{
  "run_id": "20260820-053012",
  "experiment": "001-kepler-exoplanets",
  "state": "passed",
  "started_at": "2026-08-20T05:30:12+00:00",
  "finished_at": "2026-08-20T05:30:19+00:00",
  "duration_s": 7.42,
  "error": "",
  "nodes": [ ... ]
}

Each node:

{
  "node_id": "analyze",
  "state": "passed",
  "duration_s": 0.31,
  "outputs": {
    "fit_result": {"exponent": 1.4555, "r_squared": 0.989997, "n_planets": 3565}
  },
  "logs": ["fit 3565 planets: T ∝ a^1.4555, r² = 0.989997"],
  "provenance": {
    "tool_calls": [{"tool": "power_law_fit", "ok": true}],
    "inputs_read": [{"port": "planets", "found": true}],
    "urls_fetched": [],
    "files_written": []
  },
  "fingerprint": "9f2c…",
  "error": "",
  "traceback": ""
}

Node states: passed, failed, cached, skipped, running, pending.

Useful queries

TRACE=research/001-kepler-exoplanets/runs/*/trace.json

# What ran, and how long did it take?
jq '.nodes[] | {node_id, state, duration_s}' $TRACE

# What did the evaluation actually measure?
jq '.nodes[] | select(.node_id=="evaluate") | .outputs' $TRACE

# Which tools were really called?
jq '[.nodes[] | {node: .node_id, tools: .provenance.tool_calls}]' $TRACE

# Why did it fail?
jq '.nodes[] | select(.state=="failed") | {node_id, error, traceback}' $TRACE

Large values are dataset references rather than inline data:

"planets": {"$dataset": "data/planets.parquet", "format": "parquet",
            "shape": [3565, 4], "bytes": 184320}

Open the file in data/ to see the values themselves.

When a node fails

error: analyze failed: KeyError: 'period_days'

The run stops, state becomes failed, and the node's entry carries the error and full traceback. Downstream nodes are recorded as skipped. The execution_completed gate then fails, and the run scores 0.00 — as it should.

jq -r '.nodes[] | select(.state=="failed") | .traceback' $TRACE

The debugging skill is written for exactly this moment.

After the run

science-adk score      # the six deterministic gates
science-adk report     # REPORT.md from the trace
science-adk status     # where the project stands

See also