Part of Underpass AI — memory, coordination, and execution infrastructure for reliable AI agents.
MADE is a ceremony-driven multi-agent deliberation engine. The name is the description: Multi-Agent Deliberation Engine.
Event-driven coordination plane for councils of specialist agents. It runs structured deliberations (propose → peer-critique → revise → validate → score → winner) and longer declarative YAML ceremonies as explicit state machines, enforces output contracts on what agents produce, can score with an LLM judge, and ships observability that shows why an answer won. Domain- and provider-agnostic (vLLM, Anthropic, OpenAI, local, rule-based, human-in-the-loop). Kubernetes-first.
Lineage: started as a domain-agnostic Rust port of the swe-ai-fleet
orchestrator service, and has since grown well beyond that port. It shipped
as Underpass Choreographer until the rename to MADE; that was a naming
change only — see CHANGELOG.md for every moved surface
(crates, proto package, MCP tools, environment variables, chart, image).
MADE ships as two editions. They share made-core, made-app, the domain
invariants and the workspace release version; the embedded facade calls the same
application use cases the deployable binary calls. Full comparison:
docs/editions.md.
The ceremony engine runs in-process over MCP stdio. No service, no gRPC, no NATS, no database, no provider credentials.
cargo install made-mcpThe embedded backend requires a state file: where ceremonies survive a restart is an operator decision, never a default this crate invents.
mkdir -p "${XDG_STATE_HOME:-$HOME/.local/state}/underpass-made"
MADE_MCP_BACKEND=embedded \
MADE_MCP_REDB_PATH="${XDG_STATE_HOME:-$HOME/.local/state}/underpass-made/ceremonies.redb" \
made-mcpClaude Code:
claude mcp add made --scope user \
--env MADE_MCP_BACKEND=embedded \
--env MADE_MCP_REDB_PATH="$HOME/.local/state/underpass-made/ceremonies.redb" \
-- ~/.cargo/bin/made-mcpCodex CLI (~/.codex/config.toml):
[mcp_servers.made]
command = "/home/YOU/.cargo/bin/made-mcp"
env = { MADE_MCP_BACKEND = "embedded", MADE_MCP_REDB_PATH = "/home/YOU/.local/state/underpass-made/ceremonies.redb" }Note that both registrations above name the same store. The default engine
takes one process at a time, so running both hosts at once means the second
one gets no ceremony tools at all. To share one store between them, build with
--features sqlite and either start there or convert what you already have —
sharing one ceremony store between two agent hosts
has the recipe and what it costs.
Want the design-ceremony and run-ceremony skills too — install the
MADE plugin. In Claude Code that is one line:
/plugin marketplace add underpass-ai/plugins
/plugin install made@underpass
Its launcher picks the state path for you and imports a pre-rename
Choreographer store on first start, so none of the environment above is
needed. It runs bin/made-mcp from a release package when there is one, and
otherwise falls back to the made-mcp you installed on PATH. The same
marketplace also carries the sibling kmp@underpass.
Read capability truth before you build on what the embedded surface reports.
The full underpass.made.v1 gRPC contract, optional NATS messaging and
Postgres persistence, provider-backed agents, the LLM judge, Prometheus metrics
and OTLP traces. Helm chart with pinned images and hardened manifests.
MADE_NATS_ENABLED=false MADE_SEED_SPECIALTIES=triage just run
# gRPC on localhost:50055, in-memory persistence, one exercisable councilDeploy guide: docs/operations/deploy-kubernetes.md.
- Structured deliberation — a strict one-way FSM per council run
(
Proposing → Revising → Validating → Scoring → Completed) with deterministic peer critique between agents and a bounded, total-order score for ranking. - Declarative ceremonies — longer multi-step meetings defined in YAML as explicit state machines (states, transitions, guards, retries, leases). The winning contribution of every step is an API artifact — a meeting record with a Mermaid diagram of the conversation — not a log line.
- Output contracts — JSON Schema plus field rules enforced by shipped
validators, with deterministic rejection (
NoValidProposal) when no proposal satisfies the contract: unsupported output does not become a decision. - Optional LLM-as-judge — judge-aware scoring with fail-fast configuration, plus a judge discrimination metric that tells you whether the judge actually re-ranks proposals or just burns tokens.
- Deliberation-native observability — every deliberation is a replayable
OpenTelemetry trace (the debate itself, span by span, exported over mTLS)
and Prometheus metrics designed for this domain: winner-score
distribution,
NoValidProposalrate, per-step ceremony outcomes. Seedocs/made-observability-design.md. - Two surfaces — a contract-first gRPC API, and a stdio MCP server exposing the same RPCs 1:1 to coding agents (Codex CLI, Claude Desktop), with embedded-only ceremony controls and read-only Markdown reports where no remote RPC exists.
“MADE supports X” is incomplete unless the statement identifies the running distribution and backend, the tools exposed by that executable, who performs external work, and which state survives a restart.
For MCP sessions, inspect the active tools/list result and start with
made_discover_capabilities when it is available. Its backend-filtered
catalog is authoritative for the installed executable surface. Discovery does
not prove that a real step handler, durable store, credentials, or external
authority have been configured.
See the capability-verification runbook before documenting or automating an integration.
Three planes, three repos:
| Plane | Repo | Brand name | Role |
|---|---|---|---|
| Memory + context | kmp |
Underpass KMP (Kernel Memory Plane / Kernel Memory Protocol) | One possible producer of LLM-ready context bundles from a typed knowledge graph. |
| Coordination | this repo | MADE by Underpass (Multi-Agent Deliberation Engine) | Composes councils, runs deliberations, validates outputs, hands winners to an executor. |
| Execution + governed tools | underpass-runtime |
Underpass Runtime | Sessions, governed tool invocations, artifacts, policy decisions. |
MADE is agnostic and independently usable. It does not depend
on KMP, PIR, or any downstream product. It accepts caller-supplied
ExternalContextBundles from any context source; KMP is one studied
producer, not a required dependency. Runtime execution is optional via
the RuntimeExecutor adapter. MADE does not embed any
product vocabulary (no stories, plans, incidents, claims hardcoded) —
all that is injected via configuration and proto messages.
docs/index.md— full navigation hub for every doc in this repo, grouped by audience.docs/dev-loop.md— local iteration loop, every command mirrors a CI gate.docs/release.md— versioning + cut-a-release checklist.CHANGELOG.md— unreleased changes and release-note discipline.CONTRIBUTING.md— contribution workflow, required gates, contract rules, and PR expectations.SECURITY.md— supported security scope, vulnerability reporting, and deployment hardening baseline.docs/operations/deploy-kubernetes.md— Helm install guide, including minimal standalone install and embedded NATS, TLS/mTLS, Postgres secret, provider env secrets, and Runtime executor options.docs/operations/support-matrix.md— supported Rust toolchain and release-support rules.docs/PRINCIPLES.md— honesty discipline.docs/experiments/— append-only lab notebook (baselines, scale sweeps, null results).docs/operations/mcp-stdio.md— installable stdio MCP adapter exposing the gRPC API to coding agents (Codex CLI, Claude Desktop).docs/embedded-made.md— in-process ceremony engine, host adapter injection, and the boundary between embedded and deployable distributions.docs/operations/codex-plugin.md— cumulative test ladder and local Codex plugin packaging.docs/operations/ceremony-authoring-runbook.md— writing ceremony YAML: schema keys, rounds, sizing, output contracts, and verification.docs/operations/observability-runbook.md— wiring traces, metrics, and logs in a deployment.docs/backlog.md— epic-by-epic readiness status + session log.justfileat the repo root —justlists every recipe.
Installation and host wiring are in Start here; this section is the developer loop for running the pieces from a checkout.
The service, with nothing behind it. In-memory persistence, noop messaging,
the default noop executor. No NATS, Postgres, Runtime, KMP, PIR, or provider
credentials. MADE_SEED_SPECIALTIES=triage makes it exercisable end to end
immediately.
MADE_NATS_ENABLED=false MADE_SEED_SPECIALTIES=triage just run
# without just:
MADE_NATS_ENABLED=false MADE_SEED_SPECIALTIES=triage cargo run --locked -p madeThe real ceremony engine over MCP, no service. The embedded backend
fail-fasts without MADE_MCP_REDB_PATH: where ceremony state survives a
restart is an operator decision, never a default this crate invents.
MADE_MCP_BACKEND=embedded \
MADE_MCP_REDB_PATH="$PWD/target/ceremonies.redb" \
cargo run --locked -p made-mcpMCP client wiring with no engine at all. Fixture mode returns deterministic canned responses; it must be selected explicitly and is not a live integration test.
MADE_MCP_BACKEND=fixture made-mcpMCP against the local service from a second terminal:
MADE_MCP_GRPC_ENDPOINT=http://127.0.0.1:50055 made-mcpBackend matrix and TLS configuration:
docs/operations/mcp-stdio.md.
| Crate | Purpose |
|---|---|
made-core |
Domain types, ports, events. No IO. |
made-app |
Use cases / application services. |
made-adapters |
NATS, gRPC clients, config, external integrations. |
made-embedded |
In-process ceremony facade with local defaults and injectable port adapters. |
made-proto |
Tonic-generated gRPC code (underpass.made.v1). |
made-mcp-proto |
Vendored underpass.made.v1 proto crate used to publish made-mcp independently. |
made |
Binary: wires adapters, runs gRPC + NATS. |
made-mcp |
Stdio MCP adapter with live-gRPC, embedded ceremony, and deterministic fixture backends. |
made-e2e-runner |
Operator + E2E driver binaries (incl. made-run-ceremony) that exercise the service over its public gRPC surface. |
made-tests-integration |
Integration tests backed by testcontainers-managed services. Not shipped. |
made-consumer-smoke |
Standalone NATS consumer smoke check. Not shipped. |
This project follows the same discipline as its siblings
underpass-runtime and
kmp:
- Honest documentation. No marketing claims in code, docs, or commit messages. If a capability is not implemented and exercised, it is not described as if it were. "Planned", "in progress", and "prototype" are said out loud.
- Everything is demonstrable and measurable. Any claim about behaviour, performance, or quality must be backed by a reproducible test, benchmark, or experiment that lives in this repository and runs in CI. No hand-wave numbers. No unsubstantiated quality claims.
- Scientific method for iteration. Changes that alter behaviour
follow: (1) hypothesis, (2) experiment design, (3) measurement,
(4) result, (5) conclusion — recorded under
docs/experiments/. We keep null results too. - Use-case agnostic. No vocabulary of any particular domain (software engineering, clinical, supply chain, …) leaks into MADE.
- Provider-agnostic. No LLM vendor (vLLM, Anthropic, OpenAI, local, rule-based, human-in-the-loop) is privileged over any other.
- API-first. The gRPC (
crates/made-proto/proto/…) and AsyncAPI (specs/asyncapi/…) specifications are the source of truth. Generated code follows; breaking changes must be detected by the contract gate before any Rust code compiles. - Distribution via containers and Helm. Images are built under
Dockerfile(podman and docker supported); deployment is via the Helm chart undercharts/made/.- Pinned images only (a
latesttag is refused unlessdevelopment.allowMutableImageTagsis set) - Non-root pod + container security contexts (runAsNonRoot,
readOnlyRootFilesystem,
ALLcapabilities dropped,seccompProfile: RuntimeDefault) automountServiceAccountToken: false(the binary does not call the Kubernetes API)- emptyDir on
/tmpso any library tempfile write survives the read-only root filesystem networkPolicy.enabledopt-in restricts inbound to the pod's declared ports and outbound to DNS, NATS, Postgres, and OTLP (plus any extra rules operators add)MADE_POSTGRES_URLsourceable viavalueFrom.secretKeyRefso the DSN never lands in values files- Optional
PodDisruptionBudgetgated onpdb.enabled - Chart-render CI (
scripts/ci/helm-lint.sh) exercises every hardening feature and refuses a manifest that drops one.
- Pinned images only (a
- Unit coverage: minimum 80 % of lines, target band 80–90 %, enforced
by
scripts/ci/rust-coverage.sh. - Integration tests: testcontainers-backed, real services per run (no mocks at the integration boundary).
- End-to-end tests: a runner container drives scenarios either via
docker composeor as a KubernetesJobagainst a kind cluster with the Helm chart installed (contract-true path). Both paths are manual only, launched from the repository withmake e2e-composeormake e2e-kubernetes— the per-PR gates (clippy,test,contract,integration-nats,integration-postgres,container-image,helm-chart) already cover the compile-and-unit surface; E2E is reserved for pre- release validation.
What runs today (enforced by CI, every claim is backed by a test or gate in this repository):
madebinary starts, reads config fromMADE_*env vars, and serves the fullunderpass.made.v1gRPC contract.- Implemented RPCs: every RPC in the
underpass.made.v1contract is backed by a use case —Deliberate,StreamDeliberation,GetDeliberationResult,Orchestrate,CreateCouncil,ListCouncils,DeleteCouncil,RegisterAgent,UnregisterAgent,ProcessTriggerEvent,RunCouncilDecision,RegisterContract,ListContracts,DeleteContract,RunCeremony,GetStatus, andGetMetrics. No RPC returnsUNIMPLEMENTED. Caveats: (a) provider-backedRegisterAgentkinds require the matching Cargo feature and boot-time credentials;noopis always available. (b)StreamDeliberationemits phase transitions + a finalDeliberationResultframe, not per-proposal/critique/revision events. - Optional NATS messaging: when
MADE_NATS_ENABLED=true, the service publishes all 5 outbound events (made.task.*,made.deliberation.completed,made.phase.changed) and consumes inboundTriggerEvents frommade.trigger.>. Otherwise a no-op messaging adapter is wired. - Optional seeding:
MADE_SEED_SPECIALTIES=triage,reviewerregisters oneNoopAgentand one single-agent council per specialty so a fresh deployment is immediately exercisable end-to-end. - Ceremony orchestration:
RunCeremonyexecutes a YAML-defined ceremony as a finite-state machine — states, steps with pluggable handlers, guarded transitions, and roles. A step can drive a full council deliberation; prior turns thread into later steps' briefs; the response carries a Mermaid sequence diagram of the conversation. Catalog ceremonies (daily standup, technical debate, sprint planning, speaker + Q&A) run end-to-end in CI. - Scoring: the winner of a deliberation is chosen by a pluggable
ScoringPort. The default ranks by validator pass-fraction; an optional LLM-as-judge (MADE_JUDGE_ENABLED, withMADE_JUDGE_THRESHOLD) instead rates each proposal's intrinsic quality and makes that the score. Disabled by default, and fail-fast: enabling it without a vLLM endpoint/model refuses to start rather than silently degrading.
Persistence:
- When
MADE_POSTGRES_URLis set, deliberations, councils, the agent registry, and operational statistics persist to Postgres; otherwise the in-memory defaults are wired. Persistence choice is binary: every backing is either Postgres or in-memory together, so no replica reads from a split source of truth. Migrations apply on startup — a fresh cluster is immediately exercisable. Schema lives undercrates/made-adapters/migrations/postgres/. - Agents persist as descriptors (
id,specialty,kind,attributes); liveAgentPorthandles are rehydrated through the wiredAgentFactoryPorton resolve, so no pickled provider state crosses the database boundary. - Statistics counters use an
INSERT ... ON CONFLICT DO UPDATE ... x = x + 1protocol so concurrent replicas accumulate into the same row without a read-modify-write race — verified by a 50- concurrent-record integration test.
Agent factory (provider-backed materialization):
- The binary wires
DispatchingAgentFactory, which materializeskind == "noop"unconditionally plus any provider whose Cargo feature is compiled in AND whose credentials are present at boot:agent-anthropic+MADE_ANTHROPIC_API_KEY→kind=anthropicagent-openai+MADE_OPENAI_API_KEY→kind=openaiagent-vllm+MADE_VLLM_MODEL+MADE_VLLM_ENDPOINT(+ optionalMADE_VLLM_BEARER_TOKEN) →kind=vllmPer-descriptor overrides viaprovider.model,provider.endpoint,provider.max_tokensattributes on the registered descriptor. Startup log emitsagent_kinds=listing every kind the binary will accept onRegisterAgent.
Caveats and observability:
- Prometheus metrics: the binary serves an operational metric
surface at
GET /metrics(HTTP port8080) through a domainMetricsRecorderPortand aPrometheusMetricsRecorderadapter (explicit registry, no global recorder), exposed alongside the originalStatistics-backed counters. The families cover deliberation quality (duration, winner-score distribution, terminal outcome), the LLM judge (latency, score, errors by kind, discrimination — does the judge re-rank or just burn tokens? —, tokens, scoring mode), the proposing providers (request latency, errors, in-flight gauge for vLLM serial saturation, tokens), the ceremony engine (outcomes, durations, per-step status, blocked transitions), NATS publish, and the Postgres pool. Recording is a synchronous, infallible side-channel that can never block or fail a deliberation. Seedocs/made-observability-design.mdfor the catalogue, alerts, and dashboard design. (Deferred so far: gRPC front-door RED — already covered by the request traces — and per-query Postgres latency.) StreamDeliberationstreams phase transitions only; per-proposal, per-critique, and per-revision streaming arrives in a later slice.- Distributed tracing: the core use cases, gRPC handlers, NATS
inbound subscriber, and
AutoDispatchServiceemit#[tracing:: instrument]spans with domain fields (task_id,specialty,event_id,agent_id,kind). A regression test pins thedeliberatespan name and fields. - W3C Trace Context propagation across NATS: every outbound
event carries a
traceparentheader stamped by the publisher (TraceContext::generate()when no upstream context is present). The inbound subscriber extractstrace_idandspan_idfrom the header and surfaces them as fields on thenats.trigger.inboundspan. Integration-tested against a real NATS container. - W3C Trace Context propagation across gRPC (opt-in via the
otelCargo feature): every RPC handler callslink_span_to_metadata, which readstraceparentfrom request metadata and sets it as the OTel parent context of the current tracing span. Integration-tested via atracing-opentelemetrybridge. - OTLP exporter (opt-in via the
otelfeature + runtimeMADE_OTLP_ENDPOINT): when both are present the binary installs a batching OTLP/gRPC exporter and layers thetracing-opentelemetrybridge into the subscriber, so every instrumented span ships to the configured collector with real OTel trace/span IDs. Feature off → the binary has zero OTel dependency surface. Endpoint unset → the exporter is not wired (no silent background connections).
See docs/experiments/ for anything beyond these bullet points.
Copyright © 2026 Tirso García Ibáñez.
This repository is part of the Underpass AI project. Licensed under the Apache License, Version 2.0, unless stated otherwise.
Redistributions and derivative works must preserve applicable copyright, license, and NOTICE information.
Original author: Tirso García Ibáñez · LinkedIn · Underpass AI