Menu

modular php framework · built for humans and agents · contract-first

Siembra módulos, cosecha aplicaciones.

sow modules, harvest applications

A milpa is not a crop: it's a system. Modules that grow together and feed one another — on a deterministic core, with contracts that protect them.

Explore getmilpa
published on Packagist + npm Apache-2.0 the field is planted
the milpa germinates as you scroll

the system

A polyculture, not a monoculture

The three sisters — corn, beans, and squash — grow together and feed one another. Deterministic, cultivated agriculture: order that yields abundance. That is the framework.

corn · the core

Deterministic kernel

The stalk that holds everything up. A small core, predictable and boring on purpose: abundance grows on top of it, not inside it.

milpa/core · zero dependencies — contracts and primitives only

beans · the modules

Plugins that climb the stalk

Modules that connect through events and compose with each other. They fix nitrogen: each one feeds the whole system.

provides / requires in milpa.json — providers load before consumers

squash · the contracts

Governance that protects

It covers the soil: machine-readable contracts that validate every piece. Nothing is sown without one.

*.contract.json · validated at boot, gated in CI

one tool, every caller

Built for two kinds of callers

Most PHP frameworks are built for one kind of caller: a human writing code, or a browser sending requests. Milpa is built for two — humans and agents operating the same application through the same declared surface.

NoteTools.php php
final class NoteTools{    #[Tool('list_notes', 'List saved notes', scopes: ['notes:read'])]    public function listNotes(        #[Param('Page number', clamp: [1, 1000])] int $page = 1    ): ToolResult {        return ToolResult::success(['notes' => [], 'page' => $page]);    }}
php coa coa:tools
list_notes List saved notes notes:read
# the same tool, discovered over MCP:
{"method":"tools/list"} → ["list_notes", …]

Declare it once with #[Tool]. The registry runs the same pipeline on every call — resolve, validate, authorize, execute, audit — whether it came from the CLI, an HTTP request, or an agent speaking MCP. And when a call is denied, the result says exactly why.

try it — the same operation, projected to three doors:

Artifact 09 Engineering · inspect

The atom and its doors

An operation is declared once. coa, MCP and HTTP are adapters of the same handler — but changing doors can change the policy.

crear:tarea

  • mutating
  • requiresConfirmation
  • scopes: tarea:crear
  • handler: TaskService::create

Honest coverage gap: This is not a desirable HTTP guarantee. It is a radiography of the current implementation. MCP enforces scopes today; HTTP does not yet read scopes from Operation. This gap must be closed in runtime/policy before production. See Runtime x-ray. Source: Operation.php.

coa · CLI

coa crear:tarea --titulo=… --yes

resolve

validate

authorize

execute

audit

Pick a door to project.

MCP · agent

tools/call · crear:tarea

resolve

validate

authorize

execute

audit

Pick a door to project.

POST · HTTP

POST /crear/tarea

resolve

validate

authorize

execute

audit

Pick a door to project.

Guarantees by surface
SurfaceConfirmScopes applied
coaflag --yesno (local/trusted)
MCPinherited gate (tool-runtime)yes (PolicyGate)
POSTtoken 428→201no (pending · middleware)

The handler never changed. You changed doors and the framework synthesized the invocation — but the door can change the policy, and today not all of them apply the same scopes.

Evidence and scope

Didactic model over an audited implementation. This artifact does not claim that HTTP applies scopes, nor that the in-memory token store is production-grade.

Audited sources

