GitHub - prostomarkeloff/emergent: Python is all you need.

10 min read Original article ↗

A compilation platform where frozen data compiles itself through fold. You build compilation languages — not applications. HTTP, CLI, Telegram, OpenAPI, Pydantic, SQL are libraries on the platform. Your custom compilers sit next to them.

@schema_meta(http_crud("/users", Users))
@dataclass
class User:
    id: Annotated[int, Identity]
    name: str
    email: Annotated[str, Unique, MaxLen(255)]

5 REST endpoints. Pydantic models. OpenAPI spec. You write fields, fold writes everything else.

uv add git+https://github.com/prostomarkeloff/emergent.git

The idea

Frameworks today scatter meaning across files. A User lives in a model, a serializer, a view, a URL config, a migration, a test fixture — and none of those files know about each other. An LLM (or a human) has to hold the whole graph in their head to make a correct change.

emergent inverts this. Everything about a thing is on the thing:

@schema_meta(
    http_crud("/bounties", Board, ops=(LIST, GET, CREATE)),
    Methods(),
)
@dataclass
class Bounty:
    id: Annotated[int, Identity]
    title: str
    reward: Annotated[int, Min(0)]
    status: Annotated[str, MaxLen(20), Doc("Bounty status"),
                       cli.Help("Current status"), openapi.Description("Status field"),
                       sql.Index("idx_status")]

    @classmethod
    @post("/bounties/{bounty_id}/claim")
    async def claim(cls, db: ..., bounty_id: int, hunter: str) -> Result[Bounty, DomainError]: ...

One place. Every concern — validation, CLI help text, OpenAPI description, SQL index, HTTP routes — lives as an annotation on the field it belongs to. The fold compiler reads these annotations and produces correct output for each target. No sync bugs. No scattered state. No framework magic to reverse-engineer.

This is locality by construction, and it's what makes emergent work for humans and machines alike.

The entire platform is one function. Compilation, verification, explanation, query execution, derivation — all are fold applied to different data. Everything else is consequences.


Platform, not framework

Frameworks prescribe structure. Rails prescribes MVC. Django prescribes models-views-templates. You write code inside the framework.

emergent provides primitives. You build on the platform:

Primitive What it does
fold(items, ctx, protocol, method) Iterate capabilities, dispatch by protocol, accumulate context. 8 lines.
CompilationPhase(ctx_type, protocol, initial) Reified fold configuration. One phase = one compilation language.
SchemaCompiler(phases) Composable set of phases. Algebra: +, |, -, &.
TargetCompiler(trigger, codecs, pipeline, assemble) Surface-level compilation: Application → framework artifact.

The 4 axes (Schema, Surface, Storage, Query) are libraries built on these primitives. wire.derive is a library. Your custom compilers sit next to them — not inside a framework, but on the same platform.

Create your own compiler

to_pydantic(User, axes) reads capabilities, produces a Pydantic model. to_json_schema produces OpenAPI. You build to_search_index the same way. This runs:

from dataclasses import dataclass, replace
from typing import Annotated, Protocol, runtime_checkable

from emergent.wire.compile._core import Axes
from emergent.wire.compile._phase import CompilationPhase
from emergent.wire.compile import to_pydantic, to_json_schema
from emergent.wire.axis.schema import Identity, MaxLen, Min
from emergent.wire.axis.schema._universal import SchemaAxisCapability
from emergent.wire.axis._capability import OpenAPIContext, openapi_schema


# ── Your language: search index ──────────────────────────────

@dataclass(frozen=True, slots=True)
class IndexFieldCtx:
    field_name: str = ""
    field_type: type = object
    searchable: bool = False
    boost: float = 1.0
    facet: bool = False


@runtime_checkable
class IndexCompilable(Protocol):
    def compile_index(self, ctx: IndexFieldCtx) -> IndexFieldCtx: ...


INDEX_PHASE = CompilationPhase(
    IndexFieldCtx,
    IndexCompilable,
    lambda n, t: IndexFieldCtx(field_name=n, field_type=t),
)


