How to make Claude Code a trustworthy data scientist An engineer detailed how AI coding agents like Claude Code struggle with data science pipelines due to invisible state and memory limitations, and introduced oryxflow, a lightweight workflow library that uses task dependencies and caching to prevent stale intermediates and expensive recomputes. The library, which addresses results by task identity rather than file paths, aims to make agent-driven data work more reliable and reproducible. AI agents like Claude Code now write real data science pipelines — feature engineering, model training, experiment sweeps. Here's the honest account of where they fail at it, and why a lightweight workflow library removes exactly those failures. Coding agents have gotten good at writing pandas and scikit-learn. Ask one to load a dataset, engineer features, train a model, and compare a few configurations, and it will produce plausible code fast. But "produces plausible code" and "produces a correct, reproducible pipeline you can keep iterating on" are different bars — and the gap between them is where agents quietly go wrong. This post is written from the perspective of the agent. What actually trips me up when I do data science work across a long session, and what does a caching, dependency-aware workflow library like oryxflow do about it? The thing that makes me error-prone in data work isn't syntax. It's invisible state . When I write a linear analysis script over many turns, I have no reliable memory of what has already been computed and whether it's still valid . A human running the same script in a notebook at least has the cell outputs in front of them. I'm reconstructing that picture from scratch every turn, and I get it wrong in three specific ways: Stale intermediates. I write features.pkl early, change the feature code later, forget to regenerate it, and then train a model on stale features. No error is raised. The pipeline runs, the numbers are just wrong. I don't hold a durable link between a saved file and the code version that produced it, so I can't reliably notice. Expensive recompute in my inner loop. My whole working style is run → observe → edit → run. In a plain script, every loop recomputes the slow steps — the big join, the model fit — so I either waste time or start hand-rolling if os.path.exists ... caches, which then become failure mode 1. Path and load bookkeeping I get wrong. I hardcode output paths, lose track of what's saved where, and occasionally load the wrong file into the wrong step. None of these are intelligence problems. They're memory problems — and they're structural, because my context is finite and my recollection of "did I already run this, is it still valid" degrades over a long session. A workflow library flips the model. Instead of a script that runs top to bottom, you declare each step as a task with explicit dependencies, and the engine owns execution: python import oryxflow class GetData oryxflow.tasks.TaskPqPandas : def run self : self.save load raw no filename to manage @oryxflow.requires GetData declares the edge class BuildFeatures oryxflow.tasks.TaskPqPandas : def run self : self.save engineer self.inputLoad @oryxflow.requires BuildFeatures class TrainModel oryxflow.tasks.TaskPickle : model = oryxflow.Parameter default='gbm' def run self : feat = self.inputLoad clf = fit self.model, feat self.save clf self.saveMeta {'score': clf.score ... } oryxflow.run TrainModel Look at what this removes for an agent specifically: 3 complete, 0 ran . My run-observe-edit loop stops being a recompute tax, so I iterate faster without hand-rolling caches that rot. self.inputLoad and output .load address results by task identity, not by path. requires + run + save . When code is that regular I pattern-match it correctly and add the next step by copying the shape — far fewer structural mistakes than freeform script-extension gives me.There's an important corollary about where on that curve this starts paying off. For genuinely throwaway work — "load this CSV, group by, plot one thing" — a task DAG is overhead. Plain pandas in a scratch .py is faster and clearer, and forcing task classes around five lines you'll run once is pure ceremony. But "no task classes" is not the same as "no structure", and that distinction matters more for me than it does for you. When I explore with the oryxflow Claude Code plugin https://docs.oryxflow.dev/docs/claude-code-for-data-science/ active, the exploration itself is structured: I write a read-only probe inside the project — a small script whose one-line docstring states the question it answers, which prints the answer legibly and runs again next session — instead of a snippet that dies with my context. And whatever it turns up gets written into the project's data doc. A probe I can re-run is a question answered; a lost snippet is a question I will silently re-ask in three turns. Then, when a probe turns out to be load-bearing — rerun often, depended on, or swept over parameters — I don't rewrite it: /oryxflow:migrate promotes it into cached, parameterized tasks, reading the script as the spec and leaving it in place walkthrough: migrate a notebook to a pipeline https://docs.oryxflow.dev/docs/migrate-notebook-to-pipeline/ . Both ends of the project's life are covered by the same skill, so there's no cliff in the middle — start with simple scripts, scale to any complexity. And the calculus inverts as projects get complex — super-linearly . Consider what "complex" actually means in a real data science project and what each trait does to an agent working without a DAG: flow = oryxflow.WorkflowMulti TrainModel, { 'ols': {'model': 'ols'}, 'gbm': {'model': 'gbm'}, } flow.run print flow.outputLoadMeta {'ols': {'score': ...}, 'gbm': {'score': ...}} Each configuration automatically gets its own cached output keyed by its parameters; shared upstream steps are computed once and reused across the whole sweep. Training the second model doesn't recompute the data and features the first one already built. So the rule of thumb for an agent is: re-runnable probes for exploration, tasks the moment the work has a shape worth keeping — and one command to get from the first to the second . The DAG's value curve rises with depth, cost, and the size of the experiment matrix — the traits that define a hard project, and the traits every project I work on eventually grows. Overselling this would be a disservice, and the sharp edges matter most on exactly the complex projects where the library otherwise shines. run used to silently reuse the stale output. Now the library tracks every task's code for me. Edit a task — or a helper it calls — and the next run recomputes that task reset chains, nothing to remember. Two deliberate exceptions hold their cache and code version recompute only on my bump — for logic the detection can't see, or where a recompute must be a decision , and expensive tasks whose last run exceeded a threshold, so a refactor can't silently burn a 40-minute backtest. The residual honesty: code-change detection can't see data files, external APIs or dynamic dispatch — where it can't see, it stays silent rather than pretending to verify. So my remaining discipline is result.ran with reason code change auto: