← Back to blog

Why there is no agent framework in this codebase

There is no orchestration framework in this system. No LangGraph, no LangChain, no CrewAI. The agent loop is about thirty lines and five dependencies. That was a deliberate decision made twice, and the second time the deciding argument was not the one I expected.

What a framework would give

The pitch is real and worth stating fairly. An orchestration framework offers a graph-based state machine for agent routing, tool-calling abstractions so you are not hand-rolling message handling, checkpointing and replay for long-running work, and a swappable backend layer so you are not married to one model vendor.

That last one is not hypothetical for this project. The production backend has moved vendors since, which is exactly the scenario the abstraction is sold for.

What it costs

A heavy dependency chain, for one — the popular ecosystems pull dozens of packages for functionality you use a fraction of. An abstraction layer over the model SDK that hides what is actually being sent, which is unhelpful when debugging and considerably worse when auditing. Frequent API changes you inherit as upgrade churn. And framework opinions about how tools and agents should relate that may not match the architecture you have.

The core argument: the architecture is too simple to need one

The agent loop is genuinely about thirty lines. Call the model; if it wants tools, execute them; append the results; call again. That is the whole thing, and the supervisor dispatching a specialist is the same pattern one level up, where each "tool" is an analyst invocation.

The shape is hub and spoke. Supervisor calls specialists, specialists call tools, results return. No cycles. No complex conditional routing. No parallel fan-out that has to reconverge with merge semantics. A graph framework's power is in expressing complicated graphs, and this is not a complicated graph — it is a star.

SituationFramework value
Branching with cycles, retries, fan-out and fan-inHigh — the graph DSL saves real work
Hub and spoke with a simple tool-use loopLow — plain code is clearer
Rapid prototyping across many model providersHigh — swappable backends earn their keep
A security tool a small team must audit and maintainRisk — the framework becomes a box you cannot see into

Total dependency footprint on the original build: five packages. The model SDK, a data modelling library, an HTTP client, a terminal output library, and a YAML parser. The framework alone would have added thirty or more.

The argument that actually decided it

The dependency count is a maintenance argument and it is the one usually made. It is not the one that mattered here.

This is a security product that runs inside a customer's tenant with read access to their entire security telemetry estate. Two consequences follow. First, every transitive dependency is supply chain surface in an environment where the blast radius is somebody else's security logs — and a deep dependency tree in that position is very hard to defend in a diligence conversation, regardless of how well it is maintained. Second, a security buyer's central question is what this opaque binary is doing with that access, and any answer that begins "the framework handles that" is not an answer.

The auditability test. Can I show, line by line, exactly what leaves this process and exactly what is sent to the model? With a thirty-line loop, yes. With an abstraction layer in between, I am vouching for someone else's code path in a conversation where my credibility is the product.

The benefit I did not anticipate

Months later the entire codebase moved to a different language for reasons that had nothing to do with this decision — deployment, tamper resistance, and startup behaviour.

A thirty-line loop over a vendor SDK ports in an afternoon. The hard parts of that rewrite were the domain logic, the query builders, and the auth plumbing, exactly as you would expect. What did not appear on the list at all was untangling framework idioms that had no counterpart in the target language, and I am fairly sure that is the version of the rewrite where the project would have quietly stalled.

Thin dependencies bought optionality I did not know I was buying. That is not a reason to choose them — you cannot plan for a benefit you have not foreseen — but it is worth recording as a real effect.

When I would revisit this

The decision is conditional, not ideological. A framework earns its place the moment the architecture needs any of: parallel specialist invocations that must reconverge with real merge semantics; conditional retry loops with backoff and state; investigation checkpointing for cases that outlive a process; or genuinely branching multi-step workflows.

None of those are true today. If two of them become true at once, the calculation changes and I will change with it. The point is not that frameworks are bad. It is that adopting one before your architecture has the complexity it manages means paying the full cost for none of the benefit — and, in a product whose core claim is that you can trust what it does with your data, paying a second time in auditability.

Who is writing this

I am Ivan Melekhin. Twenty-five years in cybersecurity, most of the last decade running security operations — building and operating distributed SOC and MSSP teams across Asia-Pacific, with a long detour through OT and maritime environments. This log is the build record for an autonomous SOC investigation agent I started in January 2026, written as the decisions happened rather than tidied up afterwards. I am on LinkedIn if you want to argue with any of it.