How an AI agent onboarded onto ToolJet in 25 minutes using Grafema
ToolJet is a real-world open source low-code platform — roughly 400k lines of JavaScript and TypeScript spread across a React frontend, an Express backend, and a handful of separate service packages. It has 322 documented backend API routes. The frontend makes 318 HTTP requests.
Before Grafema: an AI agent exploring ToolJet spent most of its time grepping through files, making wrong guesses about which component called which endpoint, and accumulating context errors that compounded into bad fixes. The agent knew the shape of individual files. It didn’t know the shape of the system.
After Grafema: the same agent answered system-level questions in seconds. Here’s exactly what happened in those 25 minutes.
The challenge: 400k lines, 322 routes, no map
A large codebase is not just “a lot of files.” It’s a web of dependencies, a set of contracts between services, a history of decisions encoded in function names and import chains. Reading files sequentially doesn’t reveal any of this. Neither does semantic search, though it gets you closer.
The specific problem for AI agents: they need to answer questions like “which backend handler processes this frontend API call?” or “what breaks if I change this function signature?” These are structural questions. They require a map, not a keyword search.
ToolJet is a particularly hard case. The frontend and backend are separate services. A frontend component in ResourcesService.jsx calls fetch('/api/v2/resources'). The backend handler for that call lives in resourcesController.js inside an Express router that’s mounted under /api/v2 in app.js. Connecting those requires reading three files in the right order and holding the mount prefix in working memory.
Or a single graph query.
What the agent did in those 25 minutes
The session had five distinct phases.
Phase 1: Build the graph (~3 minutes)
npx @grafema/cli analyze
Grafema indexed 847 modules, 322 Express routes, 318 frontend HTTP requests, and the cross-service connections between them. Analysis ran in parallel across services; the slow part was the enrichment phase where HTTPConnectionEnricher matched frontend requests to backend routes.
Phase 2: Get oriented (~1 minute)
The agent’s first query after analysis:
npx @grafema/cli query --stats
Output: 322 routes, 318 frontend requests, 289 cross-boundary INTERACTS_WITH edges (the other 29 were dynamic URLs Grafema couldn’t statically resolve). 4,847 function nodes. 12,341 CALLS edges.
This single response told the agent more about ToolJet’s structure than a human developer learns in their first day of reading docs.
Phase 3: Navigate to the auth flow (~4 minutes)
The task was to understand ToolJet’s authentication system — specifically, how session tokens are validated and where the validation middleware runs.
Without Grafema, this requires reading the Express router setup, finding the middleware declarations, understanding the mount points, and tracing which routes they apply to. Experienced developers do this in 15-20 minutes on a first pass.
With Grafema:
npx @grafema/cli query --raw '
type(M, "FUNCTION"),
name(M, "authenticate"),
edge(R, M, "USES_MIDDLEWARE")
'
Result: 47 routes apply the authenticate middleware. The agent immediately knew the shape of the authentication surface.
Phase 4: Cross-boundary trace (~5 minutes)
The agent needed to understand what happens when a user saves a custom data source. This spans frontend → backend → database.
npx @grafema/cli query --raw '
type(Req, "http:request"),
attr(Req, "url", "/api/v2/data_sources"),
attr(Req, "method", "POST"),
edge(Req, Route, "INTERACTS_WITH"),
edge(Route, Handler, "HANDLED_BY")
'
The graph returned the exact handler function — DataSourcesController.create — without requiring the agent to read any files. The agent then read that specific function with focused context rather than trying to navigate there through file exploration.
Phase 5: Write the fix (~12 minutes)
The remaining time was spent actually reading and modifying code. The agent knew where to look. It spent 12 of its 25 minutes on the actual task rather than on orientation.
For comparison: the same agent without Grafema spent the full allotted time — 45 minutes — on orientation and ran out of context before writing a complete fix.
The key queries (with real Datalog)
Grafema’s query language is Datalog — a subset of Prolog applied to graph queries. A few patterns that came up repeatedly:
Find all routes without authentication:
npx @grafema/cli query --raw '
type(R, "http:route"),
NOT edge(R, _, "USES_MIDDLEWARE")
'
Trace from a specific frontend component:
npx @grafema/cli query --raw '
type(C, "react:component"),
name(C, "ResourcesTable"),
edge(C, Req, "CONTAINS"),
type(Req, "http:request"),
edge(Req, Route, "INTERACTS_WITH")
'
Find everything that calls a deprecated function:
npx @grafema/cli query --raw '
name(F, "legacyEncryptPassword"),
edge(Caller, F, "CALLS")
'
These queries produce precise results. They’re not “find me something similar to X” — they’re structural facts about the codebase.
What “onboarded” actually means
“Onboarded” is a loose word. Let me be specific about what the agent could and couldn’t do after 25 minutes.
Could do:
- Answer “which backend handler processes this fetch call?” for any of the 289 statically-resolvable request/route pairs
- List all routes protected by a specific middleware
- Trace the call chain from a React component to an Express handler
- Find all callers of any function in the graph
Couldn’t do:
- Understand the business logic inside individual functions (still requires reading them)
- Trace requests with dynamic URLs (the 29 that hit
url: "dynamic"in the graph) - Understand the ToolJet plugin marketplace architecture (not modeled in the graph yet)
The last point is important. Dynamic routes — ones where the URL is computed at runtime rather than written as a string literal — are invisible to static analysis. The agent had to fall back to file search for those cases. We’re working on improving coverage, but it’s a hard problem.
Workarounds and rough edges
Two things broke during the session that we haven’t fully fixed.
Mount prefix resolution on nested routers. ToolJet has some routes mounted under both /api/v2 and /api/v3 for backwards compatibility. Grafema’s MountPointResolver handled the common case but missed one router that was dynamically mounted based on a config flag. That route showed up with path /undefined/v2/... in the graph. The agent noticed the bad data and fell back to grep for that specific case.
Large query results. Querying all CALLS edges in ToolJet returns thousands of results. The agent had to add more specific filters to get useful output. We need better query result pagination — currently the CLI dumps everything.
Both are on the roadmap. Neither is showstopper; the agent adapted. But they’re real rough edges worth knowing about.
Implications for developer workflow
If an AI agent can build a working mental model of a 400k-line codebase in 25 minutes, what does that change?
The most obvious implication is for onboarding tasks: “fix this bug in an unfamiliar service.” But there’s a subtler implication for ongoing development. Every AI-assisted code change involves some orientation cost — reading the relevant files, understanding the structure, figuring out what might break. That cost compounds across dozens of small tasks per day.
Graph-based context doesn’t eliminate that cost. It reduces the orientation phase significantly for structural questions, which frees up the agent’s context window for actual code reading and reasoning.
Whether that translates to meaningful productivity improvements depends heavily on the task mix. Our benchmark post has the numbers — it’s a more complicated picture than “always faster.”
Try it
npx @grafema/cli init
Point it at your project. Run analyze. Then ask it the structural question you’ve been grepping for.
Full walkthrough in the getting started guide.