Skip to content
Developer Preview — APIs and language features may change before 1.0

Lesson 10: Metaprogramming

Lesson 10: Metaprogramming

Your email triage works. It is deployed, tested, monitored, and learning from corrections. You analyzed it with interpretation modes. But every one of those analyses treated the machine as a black box you run.

What if a machine were just data you could read, transform, and generate, like any other value?

That is what metaprogramming in mashin is: a machine is a form, and the form.* functions let you parse, inspect, and rewrite forms as ordinary values. There is one rule that makes it safe: manipulating a form is free, but executing one is governed.

A Machine Is Data: form.from_text

form.from_text parses mashinTalk source into a form, a structured value you can navigate:

compute parse_it
let source = """
machine classify_email
implements
ask classify, using: "anthropic:claude-haiku-4"
with task "Classify this email"
returns
priority as text
"""
let f = form.from_text(source)
{machine_form: f}

f is not a string and not a running machine. It is the machine’s structure as data, ready to inspect or transform.

Reading and Rewriting a Form

form.steps(f) lists a form’s steps. form.get(f, path) reads a value at a path, tree-searching by step name so you do not have to spell out every enclosing section: form.get(f, "classify.ask_using") finds the classify step wherever it sits and reads its model. form.set(f, path, value) returns a new form with that value changed, leaving the original untouched. form.diff(a, b) reports exactly what changed between two forms.

Here is a machine that reads another machine, upgrades its model in place, and confirms the change:

machine machine_inspector
responds with
step_count as number
original_model as text
upgraded_model as text
changed as boolean
implements
compute inspect_and_upgrade
let source = """
machine classify_email
implements
ask classify, using: "anthropic:claude-haiku-4"
with task "Classify this email"
returns
priority as text
"""
let base = form.from_text(source)
let step_list = form.steps(base)
let upgraded = form.set(base, "classify.ask_using", "anthropic:claude-sonnet-4")
let changes = form.diff(base, upgraded)
{step_count: step_list.length, original_model: form.get(base, "classify.ask_using"), upgraded_model: form.get(upgraded, "classify.ask_using"), changed: changes.length > 0}
verifies
test "inspects, upgrades the model in place, and diffs the change"
given {}
expect {step_count: 1, original_model: "anthropic:claude-haiku-4", upgraded_model: "anthropic:claude-sonnet-4", changed: true}

form.set produced a new form with Sonnet in place of Haiku, and the base form is unchanged: original_model still reads Haiku, upgraded_model reads Sonnet. form.diff reports the single field that changed. This is real transformation, not string editing. You could run the same upgrade across fifty machines and get a precise diff for each one.

Content-Addressed Identity: form.hash

form.hash(f) gives a form a stable fingerprint. Identical forms hash the same; any change produces a different hash. That is how the evolution ledger tracks versions precisely: a machine’s identity is its structure, not a name or a timestamp.

The Governance Boundary

Everything so far is pure computation. Parsing, inspecting, hashing, and transforming a form are free and run in compute steps, because reading and rewriting data cannot touch the outside world.

Executing a form is different. Running generated code is a real action with real consequences, so it is governed. You cannot call form.eval from a compute step. The compiler stops you:

Error: form.eval is a governed action and cannot be called from a compute step.
Run a generated machine through a governed step instead:
ask result, from: "@system/koda/form_eval"
form_source: my_source

So to run a machine you generated, you go through the governed evaluator like any other action:

machine governed_runner
responds with
output as any
implements
compute build
let src = """
machine adder
accepts
x as number, is required
responds with
y as number
implements
compute add
{y: input.x + 1}
"""
{source: src}
ask evaluated, from: "@system/koda/form_eval"
form_source: steps.build.source
input: {x: 41}
compute done
{output: steps.evaluated}
verifies
test "runs a generated machine through the governed evaluator"
assuming evaluated {y: 42}
given {}
expect {output: {y: 42}}

The machine builds a brand new machine as data, then runs it through @system/koda/form_eval. Because that is a governed step, the execution is checked, recorded, and traced exactly like any other action. The boundary is clean, and the compiler enforces it rather than convention: manipulating forms is pure and free; executing them is governed.

Self-Improvement

Put the pieces together and a machine can improve another machine, or a future version of itself. An improver reads a target’s form, rewrites it (a better prompt, a cheaper model, an added guard), and sends the new version to a governed step that registers it in the evolution ledger:

ask proposal, from: "@system/koda/propose_improvement"
current_source: input.machine_source
goal: "reduce cost without lowering routing accuracy"

The proposal is a new version, hashed and diffed against the old one, that goes through the same promotion pipeline as any change: proposed, verified, promoted. The machine improves, and every step of that improvement is visible and auditable.

What This Means

OperationWhat it doesGoverned?
form.from_textParse source into a formNo (pure)
form.steps / form.getNavigate a form’s structureNo (pure)
form.set / form.diff / form.hashTransform, compare, fingerprintNo (pure)
form.evalExecute a formYes (governed step)
form.proposeRegister a new versionYes (evolution ledger)

Data is free. Execution is governed. That line is enforced by the compiler, not by convention.

Most systems treat self-modification as dangerous. mashin treats it as a governed capability. The same governance that applies to your machines applies to the machines they generate.

The Complete Journey

You started with six lines that classified an email. You added structure, decisions, actions, composition, deployment, goals, tests, memory, interpretation, and now programs that read and write programs.

The email triage system you built is:

  • Live: running on a schedule
  • Connected: Teams notifications, Planner tasks
  • Tested: an automated suite with mocked AI
  • Learning: remembers sender patterns and human corrections
  • Auditable: every run traced, every cost tracked, every action logged
  • Inspectable: its structure is data you can read and analyze
  • Improvable: new versions can be generated, evaluated, and promoted, all governed

All governed. All traceable. All from a language designed for exactly this.