Skip to content
Redgold.AI Blog

· 8 min read · architecture, APIs, coding agents

Replacing Hand-Written Services With Declarative Routes

Our API now has 562 route definitions written as small TOML files instead of hand-written service code. Here is what a route looks like, how we stopped new hand-written handlers from appearing, and what still did not fit.

Most backends grow by accretion. Each new endpoint is a function in some service, with its own routing, authentication check, error handling and deployment. That is fine with a few engineers. With a fleet of coding agents adding endpoints every day, it produced a long tail of slightly different services, each with its own way of checking who the caller is.

We now declare routes instead of writing them. An endpoint is a small TOML file under pipelines/routes/<domain>/, and the engine turns it into a dataflow that runs on a shared executor. There are 562 of these files today, across 32 domains.

Route definitions in the ten largest domainsRoutesfeedfeed · Routes: 60 routes60integrationsintegrations · Routes: 57 routes57settingssettings · Routes: 54 routes54agentsagents · Routes: 48 routes48taskstasks · Routes: 33 routes33tenanttenant · Routes: 31 routes31vibevibe · Routes: 24 routes24conversationsconversations · Routes: 24 routes24appsapps · Routes: 24 routes24llmllm · Routes: 22 routes22
Route definitions in the ten largest domains. Count of route files per domain folder, October 2026. The remaining 22 domains hold the other 185.
Show the data
Routes
feed60
integrations57
settings54
agents48
tasks33
tenant31
vibe24
conversations24
apps24
llm22

What a route looks like

[[nodes]]
node = "node_status"

[nodes.http]
method = "GET"
path = "/api/node/status"
route_scope = "internal"
requires_auth = true
match_kind = "exact"

[nodes.dataflow.source]
kind = "inline"

[[nodes.dataflow.stages]]
kind = "function"
qualified_name = "dts-fn-node-status"
An abridged route definition. The HTTP contract, its scope and authentication are data; the work is a named function stage.

The file says everything a reviewer needs: the method and path, who may call it, whether it needs authentication, and which stages run. Most routes are reads and writes against engine datasets and need no custom code at all. When a route does need code, it names a function, and CI builds that function, hashes the compiled artifact and pins the hash into the route, so what runs is exactly what was reviewed.

The edge service in front of all this holds no handler code. It authenticates the caller, matches the route and relays the request to the executor. Adding an endpoint is a new file and a function if needed, not a new service with its own deployment.

Safer by default

Declaring routes made the defaults visible, and we used that to change them. Authentication is required unless a route explicitly opts out. The status route above is a small example: the hand-written handler it replaced did not require authentication, and that setting was buried in handler code. In the declarative version it is a line in the file, and the default is to deny.

Stopping new hand-written handlers

Moving existing endpoints takes months. Stopping the problem from growing takes one rule. On June 25 we froze the old path for compiled handler plugins, and a CI check now enforces it as a ratchet: the number of handler crates can only go down. Two more ratchets work the same way, one for routes that still bridge into handler code and one for service API routes defined outside the manifests.

Ratchets matter more with agents than with people. An agent asked to add an endpoint copies the nearest example. If the nearest example is a hand-written handler, you get another one. With the ratchet, that pull request fails CI, and the agent has to write a route instead.

What did not fit, or broke

  • Method-specific routing. Early on, a path was routed as a whole. Publishing one method of a path declaratively, while another method of the same path was still served by an older service, needed method-qualified routing rules. Before that existed, an endpoint to create sharing links had no route at all and returned 404.
  • Things that should stay small and audited. The endpoint that mints one-time access secrets stays hand-written, by a recorded decision. It is short, security-critical and changes rarely; expressing it as a general dataflow would add moving parts without adding safety.
  • The long tail. Hundreds of handler-backed routes were ported by wrapping their old code as a declared function stage. That stops the growth and puts every route behind the same contract, but the code inside some of those functions is still the old code. Declaring a route is not the same as rewriting it.

Would we do it again?

Yes. The biggest gain is not fewer lines of code. It is that every endpoint now has the same shape, so a reviewer, a security audit or an agent can read any route the same way, and the defaults that matter are enforced in one place.