# ── Capabilities: one atom, two languages ────────────────────

@dataclass(frozen=True, slots=True)
class Searchable(SchemaAxisCapability):
    boost: float = 1.0

    # YOUR language
    def compile_index(self, ctx: IndexFieldCtx) -> IndexFieldCtx:  
        return replace(ctx, searchable=True, boost=self.boost)

    # emergent's!
    def compile_openapi(self, ctx: OpenAPIContext) -> OpenAPIContext:
        return openapi_schema(
            ctx, **{"x-searchable": True, "x-search-boost": self.boost}
        )


@dataclass(frozen=True, slots=True)
class Facet(SchemaAxisCapability):
    def compile_index(self, ctx: IndexFieldCtx) -> IndexFieldCtx:
        return replace(ctx, facet=True)

    def compile_openapi(self, ctx: OpenAPIContext) -> OpenAPIContext:
        return openapi_schema(ctx, **{"x-facet": True})


# ── Your compiler: to_search_index — same shape as to_pydantic

@dataclass(frozen=True, slots=True)
class IndexField:
    name: str
    searchable: bool = False
    boost: float = 1.0
    facet: bool = False


@dataclass(frozen=True, slots=True)
class SearchIndex:
    entity: str
    fields: tuple[IndexField, ...]


INDEX_COMPILER = SchemaCompiler(phases=(INDEX_PHASE,))


def to_search_index(cls: type, axes: Axes) -> SearchIndex:
    ec = INDEX_COMPILER.compile(cls, axes)
    return SearchIndex(
        entity=cls.__name__,
        fields=tuple(
            IndexField(
                name=c.field_name,
                searchable=c.searchable,
                boost=c.boost,
                facet=c.facet,
            )
            for fc in ec
            if (c := fc[INDEX_PHASE]).searchable or c.facet
        ),
    )


# ── Entity ───────────────────────────────────────────────────

@dataclass
class Product:
    id: Annotated[int, Identity]
    name: Annotated[str, MaxLen(200), Searchable(boost=3.0)]
    description: Annotated[str, Searchable()]
    category: Annotated[str, Facet()]
    price: Annotated[float, Min(0), Facet()]


# ── Same axes, three compilers ───────────────────────────────

axes = Axes.default()

index  = to_search_index(Product, axes)   # YOUR compiler   → SearchIndex
model  = to_pydantic(Product, axes)       # emergent's       → Pydantic BaseModel
schema = to_json_schema(Product, axes)    # emergent's       → OpenAPI JSON Schema
indexSearchIndex(entity='Product', fields=(
    IndexField(name='name', searchable=True, boost=3.0),
    IndexField(name='description', searchable=True, boost=1.0),
    IndexField(name='category', facet=True),
    IndexField(name='price', facet=True),
))

model<class 'Product'>  (Pydantic BaseModel with MaxLen, Min validation)

schema → {
    "name": {"type": "string", "maxLength": 200, "x-searchable": true, "x-search-boost": 3.0},
    "category": {"type": "string", "x-facet": true},
    "price": {"type": "number", "minimum": 0, "x-facet": true},
    ...
}

to_search_index has the same shape as to_pydantic — takes a class and axes, returns a typed artifact. Searchable implements both compile_index and compile_openapi, so it participates in both compilers. MaxLen has no compile_index — your compiler never sees it.

Execution: nodnod

emergent has two co-equal primitives. fold compiles descriptions. nodnod executes dependency graphs:

@G.node
class FetchPrice:
    @classmethod
    async def __compose__(cls, product: Product, db: Database) -> float:
        return await db.get_price(product.id)

@G.node
class FetchStock:
    @classmethod
    async def __compose__(cls, product: Product, db: Database) -> int:
        return await db.get_stock(product.id)

# FetchPrice and FetchStock have no dependency on each other → run in parallel.
# No asyncio.gather. No concurrency code. The type signatures ARE the graph.
result = await G.compose(BuildReport, product, db)

Types are the specification. fold reads capabilities from Annotated. nodnod reads dependencies from __compose__ signatures. Both: the structure IS the program.


