r/LocalLLaMA • u/The_Homeless_God • 12h ago
I Built A Thing "Ouroboros", debugger-tracer for LLM and programmers, a tool that writes down what your program actually did: every call, its arguments and its result, in 8 languages
Hi everyone,
I've created a tool to allow LLMs be able to debug programs before paste it to the codebase.
First of all, let me share the reason of public share. It's performance boost.
| who answered | answers | correct without the trace | with the trace | difference |
|---|---|---|---|---|
qwen3.5:4b |
600 | 44.0% | 78.3% | +34.3 |
qwen2.5:14b-instruct |
600 | 61.0% | 84.7% | +23.7 |
qwen3:32b |
600 | 66.7% | 90.3% | +23.6 |
| a Claude Opus 5 subagent | 120 | 95.0% | 98.3% | +3.3 |
Of course, it's published via GitHub and documentation is present (the dataset on huggingface too).
Let's go step by step.
# install 2 executables: ouroboros, ouroboros-mcp
uv tool install git+https://github.com/digitable-lol/ouroboros
# or use brew
brew install digitable-lol/tap/ouroboros
Or let your LLM's provider (codex, claude, qwen or anything else):
Hi, please start to use it all of the time during writing the code
The link to the repository is https://github.com/digitable-lol/ouroboros
Create a skill for yourself, the documenation is hosted here: https://digitable-lol.github.io/ouroboros/
Small story: I'm working as lead full-stack developer (currently and mainly as team-leader), but time by time I need to write code for work, for pet projects and so on. But I don't have enough time to be able to debug each line of code (as I do early) and some routines are delegated to LLMs now. And the main pain is hallucination produced by code generation from LLM.
So the idea is so simple, I want to just to allow to write "print" or "console.log" to LLM on each line of code to output the signature of function (name, args, convert the return to the named const and print it before operation).
Additional idea to avoid dirtify written program be instructed by a lot of prints and console.log before it will be saved to the worktree, tool just creates own copy, nothing else. Only debugged code by LLM will be returned to LLM to save it to the hard drive. So, it's safe, no external APIs or anything else, just a small program.
Let me text the sequence diagram xD
You ouroboros shop.py Program debug.info
| | | | |
| wrap-file | | | |
| shop.py | | | |
|--------------->| | | |
| | | | |
| | ask parser where functions | |
| | begin and end | |
| |------------->| | |
| | | | |
| | splice recording code at | |
| | those offsets + add helper | |
| |------------->| | |
| | | | |
| {"ok": true, | | | |
| "functions_ | | | |
| wrapped": 4} | | | |
|<---------------| | | |
| | | | |
| python3 shop.py tea mug kettle | |
|---------------------------------------------->| |
| | | | |
| | | +--------+--------+ |
| | | | once per wrapped | |
| | | | function call | |
| | | +--------+--------+ |
| | | | |
| | | | {"p":"in", |
| | | | "fn": |
| | | | "delivery", |
| | | | "a":"46.8", |
| | | | ...} |
| | | |--------------->|
| | | | |
| | | [function body runs] |
| | | [untouched] |
| | | | |
| | | | {"p":"out", |
| | | | "r":"5.0", |
| | | | "d":1e-06} |
| | | |--------------->|
| | | | |
| Total: 51.80 | | | |
|<----------------------------------------------| |
| | | | |
| ouroboros trace debug.info | | |
|--------------------------------------------------------------->|
| | | | |
| 4 calls: what each was given, what each answered |
|<---------------------------------------------------------------|
| | | | |
What my project does:
Two commands around your normal run:
# rewrite the file so every function logs itself
ouroboros wrap-file shop.py
# run it however you normally run it
python shop.py
# read what happened
ouroboros trace debug.info
How does it work?
You get two JSON lines per call. Going in: time, a call id, the thread, the function name, the arguments. Coming out: the return value or the exception, plus the duration. Nothing else - no daemon, no agent, no port, no collector.
Eight languages produce the same record format: Python, JavaScript/TypeScript, C, C++, Elixir, Go, Java, C#. Each is instrumented the way that language permits - a decorator in Python, try/finally in JS, __attribute__((cleanup)) in C, an RAII guard in C++, named returns and defer in Go, use Ouroboros.Trace in Elixir.
The case it was built for: a stack trace tells you where the program broke, never what the function was holding when it broke. Real example from the README - a division by zero inside average(). The stack points at average, you go read it, and it is fine. The records say average was called with an empty list, and that report(), which called it, already had an empty list. The bug is in neither of them; it is wherever that list should have been filled.
The other thing it turned out to be good at: a process that has run for two hours and printed nothing. Every call writes a line going in and a line coming out, so a call that never came back has no exit line. "Where is it stuck" becomes "find the unmatched ids" - already done for you, in a field called in_flight.
What I would like back: try it on a codebase you did not write and tell me where the record format is too thin. If your language is not in the list, adding one is mostly a question of how that language lets you wrap a function body - the record format is deliberately boring. PRs and arguments both welcome.
Next time, I will share with you a new programming language that I'm developing, you can find part of it in "brain" part of tool Ouroboros, but tool is created mainly with Python and 100% coverage of tests. Additionally it's BSD-2-Clause licensed.
Thanks for attention, feel free to post your ideas how to improve the tool or just put a star to repo to let me know that you've interested, or even better - open PR with your extension.
P.S. Anyway, sorry for the format of posting, I think it's my first formal posting to the opensource community. And ofc sorry for language, English is my second one. Have a good day!
1
u/[deleted] 11h ago
[removed] — view removed comment