Inspiration

We initially registered for this hackathon with our friend, Aiden. However, Aiden was struck by a car last night while on a Lime scooter and is in the hospital. Aiden quit his job at Google and hasn't started his new job yet, so he does not have insurance. We built this tool to help him negotiate his medical bills.

What it does

Jaul calls hospital billing departments on a patient's behalf to surface financial-assistance programs like income-based discounts, charity care, and interest-free payment plans that hospitals rarely advertise.

How we built it

It's built in Jac using Object-Spatial Programming: each negotiation tactic is a node, each rep response is classified onto a typed edge, and a walker traverses that graph live during the call, deciding its next move in real time rather than following a fixed script. Two LLM calls do the real work: one classifies what the billing rep just said, the other generates the agent's next line, grounded in real 501(r) hospital policy language so the pitch is accurate.

Challenges we ran into

So many:

Architecture

  • Two feature branches (sft and upgrade-1-dynamic-graph) independently rewrote the same core files around incompatible premises: one used a fixed canonical graph, the other built nodes dynamically at runtime. Reconciling them required a deliberate architectural choice (dynamic graph won) rather than a mechanical merge.
  • The graph itself is inherently unpredictable: the walker can't know which edge it'll take until the rep actually responds, so nothing about the negotiation could be pre-scripted or unit-tested against a fixed path. Therefore, testing needed a MockLLM feeding deterministic outputs per call.

Jac language quirks

  • self is implicit in obj/node/edge/walker methods but must be explicitly typed in class archetypes — omitting it compiles clean and only fails at runtime.
  • A compiler bug: double-interpolation f-strings adjacent to literal quotes silently corrupt output (lost quote boundaries in TwiML XML), invisible unless you assert on exact byte output instead of substring checks.
  • jac check false-positives on @contextlib.contextmanager functions taking parameters, flagging valid calls as errors. worked around with plain obj-based context managers.
  • Relative imports only work for files loaded as package modules, not files run directly via jac run/jac test - silently breaks at runtime despite passing static checks.
  • Monkeypatching strictly-typed attributes for tests needed setattr() since direct assignment fails Jac's type checker.

Toolchain

  • Two non-interchangeable Jac distributions exist (pip jaclang package vs. the standalone jac binary) with different import behavior for byllm and different support for global. Picking wrong would've broken the FastAPI/Jac interop uvicorn depends on.

Every one of these got fed back into CLAUDE.md as it was found, to assist Jac's young tooling/docs (see the working jac codebase section below).

Accomplishments that we're proud of

Genuine Graph Traversal A lot of hackathon projects claim "we used a graph" but really mean a dictionary lookup. Here the walker actually doesn't know its next edge until the rep speaks. The negotiation tactic sequence is discovered live via by llm() classification. That's the actual thesis of the OSP pitch (graph traversal is the logic, not a wrapper around if/else), and it survived contact with a real architectural fork (sft vs. upgrade-1) without getting watered down into something safer.

Cross-call learning: graph-native and tuned Hospital/CallOutcome/PlacedCall persistence means the agent accumulates experience about a specific hospital across calls: which tactics worked and which stalled, using the root graph itself as the database. Interpetability was key! We used this to tune the instructions by having the agent generate synthetic scenarios and train itself through self-play, observing what worked and didn't, based on new research in coding agent optimization. Codebases are kind of like insurance haggling! This tuning took simulated success rate from 45% to 54% and it exhibited qualitatively better reasoning (more accurate steps).

Grounded in real policy, not just confident LLM prose ProgramKnowledge and 501(r) grounding mean the agent's pitch is backed by actual hospital financial-assistance law.

A near-total, working Jac codebase Pushing the webhook server itself into Jac, including making raw-XML TwiML responses work through Jac's Python interop, went past what was strictly necessary and into genuinely uncharted territory for the language. In the process we shook out and documented six real compiler/tooling gaps (f-string corruption, checker false-positives, self-typing rules, import scoping) that weren't written down anywhere before.

Discipline under uncertainty When the two branches turned out incompatible, the response was to stop and ask rather than silently pick one. When Jac's native server conflicted with Twilio's contract, the decision was grounded in actual doc lookups (jac-sv-endpoints/jac-sv-deploy) rather than guessed. And every conversion step got verified with both jac check and real runtime execution, which is exactly how the f-string and import bugs got caught before they hit production.

What we learned

About Jac specifically

  • The tooling is young enough that jac check passing is necessary but not sufficient. Several real bugs (f-string quote corruption, the contextmanager checker false-positive, relative-import scoping) only showed up at actual runtime. Static checks and runtime tests each caught things the other missed; neither alone was enough.
  • obj/node/edge/walker and class archetypes have genuinely different semantics under the hood (implicit vs. explicit self), not just stylistic differences, which is worth knowing before reaching for class out of habit.
  • Jac interops with the Python ecosystem (FastAPI decorators, ABC subclassing, requests) better than expected once you know the seams, but the native server layer (walker:pub) is opinionated in ways that make it wrong for anything with a fixed wire contract like Twilio's TwiML.
  • by postinit and spawn-as-operator are more capable than the docs suggest, enough to write a fully idiomatic constructor and a Python-bridge-free telephony core, which wasn't obviously guaranteed going in.

Architecture

  • Committing to "the graph traversal IS the logic" as a real constraint is what makes the two-branch conflict (canonical vs. dynamic graph) a genuine either/or rather than something mergeable, a shallower design would let both branches coexist.
  • Grounding generation in real facts mattered more than expected for making the demo feel legitimate

What's next for jaul

Depends if we win.

Built With

Share this project:

Updates

Submission history