Why this matters

A User in Django lives in a model, a serializer, a view, a URL config, a migration. Five files that must agree. When you change one, you grep for the others and hope. When a new developer joins, they trace the chain by hand. When a coding agent tries to add a field, it hallucinates a signal that doesn't exist because the pattern looked right from the three files it saw.

This is the scattered meaning problem. It is not a tooling problem — it is a language problem. The absence of a primitive that lets you state a fact once and have every consumer derive what it needs.

emergent is that primitive. Four properties make it work:

Property What it means
Locality All concerns for a field live on the field. Annotated[str, MaxLen(255), Unique, sql.Index()] — validation, schema, DDL in one place.
Defunctionalization Behavior is data. MaxLen(255) is a frozen dataclass you can print, compare, serialize. Not a closure. Not a decorator. Data.
Open-world dispatch New capabilities participate via isinstance. No registration. No plugin system. Implement the protocol → fold picks you up.
Algebraic composition Compilers compose: FASTAPI_SCHEMA + YOUR_PHASE. Capabilities compose: (MaxLen(255), Unique). No interference between independent concerns.

The result: any observer — human, LLM, or your future self at 3 AM — reads one declaration and knows everything. One change propagates to all targets. Not by convention. By construction.


Self-describing

emergent's IR reads itself. Every axis has an explain system — structured dicts for tools, human-readable text for you:

explain_schema(User)
# === User ===
#   [SchemaName('users')]
#
#   id (int):
#     [Identity]
#
#   email (str):
#     [Unique, MaxLen(255)]
#     cli: Help('Email address')
#     openapi: Description('User email'), Format('email')
#     sql: Index('idx_email')

explain_application(app)
# === Application (3 endpoints) ===
#
#   Endpoint #1 (2 exposures):
#     [POST /api/v1/players] RequestResponseCodec
#       request: RegisterRequest, response: RegisterResponse
#     [register (cli)] RequestResponseCodec
#       request: RegisterRequest, response: RegisterResponse

explain_full(User)  # 3-layer trace
# === User (full trace) ===
#
#   Schema: 3 fields (1 identity)
#     id (int) [Identity]
#     name (str)
#     email (str) [Unique]
#
#   Derivation: 1 pattern
#     Pattern #1: Dialect (11 steps), provider=Users
#       steps: InspectEntity → RequireIdentity → BindProvider → ...
#       ops: List, Get, Create, Update, Patch, Delete
#
#   Surface: 6 exposures across 1 endpoint
#     GET /api/users [ListUserRequest → ListUserResponse]
#     GET /api/users/{id} [GetUserRequest → GetUserResponse]
#     ...

Schema, query, storage, surface, compilation trace, derivation pipeline — all explainable. No execution, no side effects. The program describes itself from its own IR.


Verified at import time

verify catches semantic contradictions before your code runs:

from emergent.wire.verify import verify_raising

@dataclass
class Sensor:
    id: Annotated[int, Identity]
    name: Annotated[str, MinLen(50), MaxLen(10)]       # ERROR: MinLen > MaxLen
    temp: Annotated[float, Min(200), Max(125)]          # ERROR: Min > Max
    secret: Annotated[str, ReadOnly(), WriteOnly()]      # ERROR: inaccessible field

verify_raising(Sensor)  # VerificationError with all issues

Three built-in phases — numeric constraints, length constraints, semantic flags — each running as a standard compilation phase. Open-world: add your own verification phases without modifying emergent.


Four levels of control

# Level 1 — auto CRUD: one decorator, full API
@schema_meta(http_crud("/products", Store))
@dataclass
class Product:
    id: Annotated[int, Identity]
    name: str
    price: float

# Level 2 — CRUD + methods: derive CRUD, hand-write domain logic
@schema_meta(http_crud("/bounties", Board, ops=(LIST, GET, CREATE)), Methods())
@dataclass
class Bounty:
    id: Annotated[int, Identity]
    title: str
    reward: int

    @classmethod
    @post("/bounties/{bounty_id}/claim")
    async def claim(cls, db: ..., bounty_id: int, hunter: str) -> Result[Bounty, DomainError]: ...

