Type-safe DSLs for common patterns. Composable, local-first, lazy.
Project description
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.
# distribution is "emergent-py"; you import it as "emergent"
pip install emergent-py
# add extras for the targets you compile to:
pip install "emergent-py[fastapi]" # HTTP + OpenAPI
pip install "emergent-py[sa]" # SQLAlchemy
pip install "emergent-py[all]" # everything (fastapi, sa, tg, cli)
import emergent
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
foldapplied 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
index → SearchIndex(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 | 01–14 | CRUD, methods, transforms, auth, nested resources, multi-target, custom dialects, raw wire, bridge |
| V–VI | 15–18 | Query axis, providers, ops & runners, scope & DI |
| VII–VIII | 19–24 | Enrichers, storage, compilation internals, stateful codecs, roulette walkthrough, design philosophy |
| IX | 25–27 | 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file emergent_py-0.9.5.tar.gz.
File metadata
- Download URL: emergent_py-0.9.5.tar.gz
- Upload date:
- Size: 393.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d80eabb02192ad00aee1882067ce77f648dbe4b610e766353625ceafa7bd798f
|
|
| MD5 |
03619a3315a0024b1de8e95d4449e65a
|
|
| BLAKE2b-256 |
bacc166fe3a04d622907ccbf2ad8a73407a38669a18bf6fcb54bf48b0aaf6019
|
File details
Details for the file emergent_py-0.9.5-py3-none-any.whl.
File metadata
- Download URL: emergent_py-0.9.5-py3-none-any.whl
- Upload date:
- Size: 494.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
97491ce249af1a3aa072542ef52878b185b9786a2680fd1897587bcb3659b1a8
|
|
| MD5 |
0834dd8b2fd83ae48dcf435861600b36
|
|
| BLAKE2b-256 |
54df5785831ba73a40e599c4cadcd6b4fde59847acc7d20641ccba43c786feed
|