Skip to main content

Conscribe

Inheritance is registration. __init__ signature (plus __wiring__) 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:

  1. Manual registration — Write a class, inherit a base → it's registered. No registry["foo"] = FooClass.
  2. Config guesswork — Your __init__ parameters become the config schema, and __wiring__ adds the cross-registry fields on top. 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}.

A class that declares no config knobs of its own still gets its wired fields — def __init__(self) -> None produces a config model containing the receptors, not an empty one.

Capability-relative keys

When the target registry was created with a key_separator, an explicit key list may name a bare trailing segment instead of a fully-qualified key. Conscribe expands it to every registered key that ends with that segment, so a class does not have to name the provider it will run against:

# Registered: "browser.click", "desktop.click", "browser.scroll"

class PortableAgent(BaseAgent):
    __wiring__ = {"action": ("action", ["click"], ["scroll"])}
    # -> Literal["browser.click", "desktop.click", "browser.scroll"]

class PinnedAgent(BaseAgent):
    __wiring__ = {"action": ("action", ["browser.click"])}  # escape hatch
    # -> Literal["browser.click"]

A key that already contains the separator is passed through unchanged. This is not a fifth mode — it only relaxes how the key strings inside the existing modes are matched, and it is inert for registries without a key_separator.

If a short name matches several providers, all of them land in the Literal[...]. Conscribe expands; it does not arbitrate. Config generation cannot know which providers a given assembled runtime will hold, so every candidate is legitimately possible at that point; picking one belongs to the consuming framework, at the moment it knows the concrete provider set.

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__ + __wiring__
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__ + __wiring__ are 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.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for conscribe 1.3.0
File Size Uploaded
conscribe-1.3.0.tar.gz 80.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for conscribe 1.3.0
File Interpreter ABI Platform
conscribe-1.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 176.2 kB

Release files / conscribe-1.3.0.tar.gz

Download URL conscribe-1.3.0.tar.gz
Size 80.4 kB
Tags Source
SHA-256 checksum
How to use checksums
cd5de370ff3a9cb4e790bbf3214a293053a1b27a27c45217b307f3de73c1c1e4
BLAKE2b-256 checksum
How to use checksums
d3e6491f840fc3c4fb15f3b05ccd8f26fbf32b566caccf9b87095146109089fb
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.3.0-py3-none-any.whl

Download URL conscribe-1.3.0-py3-none-any.whl
Size 95.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6eac14a0b1d7006f7baed49422aee41bb690a2f7cdf697cff54300670c68ad3a
BLAKE2b-256 checksum
How to use checksums
1fdb8f078fb00960279d2d0f6c97f8eaefe3f608e522918fcff86f9d973bcd09
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release history Release notifications | RSS feed

This release

1.3.0 This release

2 release files

1.2.0

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page