# Level 3 — hand-written methods: every endpoint explicit, full control
@schema_meta(Methods())
@dataclass
class Auth:
    @classmethod
    @post("/login")
    async def login(cls, creds: Credentials) -> Result[Token, AuthError]: ...

# Level 4 — raw wire: one endpoint, three targets
endpoint(runner)
    .expose(HTTPRouteTrigger("POST", "/register"), rrc(Req, Resp))
    .expose(CLITrigger("register"), rrc(Req, Resp))
    .expose(TelegrindTrigger(Command("register")), rrc(Req, Resp))

Pick what fits. Mix in one app. Drop down a level when you need control, stay high when you don't.


Semantic transforms

Transforms in emergent dispatch on domain meaning, not syntax. This is a novel mechanism — no existing macro system combines domain-semantic awareness with compositional algebra:

@schema_meta(
    http_crud("/posts", Posts),
    WithoutDelete(),    # ← removes DELETE op across all targets
    Paginated(20),      # ← adds pagination to ops with Pageable effect
    Sorted(),           # ← adds sorting to LIST
    Filtered("author"), # ← adds exact-match filter
)
@dataclass
class Post:
    id: Annotated[int, Identity]
    title: str
    body: str
    author: str

Capabilities stack as arguments in @schema_meta() — frozen data in, frozen data out. They compose because they operate on algebraic structure, not string templates or AST nodes.


Examples

Example What it shows
roulette/ HTTP + CLI + Telegram from one codebase
cross_compile/ Bridge legacy FastAPI → CLI
full_stack/ Full-stack example
wiring.py Raw wire: endpoint + trigger + codec

Tutorial

A story-driven, 27-chapter walkthrough — from first API to handing your codebase to a coding agent.

Start here → docs/tutorial/00-intro.md

Part Chapters What you'll build
I–IV 0114 CRUD, methods, transforms, auth, nested resources, multi-target, custom dialects, raw wire, bridge
V–VI 1518 Query axis, providers, ops & runners, scope & DI
VII–VIII 1924 Enrichers, storage, compilation internals, stateful codecs, roulette walkthrough, design philosophy
IX 2527 Verify & explain, why emergent is LLM-native, agent workflows

Docs

docs/intro.md Introduction (EN)
docs/intro_ru.md Введение (RU)
docs/essence.md The essence — one function, one operator
docs/philosophy.md Design philosophy
docs/theory/architecture.md Architecture — theory, invariants, algebraic properties
docs/internal/wire-reference.md Wire reference — axes, capabilities, compile, bridge
docs/internal/cheatsheet.md Cheatsheet — all axes, every import, every pattern
docs/theory/universal-derivation.md Derivation — entity → endpoints via fold
docs/theory/compiler-deep-dive.md Compiler deep-dive — developing custom compilers
docs/emergent-and-ai.md emergent + AI agents
docs/book/ Book — 5-chapter SICP-style deep dive into compilation thinking

Where we are

emergent is young — started January 2026. Already runs in production. The core architecture (fold, CompilationPhase, SchemaCompiler, TargetCompiler, verify, explain, derive) is stable. What's still growing: the ecosystem, the stdlib, the number of built-in targets and dialects. Breaking changes happen, but we keep them well-motivated.


Stack

Layer What
deployme.py Application → infrastructure (compose, k8s)
emergent platform: fold, CompilationPhase, SchemaCompiler, TargetCompiler
emergent.wire libraries: axes (schema, surface, storage, query), derive, bridge, verify
emergent.graph runtime: Composer, ScopeFamily, RuntimePolicy (Cooperative / WorkStealing)
emergent.ops/saga/cache patterns: data-driven dispatch, compensation chains, tiered caching
nodnod typed dependency graphs, auto-parallelization, Either, Scope
combinators.py retry, timeout, fallback, race
kungfu Result, Option, LazyCoroResult

Define a context. Define a protocol. Call fold.

You just created a compilation language.