small gleam coding and persistent agent daemon with a detachable cli and a canvas ui albedo.sh

Python-kernel: Definition-source snapshots omit decorators despite the documented contract #233

Closed aly.codes
Closed opened by aly.codes

_definition_changes() captures function/class source with ast.get_source_segment(source, statement). A FunctionDef, AsyncFunctionDef or ClassDef span begins at the definition, not at its preceding decorator lines, so the captured source omits decorators.

robot-docs/kernel-state.md currently says: “Decorators are included.” The implementation and that contract disagree.

Source checked #

  • Initial study: 62226db9790f07f6b3cdfe49cd94a3fc5212b087
  • Rechecked against public HEAD on November 10, 2026: 5f90049d9339a02a2c9ce6834ac3ec76d6039ccd; the helper and documentation are unchanged.
  • priv/python/albedo_cells.py, _definition_changes, lines 1083–1122; the relevant call is line 1118.
  • Source-file SHA256 in both checked revisions: 8e5c6b77db736ed1247950a8bb0f8b38e1e7aca86c301e4eff7f11b889fd6288.

Minimal reproduction #

This executes only the two existing pure capture helpers from a local checkout, not the Albedo module, daemon or kernel:

import ast
from pathlib import Path

source = Path("priv/python/albedo_cells.py").read_text()
tree = ast.parse(source)
helpers = [
    node for node in tree.body
    if isinstance(node, ast.FunctionDef)
    and node.name in {"_bound_names", "_definition_changes"}
]
assert len(helpers) == 2
scope = {"ast": ast}
exec(compile(ast.Module(body=helpers, type_ignores=[]), "capture-helpers", "exec"), scope)

cell = "@decorate\ndef sample():\n    return 7\n"
changes = scope["_definition_changes"](cell, ast.parse(cell))
captured = next(row[2] for row in changes if "sample" in row[1])
print(captured)
assert "@decorate" in captured, "decorator source was lost"

Observed output, followed by the failing assertion:

def sample():
    return 7

Reproduced using Python 3.12.3 and Python 3.13.16. The same tiny helper probe was repeated against the newer HEAD above.

Expected and possible consequence #

If the documented source-restoration contract is intended, retain the full definition including decorators. Replaying the current captured segment can replace a decorated binding with an undecorated one—for example, dropping a wrapper's behavior or a dataclass transformation—while successful name restoration alone may not expose that semantic change.

Scope: this report directly reproduces source-capture loss. I have not executed an end-to-end snapshot/restore test. It is not a claim that every restoration fails: dill-loaded values, failed replay and deferred-value paths can change particular outcomes.

Suggested regression coverage #

  • Decorated synchronous and asynchronous functions, and decorated classes.
  • Multiple/multiline decorators and top-level indentation/source-span handling.
  • A restore test whose decorated and undecorated bindings behave differently, not merely a check that the name exists.
  • Preserve the existing trust/side-effect model of definition replay; do not treat replay as a security sandbox.

A possible implementation direction is to take the source span from the first decorator's line (including its @) through the definition's end, rather than the definition node's default span. If decorator omission is intentional instead, document the limitation and avoid implying full definition fidelity.