Metadata-Version: 2.4
Name: cordis-ouroboros
Version: 0.1.0
Summary: A Python implementation of the Cordis core protocol
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: mypy>=1.13; extra == "dev"
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"

# Ouroboros

> A plugin framework for Python built on reversible registration as a first-class citizen.

**Phase 1 core is implemented and validated, but the public API remains pre-1.0
and may change without notice.**

## What is this?

Ouroboros is a general-purpose composition framework that lets any Python
application decompose itself into plugins that can be dynamically mounted, safely
unmounted, and discover each other by service key rather than by import. A
synchronous root Context composes the registry, event bus, service resolver,
logger, and root lifecycle; every runtime operation still receives its invoking
Context explicitly so derived scopes retain ownership.

It is a Python-native reimplementation of the architectural protocol pioneered by [Cordis](https://github.com/cordiverse/cordis), the meta-framework extracted from the [Koishi](https://koishi.chat) chatbot by Shigma and later vendored by [DeepSeek Harness](https://github.com/deepseek-harness/deepseek-harness) as its plugin substrate.

## Relationship to Cordis

Ouroboros is **not** a line-by-line port of Cordis. It is an independent Python implementation that shares the same core protocol:

- **Reversible effects** — every registration returns a disposer; unloading a plugin unwinds all its registrations in reverse order.
- **Dynamic fiber lifecycle** — plugins mount and unmount at runtime; dependencies are resolved reactively, not by topological sort.
- **Key-based service discovery** — plugins obtain dependencies via `ctx.get("name")` rather than importing concrete implementations.
- **Multi-semantics event bus** — `emit`, `serial`, `bail`, `parallel`, and `waterfall` dispatch modes cover observation, short-circuit, concurrent, and around-middleware patterns.
- **Scope isolation** — `ctx.isolate("name", label)` creates independent service scopes so the same service key can coexist in multiple instances.

The implementation diverges where Python's language semantics and standard library offer a simpler path than Cordis's TypeScript original:

| Cordis (TypeScript) | Ouroboros (Python) | Why |
|---|---|---|
| Hand-written effect disposal | Explicit `DisposerStack` | Preserves synchronous rollback before setup errors escape, then joins mixed async cleanup |
| Traceable Proxy system (~120 LOC) | Not needed | Python's bound methods preserve `self` — no JS `this`-binding problem |
| Schemastery (vendored schema lib) | Pydantic v2 | Python ecosystem standard |
| Custom logger service (246 LOC) | `logging` module | Standard library |
| String-concatenated epoch `":3:5:7"` | `frozenset` of `(service, provider id)` pairs | Order-insensitive, name-preserving snapshot |
| `Symbol` for isolate keys | `object()` instances | Python objects are naturally unique |
| `AggregateError` | `ExceptionGroup` (3.11+) | Standard library |

No Cordis source code is included in this repository. The design is documented as an architectural reference; the code is an original Python implementation.

## Quick start

```python
import asyncio

from ouroboros import Context


async def main() -> None:
    async with Context() as ctx:
        def greeting(plugin_ctx: Context, config: str) -> None:
            plugin_ctx.provide("greeting", config)

        fiber = ctx.plugin(greeting, "hello")
        await fiber
        print(ctx.greeting)


asyncio.run(main())
```

See [`examples/basic.py`](examples/basic.py) for dependency activation, events,
and automatic teardown in one runnable example.

## Phase 1 scope

Phase 1 includes synchronous Context bootstrap, reversible mixed-mode Effects,
reactive Fibers, scoped services, five event modes, plugin registry/runtime
tracking, Pydantic validation, config intercepts, and stdlib logging.

Current limitations:

- Python 3.11+ and one running asyncio loop per active root;
- plugin mounting requires a running loop, although `Context()` does not;
- config validation supports Pydantic v2 `BaseModel` subclasses and is synchronous;
- event names and service names are strings, with best-effort typing for core events;
- circular dependencies remain `PENDING`; Phase 1 does not diagnose the cycle;
- there is no persistence/loader layer or cross-process transport.

## Target use cases

Any system that satisfies all three criteria:

1. **Runtime evolution** — components join and leave while the process runs.
2. **Reversible extensions** — side effects of an extension must unwind cleanly when it is removed.
3. **Swappable implementations** — the same interface may have multiple implementations switchable at runtime.

Examples: AI agent harnesses, chatbot frameworks, data pipelines, IDE extension systems, game server mod systems, test infrastructure.


## License

Apache-2.0. See [LICENSES/Apache-2.0.txt](LICENSES/Apache-2.0.txt).

SPDX headers are used throughout the codebase in compliance with the [REUSE](https://reuse.software/) specification.
