Agentic coding with Oxagen
Three end-to-end agent threads — performance triage, cross-repo refactor, and stack-trace triage — with the exact MCP tool calls and the response shapes the agent sees.
Agentic coding with Oxagen
Three real agent threads, each one MCP call away from a complete answer
on Oxagen, ten or more tool calls away on a vanilla setup. The MCP
tool names and input fields below are taken verbatim from
mcp.oxagen.ai — see
services/mcp-server/_schemas.py
for the canonical Pydantic shapes.
Prerequisites
- An Oxagen workspace with a GitHub connection that has finished a backfill.
- An Oxagen API key (Settings → API Keys).
- Claude Code, Cursor, VS Code, Windsurf, or Codex with the Oxagen MCP server installed — see MCP Server for one-line installs.
Thread A — "Why is processOrder slow?"
The agent has a function name and a vague performance complaint. It needs the signature, the recent commits that touched the function, the callers, the tests, and the docs that describe behaviour — enough context to form a hypothesis without reading source.
One MCP call on Oxagen
// tool: ontology.explain_function
{
"name": "processOrder"
}The response is the full edge neighbourhood for the function — what it calls, throws, reads, writes, and returns — plus typed records the agent reads directly:
{
"function": {
"id": "a3f2c1d8-...",
"name": "processOrder",
"signature": "async function processOrder(input: OrderInput): Promise<OrderResult>",
"file": "services/api/lib/orders.ts",
"is_exported": true,
"is_async": true,
"kind": "function"
},
"calls": [
{ "id": "b8c1...", "name": "validateCard" },
{ "id": "c4d9...", "name": "chargeStripe" },
{ "id": "d1f8...", "name": "writeOrderRow" }
],
"throws": [
{ "id": "e5b7...", "name": "PaymentError" }
],
"reads": [
{ "id": "f2a8...", "name": "OrderInput" }
],
"writes": [
{ "id": "9b3c...", "name": "Order" }
],
"returns": [
{ "id": "1d6e...", "name": "OrderResult" }
],
"tested_by": [
{ "id": "7a2b...", "name": "processOrder_handles_decline" }
],
"recent_commits": [
{
"sha": "9a4c1f...",
"short_sha": "9a4c1f10",
"message": "fix(api): retry chargeStripe on transient 5xx",
"authored_at": "2026-04-22T14:03:12Z",
"author_name": "Alex Rivera",
"files_changed": 3,
"insertions": 41,
"deletions": 12
}
]
}The agent now has an immediate hypothesis: the recent commit added a
retry around chargeStripe. With one follow-up call —
code.find_callers { target: { node_id: "a3f2c1d8-..." }, depth: 2 } —
it pulls every transitive caller and decides whether the latency lives
in processOrder or upstream.
Note.
recent_commitsrequires the GitHub connection's commit ingestion to have finished — until backfill completes for a repo, the array will be empty. The other fields populate as soon as the symbol is parsed.
The same task on a vanilla coding agent
Without a code graph the agent reproduces the answer from primary sources, paying for every step:
read_file services/api/lib/orders.ts— locate the function.bash grep -rn "processOrder(" services/— find callers.bash git log --follow --oneline -n 5 services/api/lib/orders.ts— recent history.bash git show 9a4c1f10— read the diff.bash grep -rn "PaymentError" services/api/— find error sites.bash grep -rn "describe.*processOrder\|test.*processOrder" services/api/— find tests.read_file× N to actually read each candidate.
Twelve-plus tool calls, a 50 KB context window of file dumps, and the
agent still has to read each blob to assemble what Oxagen returned in
one typed payload. Run the comparison yourself — methodology and
numbers live in
oxagenai/oxagen-evals.
Thread B — "Refactor User.email to User.contactEmail"
The agent must enumerate every reference site across functions, files,
tests, and docs. Regex catches the easy cases and quietly misses the
inconvenient ones — a doc that says the User's email field, a test
that destructures { email } from a fixture, a re-export that aliases
the symbol.
One MCP call on Oxagen
First, anchor on the symbol node:
// tool: ontology.explain_function
// (works for any named symbol — function, class, or type)
{
"name": "User"
}Then traverse the typed edges that constitute a "reference":
// tool: ontology.traverse
{
"from_node_id": "8c4f1a2b-...",
"max_hops": 2,
"edge_types": ["calls", "imports", "references"],
"limit": 200
}The response is an ordered list of paths, shortest-first, with walk-ordered nodes and edges:
{
"paths": [
{
"nodes": [
{ "id": "8c4f...", "type": "code.symbol", "name": "User" },
{ "id": "d1f8...", "type": "code.file", "name": "services/api/routes/users.ts" }
],
"edges": [
{ "type": "imports", "source_id": "d1f8...", "target_id": "8c4f..." }
]
},
{
"nodes": [
{ "id": "8c4f...", "type": "code.symbol", "name": "User" },
{ "id": "e5b7...", "type": "code.doc", "name": "docs/architecture/users.md" }
],
"edges": [
{ "type": "references", "source_id": "e5b7...", "target_id": "8c4f..." }
]
},
{
"nodes": [
{ "id": "8c4f...", "type": "code.symbol", "name": "User" },
{ "id": "7a2b...", "type": "code.function", "name": "test_user_email_is_required" }
],
"edges": [
{ "type": "imports", "source_id": "f2a8...", "target_id": "8c4f..." },
{ "type": "calls", "source_id": "7a2b...", "target_id": "f2a8..." }
]
}
]
}The doc-to-symbol references edge is what catches the markdown that
a regex sweep misses. Once the agent has the paths, it does the rename
deterministically — every site is named in the response, with file
path and line range available on the underlying node properties.
For the reverse direction — every function that mutates the symbol —
ontology.impact_of { name: "User" } returns the inbound reads /
writes / modifies / calculates set in one call.
Note. Internal markdown links materialise as file-to-file
referencesedges betweencode.filenodes — see Code Graph.
Thread C — "Triage a stack trace from production"
A pager fires. The on-call engineer pastes the stack trace into the
agent. The agent must produce a suspect set — the failing function,
its callers, the recent commits that touched any of them — without a
clone or a git log.
One MCP call on Oxagen
Stack trace:
TypeError: Cannot read property 'amount' of undefined
at calculateTotal (services/api/lib/totals.ts:42:18)
at processOrder (services/api/lib/orders.ts:104:24)
at handleCheckout (services/api/routes/checkout.ts:67:11)The agent extracts calculateTotal and runs:
// tool: ontology.traverse
{
"from_node_id": "<resolved-id-for-calculateTotal>",
"max_hops": 3,
"edge_types": ["calls"],
"limit": 100
}The traversal returns every function reachable from calculateTotal
within three hops, walked along calls edges. The agent pairs that
with one more call to gather the recent diffs:
// tool: ontology.ask
{
"prompt": "Which commits in the last 14 days modified calculateTotal, processOrder, or handleCheckout?",
"top_k": 30
}The response cites commit nodes by UUID:
{
"intent": "query",
"answer": {
"narrative": "Three commits touched these functions in the window. 9a4c1f10 (Alex Rivera, 2026-04-22) added a retry around chargeStripe in processOrder. 3b2e8d77 (Sam Park, 2026-04-25) refactored calculateTotal to read from a new line-item shape and is the most recent change to the file mentioned in the stack frame.",
"citations": [
{ "node_id": "3b2e8d77-...", "type": "code.commit", "name": "3b2e8d77" },
{ "node_id": "9a4c1f10-...", "type": "code.commit", "name": "9a4c1f10" }
],
"candidate_count": 17,
"retrieval_mode": "hybrid"
}
}Two MCP calls, deterministic node IDs the on-call engineer can paste
into the dashboard. No clone. No git log. The agent's next step is a
one-line fix or a git revert with a SHA in hand.
Note.
code.commitnodes ship with the code-graph rollout. Until then,ontology.askanswers the same question from semantic context — the commit citations appear post-rollout.
Patterns the threads share
- Identity comes from the bearer token. None of the calls pass
workspace_idoruser_id— the MCP server resolves them from the verified token. Cross-workspace queries return empty. - Node IDs are stable. Once the agent has a UUID for
processOrder, it can cite it for the rest of the session. The graph is deterministic; there is no embedding-hit-or-miss. - Caps are conservative.
max_hopsdefaults to 3 (cap 5),limitdefaults to 50 (cap 500). The MCP server refuses queries that would exceed the cap so agent-side pagination is predictable. - Edges are typed.
calls,imports,references,tests,is_tested_by,throws,is_thrown_byare exact strings. Pass them asedge_typesfilters; do not invent new ones.
Run the evals
The cost and tool-call deltas above are reproducible. The methodology
and the harness live in
oxagenai/oxagen-evals —
clone it, point it at your workspace, run the three threads on Haiku
and on Opus, compare the cost-per-query and accuracy numbers in the
generated report. Numbers we cite anywhere in the docs come from this
harness; if they shift, the docs page shifts with them.
Where to go next
- Code Graph — the canonical reference for nodes, edges, and the agent query.
- Cheaper models with Oxagen — the eval methodology and the cost argument.
- Agent Memory — how the agent's past sessions feed back into the next call's context.