agent-authoring¶
When this skill applies
Writing an agent¶
One node, one file, one class, one method.
"""Fit the oscillation period from a trajectory."""
from typing import ClassVar
from science_adk import AlgorithmAgent, Port, Ports
class FitPeriod(AlgorithmAgent):
"""Recover the period from zero crossings of the angle."""
assumptions: ClassVar[list[str]] = [
"The motion is undamped, so every cycle has equal duration.",
]
ports: Ports = Ports(
inputs=[Port("trajectory", "dataframe")],
outputs=[Port("period", "float")],
)
async def execute(self):
frame = await self.input("trajectory")
await self.log(f"analysing {len(frame)} samples")
...
return {"period": period}
The file lives at agents/<node-id>.py, matching the node's id in
workflow.json.
The three rules the engine enforces¶
Ports must match the DAG exactly. Same names, both directions. If they
disagree, validate will say so and nothing runs.
Every class attribute needs a type annotation. Agents are pydantic models,
so a bare GRAVITY = 9.81 fails at import with a confusing message about model
fields. Write GRAVITY: ClassVar[float] = 9.81. Better still, put it in
params where the optimizer can reach it.
execute returns exactly the declared outputs. No extras, no omissions, no
None for a required port.
What you get¶
await self.input("name") # a declared input, hydrated
self.param("name", default) # from `params` in workflow.json
await self.call_tool("name", **kw) # a configured tool
await self.fetch(url) # an HTTP GET, with backoff
self.artifact("plot.png") # a path inside this run's data/
await self.log("...") # progress, into the trace
Inputs, tool calls and fetches are recorded in a provenance ledger the agent
cannot write to. That ledger is what makes the audit meaningful, and it is why
tools in workflow.json has to be true.
Returning data¶
Return ordinary Python values. Anything large — a DataFrame, an ndarray, a long
list — is written to data/ automatically and travels onward as a pointer, so
the trace stays readable and the file stays with the run. Downstream nodes
receive the real object, not the pointer.
Parquet, npy and json are the storage formats. There is no pickle path: an artefact only readable by the process that wrote it is not an artefact.
Do not¶
Do not catch exceptions to keep going. A crash is information. A swallowed
crash becomes a fabricated number the moment something downstream uses it. The
validator rejects except: pass.
Do not hard-code a result. If it should be computed, compute it.
Do not read the target. The goal's threshold is deliberately invisible to agent code. An evaluation node that knows the number it must beat is one that will eventually be written to beat it.
Do not call a language model. An agent is a reproducible computation. The reasoning happened when you wrote the code.
Primitives¶
Pick the base class matching the node's kind:
ConfigAgent—self.settings(**defaults)merges declared defaults withparams.DataAgent— setsource_kindto"measured","simulated"or"synthetic", truthfully;check_frame(df, [...])asserts shape early.AlgorithmAgent— populateassumptions.TrainingAgent—self.split(data)andself.seed_everything(). Never evaluate on the training split.EvaluationAgent— returnself.report(metric=..., value=...). It rejects NaN, because a NaN is a failed computation rather than a poor score.VisualizationAgent—self.save_figure(fig, "name").CompositeAgent— setworkflowto a nestedworkflow.json.
Dependencies¶
Anything installed in the environment can be imported. numpy, pandas and matplotlib are common; the engine itself does not require them. If a run fails on a missing import, install it rather than working around it.
Check before running¶
science-adk validate # ports, structure, integrity patterns
science-adk run # execute
science-adk run --only fit # just this node and its dependents
science-adk run --reuse # skip nodes whose inputs and code are unchanged
--reuse is what makes iteration bearable: change one node and only that node
and its descendants re-execute.
When a node fails¶
Read the traceback in trace.json and load the debugging skill. Do not
weaken the node until it passes.