Automatic class registration and config typing stub generation for layered Python architectures
Project description
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
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 conscribe-0.9.0.tar.gz.
File metadata
- Download URL: conscribe-0.9.0.tar.gz
- Upload date:
- Size: 63.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab71d4151221ff50134cdbabf91e6af9c038347ac73cdd51277d6a0486d7dabc
|
|
| MD5 |
1cffdf6e52902fe3a4858f5c32c682e7
|
|
| BLAKE2b-256 |
71908d1d5c84e8a1a45053a2e15ccd43bfc7291205ea9497ec6e31761ee363f7
|
File details
Details for the file conscribe-0.9.0-py3-none-any.whl.
File metadata
- Download URL: conscribe-0.9.0-py3-none-any.whl
- Upload date:
- Size: 77.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74ba7d819061091342f2a2e7db2153df61d1155670dd1ce2641e0bdcca07e85b
|
|
| MD5 |
c42d1e122954799b40cb6858a4e78d13
|
|
| BLAKE2b-256 |
6f492af5496327cd1a8aa245f2870caace216f7daa786702b3ead856cb82978e
|