DEV Community

Aman Bhawsar
Aman Bhawsar

Posted on

GovCon Intelligence — an agent that can't answer without real structured data

Sanity Challenge Path One Submission

This is a submission for the Sanity Challenge, Path One: Ship an Agent That Queries Real Content

What I Built

GovCon Intelligence — an agent that answers real questions about US
federal contractors (who works with which agency, how much they've been
paid, whether they're growing or shrinking) by querying a live, structured
Sanity dataset through Sanity Context MCP. Not a summary of documents — a
real relational query over contractor documents that reference
agency documents.

The brief was blunt: "if a keyword search would have gotten you the same
answer, aim higher."
So the question I asked myself building this was: what
can I ask that a flat-text search genuinely cannot answer?

"Which contractors work with the Department of Defense and are not
decreasing in growth?"

That's a join, not a lookup. It needs to filter contractors by a boolean on
their own field (growthTrend != "decreasing") and by whether any of
their referenced agency documents has a specific name. No amount of
full-text search over a JSON blob gets you that. Real result, from real
data, first try:

Contractor DoD amount Growth
Booz Allen Hamilton $42.9B stable
Accenture Federal Services $7.54B stable
Deloitte Consulting $7.16B stable

The data is real, not a fixture: I pulled it live from my own
Federal Contractor Intelligence
Apify actor, which queries USAspending.gov directly, and pushed the output
into Sanity as contractor/agency documents with genuine references
(not flattened strings). Lockheed Martin's $622B lifetime value in the demo
is a real US government spending record.

Demo

Live, no login, no setup: https://govcon-intelligence-seven.vercel.app

Try asking:

  • "Who has the highest lifetime contract value?"
  • "Show me contractors with a declining growth trend"
  • "Which contractors work with NASA?"

Every answer is a live groq_query call against the real dataset — nothing
is canned. The demo runs on a free-tier LLM key (NVIDIA, openai/gpt-oss-20b,
no card required) so anyone judging this can run it without paying for
anything, and it's rate-limited to 15 questions/hour/IP to keep that key
usable for everyone.

Code

Repo is private (I keep all my repos private as a personal practice), so
here's the core of it directly — the whole agent loop is deliberately small:

# agent.py — the loop: ask the LLM, let it call groq_query on the live
# MCP endpoint, feed the real result back, repeat until it has an answer.
def ask(question):
    messages = [{"role": "system", "content": SYSTEM_PROMPT},
                {"role": "user", "content": question}]
    for i in range(6):
        msg = llm_call_openai_format(messages, allow_tools=(i < 5))
        messages.append(msg)
        tool_calls = msg.get("tool_calls")
        if not tool_calls:
            cleaned = _clean(msg.get("content"))
            if cleaned:
                return cleaned
            break
        for tc in tool_calls:
            args = json.loads(tc["function"]["arguments"])
            mcp_result = mcp_call("tools/call",
                                   {"name": "groq_query", "arguments": args})
            tool_text = mcp_result["result"]["content"][0]["text"]
            messages.append({"role": "tool", "tool_call_id": tc["id"],
                              "content": tool_text})
    # Forced final answer, tools off — "no match" is a valid, honest answer
    messages.append({"role": "user", "content":
        "Based only on the query results above, give your final answer now."})
    final = llm_call_openai_format(messages, allow_tools=False)
    return _clean(final.get("content")) or "No matching contractors found."
Enter fullscreen mode Exit fullscreen mode
# mcp_call — talks to the real Sanity Context MCP endpoint over HTTP
def mcp_call(method, params):
    body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method,
                        "params": params}).encode()
    req = urllib.request.Request(
        SANITY_MCP_URL, data=body, method="POST",
        headers={"Authorization": f"Bearer {SANITY_ORG_TOKEN}",
                 "Content-Type": "application/json",
                 "Accept": "application/json, text/event-stream"})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.loads(r.read())
Enter fullscreen mode Exit fullscreen mode

Flask serves a small dark-themed chat UI in front of it (live stats bar,
markdown tables, source citation on every answer) — deployed on Vercel.

How I Used Sanity

Schema is two document types, deliberately kept small so the reference
relationship is the whole point:

// contractor: name, uei, totalLifetimeValue, growthTrend, leadScore,
// exclusionStatus, and topAgencies[] — an array of
// { agency: reference -> agency, amount: number }
//
// agency: name, isDoD
Enter fullscreen mode Exit fullscreen mode

I pointed Sanity Context at my own Sanity dataset (GROQ mode, not a
crawled Knowledge Base — the content already lives in Sanity as real
documents, so there was nothing to crawl) and created an MCP endpoint scoped
to _type in ["contractor", "agency"]. The agent calls two of the tools
Context exposes: initial_context (to learn the schema on first turn) and
groq_query (to actually answer). The system prompt is explicit that it may
only state numbers the tool returned — never estimate.

Three things broke on the way that are worth being honest about:

  1. Sanity Context requires a deployed Studio app, not just a deployed schema — I hit a hard -32004 error until I ran sanity deploy against a Studio actually built on sanity@6 (my first template was v3, which doesn't qualify).
  2. The model I originally wired had been retired from NVIDIA's catalog since I last used it — found the current one by checking the live /v1/models list instead of guessing.
  3. gpt-oss's harmony format occasionally leaked an internal commentary to=functions... fragment into the answer instead of using the structured tool_calls field, and separately could burn all its tool-call turns chasing a genuinely-empty result. Fixed both at the agent layer rather than papering over them in the UI — see the _clean() strip and the forced tools-off final turn in the code above.

Sanity Project Details

Project ID: 6t5rf0s6, dataset production.
MCP endpoint name: govcon-intelligence-agent (GROQ mode).

Agent Session

(Optional — will add a curated Claude Code session transcript via the DEV agent-session uploader before publishing, and remember to hit "Make Public.")

Top comments (0)