9. lackpy — Intent to Program with Local Models¶
lackpy turns a natural-language intent into a small, restricted, runnable program. It is “Python that lacks most of Python”: a deliberately narrow language a model can target safely, generated (often by a local model on GPU) and executed under tight controls.
The pipeline¶
intent (NL) ─► generate (local model) ─► validate (restricted grammar) ─► run (sandboxed)
generate — produce a program from intent. The model is configurable (
lackpy.provider_list,config); on this suite’s GPU host it is a local ollama model (e.g.qwen3:14b), so generation is private and cheap.validate — the program must parse within lackpy’s restricted grammar (
ALLOWED_NODES/FORBIDDEN_NODES,ALLOWED_BUILTINS/FORBIDDEN_NAMES). This is a language-level guarantee, not a prompt request.run_program / delegate — execute under a
RestrictedRunner, with a kit of allowed tools and a sandbox (Chapter 12).
CLI: lackpy -c "<intent>" [--generate|--validate|--create]; lackpyctl manages
workspaces, kits, toolboxes, templates, and the MCP server. language_spec documents the
allowed language — read it before assuming a Python feature exists.
Two distributions: language vs runtime (0.12+)¶
As of 0.12, lackpy ships as two PEP 420 namespace
distributions that share the lackpy namespace:
lackpy-lang— the language leaf: grammar, validator, grader, spec. Stdlib-only, no runtime, no model. This is the “language-level guarantee” the validate step relies on.lackpy— the runtime: generation, theRestrictedRunner, kits, the policy chain, the MCP server. Depends onlackpy-lang.
Neither ships a top-level lackpy/__init__.py, so import from submodules — there are no
top-level re-exports:
from lackpy.lang import validate, compute_grade # just the language (lackpy-lang)
from lackpy.service import LackpyService # the runtime (lackpy)
The payoff: a tool that only needs to reason about programs — validate, grade, or share
the language’s vocabulary — can pip install lackpy-lang without the runtime. kibitzer does
exactly this: it imports the shared failure-mode taxonomy from lackpy.lang.failure_modes
(Chapter 8) rather than mirroring it.
Kits and toolboxes¶
A kit is the resolved set of tools/callables a generated program may use; a toolbox
is where those come from. kit_list/kit_info/kit_create, toolbox_list. The kit is
the upper bound on capability: policy can restrict the kit but never grant beyond it.
The policy chain¶
lackpy resolves what a program may do through a chain of policy sources, highest priority first. Three matter here:
kit — ground truth for available tools.
kibitzer — coaching/hints from the session.
umwelt — the world-model policy: it can hide/deny tools and attach constraints (max-level, allow/deny path patterns), read from the same compiled
policy.dbkibitzer uses (Chapter 11). This is the second enforcer of “one policy, two enforcers.”
Why mode matters here. The umwelt source resolves tool policy for the active mode. Originally lackpy was mode-blind — it resolved with no context, so a rule like “deny
Bashin review mode” leaked into every mode and over-restricted. The fix addedmodeto lackpy’sPolicyContextand threaded it into the resolve call, so a generated program is restricted for the mode the agent is actually in. If you wire a new policy source, thread the mode.
When to reach for lackpy¶
When a task is better expressed as “generate a small program to do X over these tools” than as a sequence of your own tool calls — data munging, a structured transform, a one-off analysis — and you want it generated locally and run safely. For ordinary code understanding, stay with squackit; lackpy is for generating and running constrained code.