Skip to content

debugging

When this skill applies

When a run fails

A failure is information. The job is to find out what the code actually did, not to make the red text go away — those are different goals and they diverge quickly under time pressure.

Read the evidence

science-adk score            # which gates failed, and why
cat research/<exp>/runs/<run>/trace.json

The trace holds each node's state, its error, the full traceback, its logs and its recorded provenance. The failing node's traceback is nearly always enough.

The failures you will actually hit

class attribute X has no type annotation Agents are pydantic models. Write X: ClassVar[float] = 9.81, or move the value into params.

input ports [...] do not match workflow.json [...] The class and the DAG disagree. Decide which is right and change the other.

Required input 'x' of 'node' has no value No edge feeds that port, or the upstream node failed. Check the trace for the node above it.

Node 'x' needs 'y.z', which was never produced An upstream node failed, so its output never reached the bus. Fix the upstream failure; this one is a symptom.

Unknown tool 'x' Run science-adk tools to see what is actually configured. Either the name is wrong or the provider is not set up in science.toml.

declared tools but made no successful tool call The node claims a tool in workflow.json and never called it. Either call it, or remove the claim — but be sure the data really came from where the code implies.

ModuleNotFoundError Install the dependency. Do not restructure the science around a missing import.

Iterate cheaply

Re-running everything to test one fix wastes time and blurs cause and effect:

science-adk run --only <node> --reuse

This re-executes only that node and its dependents, reusing everything upstream whose code and inputs are unchanged. Fingerprints make the decision, so a "cached" node really is unchanged.

Diagnose before editing

State what you think is wrong before you change anything, then confirm it. The alternative — adjusting things until the error moves — reliably produces code that passes for reasons nobody understands.

To inspect a node in isolation, import it and run it directly:

import asyncio
from science_adk import load_agent_class

Agent = load_agent_class(Path("research/exp/agents/fit.py"))
agent = Agent(name="fit", params={"n": 10}).bind(data=my_frame)
print(asyncio.run(agent.run()))

Fixes that are not fixes

Wrapping it in try/except. Now it fails silently and the number downstream is fiction. The validator rejects the worst forms of this.

Returning a default when the computation fails. Same problem, harder to spot later.

Loosening the target until it passes. The target describes what would answer the question. Changing it changes the question.

Replacing the hard part with something simpler that returns a plausible number. This converts a failed experiment into a false one.

Deleting the failing node. If it was in the DAG it was doing something. If it genuinely was not, say so and explain why.

When the result looks wrong rather than crashing

A run that completes can still be broken, and this is the more dangerous case. Suspect:

  • Units — the most common silent error in scientific code.
  • Sign and direction — minimising what should be maximised.
  • Off-by-one in a window, index or split.
  • Leakage — test data seen during fitting.
  • Silent truncation — an empty filter result that produces a valid-looking aggregate over nothing.

Check magnitudes against known values. If a number is off by roughly 60, 3600, π or a power of ten, that is a strong hint about where to look.

Knowing when to stop

If you have made several attempts without understanding the cause, stop and report: what fails, what you tried, what you ruled out, and what you would try next. That is a useful contribution. Continuing to mutate code until it passes is not — and the result, if it ever goes green, will not be worth anything.