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¶
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:
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.