Metadata-Version: 2.4
Name: invariant-core
Version: 0.1.0
Author-email: Kaj Siebert <kaj@k-si.com>
License: MIT
License-File: LICENSE
Requires-Python: <4.0,>=3.10
Requires-Dist: cel-python<0.6.0,>=0.5.0
Description-Content-Type: text/markdown

# Invariant

A Python-based deterministic execution engine for directed acyclic graphs (DAGs). Invariant treats every operation as a pure function, providing aggressive caching, deduplication, and bit-for-bit reproducibility.

## Features

- **Aggressive Caching**: Artifacts are reused across runs if inputs match
- **Deduplication**: Identical operations execute only once
- **Reproducibility**: Bit-for-bit identical outputs across runs
- **Immutability**: Artifacts are frozen once created
- **Determinism**: Operations rely only on explicit inputs

## Installation

```bash
# Clone the repository
git clone <repository-url>
cd invariant

# Install dependencies
uv sync
```

## Quick Start

```python
from invariant import Executor, Node, OpRegistry, cel, ref
from invariant.ops import stdlib
from invariant.store.memory import MemoryStore

# Create registry and register operations
registry = OpRegistry()
registry.register_package("stdlib", stdlib)

# Pipeline: compute (3 * 4) + (5 * 6), then scale the total
#
#   ab ──┐
#        ├── total ── scaled
#   cd ──┘
#
# Literal values flow directly into params — no wrapper nodes needed.
# ref()  passes a computed artifact to a downstream op.
# cel()  evaluates a CEL (Common Expression Language) expression against
#        upstream artifacts, useful for extracting or transforming values
#        without a dedicated op.
graph = {
    "ab": Node(
        op_name="stdlib:multiply",
        params={"a": 3, "b": 4},                    # literal inputs
        deps=[]
    ),
    "cd": Node(
        op_name="stdlib:multiply",
        params={"a": 5, "b": 6},                    # literal inputs
        deps=[]
    ),
    "total": Node(
        op_name="stdlib:add",
        params={"a": ref("ab"), "b": ref("cd")},    # ref() passes artifacts directly
        deps=["ab", "cd"]
    ),
    "scaled": Node(
        op_name="stdlib:multiply",
        params={"a": ref("total"), "b": cel("ab + cd")},  # cel() computes from upstreams
        deps=["ab", "cd", "total"]
    ),
}

# Execute the graph
store = MemoryStore()
executor = Executor(registry=registry, store=store)
results = executor.execute(graph)

print(results["ab"])      # 12
print(results["cd"])      # 30
print(results["total"])   # 42
print(results["scaled"])  # 42 * (12 + 30) = 1764
```

## Architecture

Invariant separates graph definition from execution in two phases:

1. **Phase 1: Context Resolution** - Builds input manifests for each node
2. **Phase 2: Action Execution** - Executes operations or retrieves from cache

### Documentation

| Document | Description |
|:--|:--|
| [docs/architecture.md](docs/architecture.md) | System overview, design philosophy, and reference test pipeline |
| [docs/expressions.md](docs/expressions.md) | **Normative reference** for `ref()`, `cel()`, `${...}` parameter markers and the CEL expression language |
| [docs/executor.md](docs/executor.md) | **Normative reference** for the two-phase execution model, caching, and artifact storage |
| [examples/README.md](examples/README.md) | Runnable examples with walkthroughs, DAG diagrams, and run instructions |
| [AGENTS.md](AGENTS.md) | Quick-start guide for AI agents working with this codebase |

## Development

```bash
# Run tests
uv run pytest

# Run tests with coverage
uv run pytest --cov=src --cov-report=html

# Run linting
uv run ruff check src/ tests/

# Format code
uv run ruff format src/ tests/
```

## License

MIT License - see [LICENSE](LICENSE) for details.

