from IPython.display import display,Markdown
import textwraptracetools
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.
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 tupleTrace 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: { “
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*totalTry 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.
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))