tracetools

Solveit tools for tracing function execution
from IPython.display import display,Markdown
import textwrap

tracetool is an LLM tool that traces function execution and captures variable snapshots after each line runs, using Python 3.12’s sys.monitoring for low-overhead tracing.

For each call to the target function (including recursive calls), the LLM receives a stack trace showing how that call was reached, plus a per-line trace dict mapping each executed source line to its hit count and variable snapshots. Variables that don’t change are shown as a single (type, repr) tuple, while those that evolve across iterations appear as a list of snapshots.

This is particularly useful when the LLM needs to understand unfamiliar code by seeing exactly what happens step-by-step, debug issues by watching how variables change, verify loop behavior by confirming iteration counts and accumulator values, or explore recursive functions where each call gets its own trace entry.


source

tracetool

def tracetool(
    sym:str, # Dotted symbol path of callable to run
    args:list=None, # Positional args for callable (JSON values passed directly)
    kwargs:dict=None, # Keyword args for callable (JSON values passed directly)
    target_func:str=None, # Dotted symbol path of function to trace (defaults to sym)
)->list[tuple[str, dict[str, tuple[int, dict[str, tuple | list]]]]]: # List of (stack_str,trace_dict); trace_dict maps source snippets to (hit_count, variables), unchanged vars collapsed to single tuple

Trace execution using sys.monitoring (Python 3.12+), returning a list of per-call traces.

Return: - list of length <= 10 - one element per call to target_func (including recursion) - each element is: (stack_str, trace_dict) stack_str: call stack string (filtered so fn is the shallowest frame shown) trace_dict: { “”: ( hit_count, { “var”: [ (type_name, truncated_repr), … up to 10 ], … } ), …}

Semantics: - “Line” means an AST-level line: separate statements (even if on one physical line via ;). - Compound statements are keyed by their header only. - Unchanged variables → ('type', 'repr') tuple, changed variables → [('type', 'repr'), ...] list. - Comprehensions are treated as a line node and are monitored, including inside the comprehension frame, with per-iteration snapshots. - Snapshots are recorded after each line finishes, so assignments show updated values.

def demo(n,m='x'):
    total = 0
    for i in range(n): total += i
    return m*total

Try using &tracetool to trace demo(5, m='y'). Based on the results, tell me how demo works.

def quotefunc(s): return textwrap.wrap('aaa '*10, width=10, subsequent_indent='> ')
tests = 'aaa '*10
quotefunc(tests)
['aaa aaa', '> aaa aaa', '> aaa aaa', '> aaa aaa', '> aaa aaa']

Try tracing textwrap.TextWrapper._wrap_chunks by calling quotefunc.


source

fmt_trace

def fmt_trace(
    traces
):

Format tracetool output as markdown

For a human friendly version, use fmt_trace on the result.

r = tracetool(sym='quotefunc', args=['test'], target_func='textwrap.TextWrapper._wrap_chunks')
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[23], line 1
----> 1 r = tracetool(sym='quotefunc', args=['test'], target_func='textwrap.TextWrapper._wrap_chunks')

Cell In[15], line 29, in tracetool(sym, args, kwargs, target_func)
     25     - Comprehensions are treated as a line node and are monitored, including inside the comprehension frame, with per-iteration snapshots.
     26     - Snapshots are recorded after each line finishes, so assignments show updated values.
     27     """
     28     target = resolve(target_func) if target_func else None
---> 29     traces = tracefunc(resolve(sym), *(args or []), target_func=target, **(kwargs or {}))
     30     return [(stack, {src: (hits, _process(vars)) for src, (hits, vars) in d.items()}) for stack, d in traces]

File ~/aai-ws/tracefunc/tracefunc/core.py:444, in tracefunc(fn, target_func, incl_unhit, *args, **kwargs)
    441         monitoring.free_tool_id(tool_id)
    442     return exc
--> 444 exc = _run_with_monitoring()
    446 # Convert per-call states into the requested output format.
    447 res = TraceResults((st.stack, {k: (v["count"], v["vars"]) for k, v in st.data.items() if v["count"] or incl_unhit}) for st in calls)

File ~/aai-ws/tracefunc/tracefunc/core.py:437, in tracefunc.<locals>._run_with_monitoring()
    434 monitoring.register_callback(tool_id, events.INSTRUCTION, _mon_instruction)
    436 exc = None
--> 437 try: fn(*args, **kwargs)
    438 except Exception as e: exc = e
    439 finally:

Cell In[19], line 1, in quotefunc(s)
----> 1 def quotefunc(s): return textwrap.wrap('aaa '*10, width=10, subsequent_indent='> ')

File ~/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/textwrap.py:384, in wrap(text, width, **kwargs)
    374 """Wrap a single paragraph of text, returning a list of wrapped lines.
    375 
    376 Reformat the single paragraph in 'text' so it fits in lines of no
   (...)    381 wrapping behaviour.
    382 """
    383 w = TextWrapper(width=width, **kwargs)
--> 384 return w.wrap(text)

File ~/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/textwrap.py:359, in TextWrapper.wrap(self, text)
    357 if self.fix_sentence_endings:
    358     self._fix_sentence_endings(chunks)
--> 359 return self._wrap_chunks(chunks)

File ~/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/textwrap.py:288, in TextWrapper._wrap_chunks(self, chunks)
    285     del chunks[-1]
    287 while chunks:
--> 288     l = len(chunks[-1])
    290     # Can at least squeeze this chunk onto the current line.
    291     if cur_len + l <= width:

File ~/aai-ws/tracefunc/tracefunc/core.py:407, in tracefunc.<locals>._run_with_monitoring.<locals>._mon_instruction(code, offset)
    404 call_idx = call_by_fid.get(fid)
    405 if call_idx is None: return
--> 407 pos = _positions_for(code).get(offset)
    408 # Special handling for comprehensions: snapshot per iteration using element/produce opcodes.
    409 if code.co_name in comp_code_names:

File ~/aai-ws/tracefunc/tracefunc/core.py:244, in tracefunc.<locals>._positions_for(code)
    243 def _positions_for(code):
--> 244     return pos_cache.setdefault(code, {ins.offset: (pos.lineno, pos.col_offset) for ins in dis.get_instructions(code) if (pos := getattr(ins, "positions", None)) and pos.lineno is not None and pos.col_offset is not None})

File ~/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/dis.py:753, in _get_instructions_bytes(code, linestarts, line_offset, co_positions, original_code, arg_resolver)
    750 else:
    751     cache_info = None
--> 753 label = arg_resolver.get_label_for_offset(offset) if arg_resolver else None
    754 yield Instruction(_all_opname[op], op, arg, argval, argrepr,
    755                   offset, start_offset, starts_line, line_number,
    756                   label, positions, cache_info)

File ~/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/dis.py:542, in ArgResolver.get_label_for_offset(self, offset)
    539         return argval
    540     return None
--> 542 def get_label_for_offset(self, offset):
    543     return self.labels_map.get(offset, None)
    545 def get_argval_argrepr(self, op, arg, offset):

File ~/aai-ws/kernmini/kernmini/kernel.py:821, in MiniKernel.handle_sigint(self, signum, frame)
    819 if parent.cancel_async_execution(wake=True): return
    820 if not parent.sync_executing.is_set(): return
--> 821 raise KeyboardInterrupt

KeyboardInterrupt: 
Markdown(fmt_trace(r))