Conscribe
Inheritance is registration. __init__ signature is config schema.
Conscribe is a Python library that provides automatic class registration and config typing stub generation for layered architectures. It eliminates two categories of boilerplate:
- Manual registration — Write a class, inherit a base → it's registered. No
registry["foo"] = FooClass. - Config guesswork — Your
__init__parameters become the config schema. IDE autocomplete and fail-fast validation come for free.
pip install conscribe
Requires Python >= 3.9. Built on Pydantic v2.
Who Is This For?
Framework developers building config-driven systems with pluggable layers (agents, LLM providers, browser backends, etc.) who need registries, factories, protocol checks, and config schemas — without N layers × boilerplate.
Framework users who write YAML configs and want IDE autocomplete, parameter docs, and fail-fast validation at startup instead of crashing N steps in.
Quick Start
1. Define a Layer
from typing import Protocol, runtime_checkable
from conscribe import create_registrar
@runtime_checkable
class ChatModelProtocol(Protocol):
def chat(self, messages: list[dict]) -> str: ...
LLMRegistrar = create_registrar(
"llm",
ChatModelProtocol,
discriminator_field="provider",
strip_prefixes=["Chat"],
)
2. Create a Base Class
class ChatBaseModel(metaclass=LLMRegistrar.Meta):
__abstract__ = True
3. Write Implementations (auto-registered)
class ChatOpenAI(ChatBaseModel):
"""OpenAI LLM provider.
Args:
model_id: Model identifier, e.g. gpt-4o
temperature: Sampling temperature, 0-2
"""
def __init__(self, *, model_id: str, temperature: float = 0.0):
self.model_id = model_id
self.temperature = temperature
def chat(self, messages): ...
# Registered as "open_ai". No decorator. No registry call.
4. Discover & Use
from conscribe import discover
discover("my_app.llm.providers")
llm_cls = LLMRegistrar.get("open_ai") # → ChatOpenAI
llm = llm_cls(model_id="gpt-4o")
print(LLMRegistrar.keys()) # ["open_ai", "anthropic", ...]
Config Typing
Your __init__ signature is the config schema. Conscribe extracts it, builds a Pydantic discriminated union, and generates stubs for IDE autocomplete:
from conscribe import build_layer_config, generate_layer_config_source
result = build_layer_config(LLMRegistrar)
source = generate_layer_config_source(result)
See the Config Typing Guide for full details.
Config Tiers
| Tier | What You Write | What Users Get |
|---|---|---|
| 1 | Plain __init__(self, *, x: int = 5) |
Names + types + defaults |
| 1.5 | + Google/NumPy docstring with Args: |
+ descriptions |
| 2 | + Annotated[int, Field(ge=0)] |
+ constraints |
| 3 | __config_schema__ = MyModel |
Full Pydantic model |
Nested Config (Hierarchical Keys)
For layers with natural hierarchies (e.g., model_type → provider):
LLM = create_registrar(
"llm", ChatModelProtocol,
discriminator_fields=["model_type", "provider"],
key_separator=".",
)
Produces hybrid YAML configs where level 0 is flat and level 1+ is nested:
llm:
model_type: openai # flat (level 0)
temperature: 0.7 # flat (level 0 param)
provider: # nested (level 1)
name: azure
deployment: my-deploy
Cross-Registry Wiring
Declare which values from other registries a class accepts. Conscribe constrains the config field to Literal[...]:
# Given: LoopRegistrar with "react" and "codeact" registered
class BaseAgent(metaclass=AgentRegistrar.Meta):
__abstract__ = True
__wiring__ = {"loop": "loop"} # all keys from "loop" registry
class SWEAgent(BaseAgent):
__wiring__ = {"loop": ("loop", ["react"])} # narrows to subset
def __init__(self, *, max_steps: int = 10): ...
Generated config:
class SweAgentConfig(BaseModel):
name: Literal["swe_agent"] = "swe_agent"
max_steps: int = 10
loop: Literal["react"] = ... # wired from: loop
Four modes:
"loop": "agent_loop"— auto-discover all keys from registry"loop": ("agent_loop", ["react", "codeact"])— explicit subset"obs": ("observation", ["terminal"], ["filesystem"])— required + optional keys"browser": ["chromium", "firefox"]— literal list (no registry)
The 3-element tuple distinguishes required and optional keys — both appear in Literal[...], but the distinction is available as metadata for downstream negotiation logic.
Inheritance: child __wiring__ merges with parent (child keys override). Use None to exclude: {"llm": None}.
Composed Config (Multi-Layer Inline Wiring)
Combine multiple layers into a single config schema. Wired fields become full inline config objects instead of key selectors — enabling recursive IDE autocompletion across layers:
from conscribe import build_composed_config, generate_composed_json_schema
result = build_composed_config(
{"llm": LLMRegistrar, "agent": AgentRegistrar},
inline_wiring=True, # wired fields become target layer union types
)
schema = generate_composed_json_schema(result)
YAML config with inline wiring:
agents:
- name: browser_use
use_vision: true
llm: # full LLM config inline — IDE autocompletes all fields
provider: openai
model: gpt-4o
temperature: 0.7
- name: react
llm:
provider: anthropic
model: claude-3
Use inline_wiring=False to keep wired fields as Literal[...] key selectors.
Cross-Registry Diamond Inheritance
Register a class in multiple registries:
CombinedMeta = LLM.Meta | Agent.Meta
class LLMAgent(metaclass=CombinedMeta):
... # in both LLM and Agent registries
API Reference
Registration
| API | Purpose |
|---|---|
create_registrar(name, protocol, ...) |
Create a layer registrar (recommended entry point) |
Registrar.get(key) |
Look up a registered class |
Registrar.keys() |
List all registered keys |
Registrar.children(prefix) |
Query hierarchical key descendants |
Registrar.tree() |
Get nested dict of key hierarchy |
Registrar.bridge(external_cls) |
Create bridge for external class |
Registrar.register(key) |
Manual registration decorator |
discover(*package_paths) |
Import modules to trigger registration |
scan_registrar_definitions(root) |
Static AST scan for registrar definitions |
list_registries(root, ...) |
Runtime list of all registered content |
Config Typing
| API | Purpose |
|---|---|
extract_config_schema(cls, mro_scope, mro_depth) |
Extract Pydantic model from __init__ |
build_layer_config(registrar) |
Build discriminated union (flat or nested mode) |
generate_layer_config_source(result) |
Generate Python stub source code |
generate_layer_json_schema(result) |
Generate JSON Schema |
build_composed_config(registrars, inline_wiring) |
Build multi-layer composed config |
generate_composed_json_schema(result) |
Generate composed JSON Schema |
generate_composed_config_source(result) |
Generate composed Python source |
compute_registry_fingerprint(registrar) |
Compute registry fingerprint hash |
get_registry(name) |
Look up a registry by name (for wiring) |
Design Principles
- Zero registration burden — Inherit a base class = registered
__init__is the single source of truth — No duplicate config definitions- Fail-fast — Duplicate keys raise immediately; invalid config rejects at startup
- Domain-agnostic — Pure infrastructure, knows nothing about agents or LLMs
- Stubs and runtime are separate — Stale stubs don't affect correctness
Documentation
Full documentation is shipped inside the package (accessible at site-packages/conscribe/) and browsable on GitHub:
| Document | Description |
|---|---|
llms.txt |
AI entry point — package summary and navigation |
docs/overview.md |
Core concepts and architecture |
docs/guide-alice.md |
Tutorial: building a framework with conscribe |
docs/guide-bob.md |
Tutorial: consuming a conscribe-based framework |
docs/api-reference.md |
Full API signatures and examples |
docs/recipes.md |
Task-oriented "how do I X?" |
docs/registration.md |
Registration subsystem internals |
docs/config-typing.md |
Config typing pipeline internals |
docs/mro-and-degradation.md |
MRO chains and type degradation |
docs/cli.md |
CLI reference (scan, list, inspect, generate-*) |
License
MIT
Release files for conscribe 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| conscribe-1.2.0.tar.gz | 77.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| conscribe-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 170.3 kB
Release files / conscribe-1.2.0.tar.gz
| Download URL | conscribe-1.2.0.tar.gz |
|---|---|
| Size | 77.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
52760f65116fb209fb04b568ae8451ade79f340ed21e242f4076d4e800b0af8b
|
|
BLAKE2b-256 checksum How to use checksums |
1b597ad38fdc988e43f814c32c583245fe59028bc95dae71b5f1ba47bcccd924
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / conscribe-1.2.0-py3-none-any.whl
| Download URL | conscribe-1.2.0-py3-none-any.whl |
|---|---|
| Size | 92.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
011195c9767941c8204353aa46bd1f260b3c10c870d502c886bfcdd5bcb98828
|
|
BLAKE2b-256 checksum How to use checksums |
afe91190d7b9c8e3afe97fa1497041826a2cd88776e1d21487d8ecf37d9eb878
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|