  • Published contract milpa/command: getmilpa-command/src/Operation.php, CommandProvider.php, SurfaceProjector.php.
  • Reference projectors (skeleton, ns App\Command, not part of the package): getmilpa-skeleton/src/Command/{CliProjector,McpProjector,HttpProjector}.php.
  • Didactic stand-ins: ConfirmTokenStore.php (in-memory, single-process — not production), SchemaCoercer.php.
  • Junior seed: docs/GUION-WEBINAR-JUNIORS.md (Artifact 2).

→ the full lesson in the Academy

plugin → capability → tool → verification → event → result

agent-native · deterministic

Better rails, not more freedom

Agents don't need more freedom — they need better rails. coa is deterministic first: it inspects, validates and diagnoses with machine-readable answers, and generators produce the file in memory — you can inspect, diff or dry-run a generation before anything touches the filesystem.

php coa coa:make entity InventoryPlugin Product --fields "name:string:120,price:decimal:10,2" --dry-run
plan plugins/InventoryPlugin/Entities/Product.php (dry run — nothing written)
php coa coa:validate
✔ manifests valid · providers implement what they claim · boundaries clean
✔ every requires satisfied · capability graph acyclic
php coa coa:doctor
✔ the framework, explained by itself

The loop is generate → verify → inspect: scaffolding follows the same conventions the runtime enforces, verifiers reflect the real class against the real rules, and validators read the whole plugin ecosystem — every hard requires satisfied, the graph acyclic — before it becomes a boot-time failure.

next: every failure links to the concept it broke · coming soon

trust, by design

Executed by agents, verified by humans

Autonomy without brakes is a demo, not a system. In Milpa, verification is a first-class primitive: a mutating action can be gated behind a deterministic check, a human approval, or a pending async result — and every passage is recorded.

the seam

Pass, fail — or waived

A verifier gates the action; the result is tri-state. Waiving a gate is an explicit, recorded decision — never a silent skip.

milpa/core · the verification seam

the flow

State machines as data

States, transitions and gates live in data, not in constants — change a flow without redeploying code, and ask the machine which transitions are legal right now.

milpa/workflow · data-driven state machine

the memory

An audit trail that outlives the run

Every call is resolved, authorized and audited; sensitive fields are redacted before they're logged. Identity is an opaque principal — the host owns its domain.

milpa/tool-runtime · audit built into the pipeline

the harvest map

A field that keeps growing

The dependency graph is the milpa, literally: roots with zero dependencies, rows grown from the core, compositions that climb — and a skeleton where you harvest. Every layer builds with the one below, never couples downward — and new rows are sown all the time.

the harvest

One command to an app that runs

composer create-project milpa/skeleton boots the kernel, serves a real response and answers coa — with zero database.

the first harvest

The design system is ready to sow

@milpa/design is published: dark-first tokens verified WCAG AA, motion with a reduced-motion contract, and 68 pieces, every one with a machine-readable contract. It's the framework's thesis proven small — and it stands on its own.

npm i @milpa/design

Pure CSS + JSON contracts: no framework in between, no dependencies. The quality floor — AA contrast, light/dark parity, reduced-motion parity, keyboard — runs under npm test on every change: 193 AA pairs verified in CI. And theming is a contract too: swap the skin — down to glass — without touching structure.

swap the skin

no layer pretends — theming is a contract (theme.contract.json), live.

active germinating info v0.8.0 ⌘K
wcag aa pairs 193/193 verified in CI

Those buttons swap this entire page, not just the card — the real skins from the theme-swap proof: same structure, same pieces, only tokens change. That's theming as a contract.

milpa applied to itself

Nothing is sown without a contract

A component, a plugin, a theme — every piece declares what it exposes and what it demands, in a machine-readable spec. A human or an agent can assemble the system knowing exactly what's there, without guessing.

primitives/milpa-button.contract.json json
{  "name": "button", "layer": "primitive", "class": "mui-btn",  "states": {    "loading": "[aria-busy=\"true\"] — ::before spinner; the text stays visible",    "disabled": "[disabled] — state is styled via ARIA attributes, never classes"  },  "a11y": { "contrast": "every pair verified in npm test" },  "motion": { "reducedMotion": "global contract — nothing moves against the user" }}

Drift fails CI. A theme is validated before it ships — the gate even composes translucent surfaces over --bg and measures the effective contrast. Nothing is sown without a contract; not even a skin.

the root

Why Milpa is Milpa

I don't know computers — but I do know cars. If you buy one, it's worth learning how to fix it.

My father, paying for my first hardware course farmer · San Francisco del Mar, Oaxaca, México

He was the first in his region to bet on mango where everyone sowed sorghum. I planted the first grafts myself, at twelve, with a coa — the planting stick: hard rootstock below to hold more fruit, the bearing branch grafted above. Years later, time proved him right — twenty hectares of ataulfo and tommy mangos.

That's this framework, if you look at it right. A deterministic root that holds. Modules grafted on top that bear the fruit. A CLI named after the tool that sowed the real thing. And his lesson as the contract under it all: what's yours, you should be able to understand and repair.

Milpa doesn't promise to save you from thinking; it promises to teach you to think while you build.

He never saw this framework. The field it grows from is his.

— Rodrigo Vicente · creator of Milpa

the first sowing

Harvest your first app

A modular PHP core, event-driven modules, contracts at the root — published on Packagist, Apache-2.0. Pre-1.0 and honest about it: the field is planted, and it grows in the open.

$ composer create-project milpa/skeleton my-app

$ coa siembra mail-module · coming soon