sanctum-engine 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sanctum_engine-0.2.0/.github/workflows/ci.yml +31 -0
- sanctum_engine-0.2.0/.github/workflows/docs.yml +48 -0
- sanctum_engine-0.2.0/.github/workflows/release.yml +58 -0
- sanctum_engine-0.2.0/.gitignore +12 -0
- sanctum_engine-0.2.0/CHANGELOG.md +82 -0
- sanctum_engine-0.2.0/CONTRIBUTING.md +164 -0
- sanctum_engine-0.2.0/LICENSE +21 -0
- sanctum_engine-0.2.0/PKG-INFO +175 -0
- sanctum_engine-0.2.0/README.md +128 -0
- sanctum_engine-0.2.0/benchmarks/superstep_overhead.py +149 -0
- sanctum_engine-0.2.0/docs/api.md +61 -0
- sanctum_engine-0.2.0/docs/architecture.md +253 -0
- sanctum_engine-0.2.0/docs/assets/trace-viewer.svg +50 -0
- sanctum_engine-0.2.0/docs/comparison.md +36 -0
- sanctum_engine-0.2.0/docs/concepts/aether.md +41 -0
- sanctum_engine-0.2.0/docs/concepts/codex.md +35 -0
- sanctum_engine-0.2.0/docs/concepts/omens.md +43 -0
- sanctum_engine-0.2.0/docs/concepts/oracles.md +38 -0
- sanctum_engine-0.2.0/docs/concepts/policies.md +51 -0
- sanctum_engine-0.2.0/docs/concepts/ritual.md +36 -0
- sanctum_engine-0.2.0/docs/concepts/sigils-and-edges.md +39 -0
- sanctum_engine-0.2.0/docs/concepts/spells.md +51 -0
- sanctum_engine-0.2.0/docs/concepts/wards.md +36 -0
- sanctum_engine-0.2.0/docs/getting-started.md +71 -0
- sanctum_engine-0.2.0/docs/guides/human-in-the-loop.md +54 -0
- sanctum_engine-0.2.0/docs/guides/robust-tool-calling.md +53 -0
- sanctum_engine-0.2.0/docs/guides/time-travel.md +43 -0
- sanctum_engine-0.2.0/docs/guides/tracing.md +49 -0
- sanctum_engine-0.2.0/docs/index.md +33 -0
- sanctum_engine-0.2.0/examples/human_in_the_loop/main.py +97 -0
- sanctum_engine-0.2.0/examples/quickstart_ollama.py +76 -0
- sanctum_engine-0.2.0/examples/research_ritual/main.py +105 -0
- sanctum_engine-0.2.0/examples/resilient_pipeline/main.py +104 -0
- sanctum_engine-0.2.0/examples/sse_flask.py +97 -0
- sanctum_engine-0.2.0/mkdocs.yml +51 -0
- sanctum_engine-0.2.0/pyproject.toml +109 -0
- sanctum_engine-0.2.0/src/sanctum/__init__.py +66 -0
- sanctum_engine-0.2.0/src/sanctum/aether/__init__.py +23 -0
- sanctum_engine-0.2.0/src/sanctum/aether/core.py +166 -0
- sanctum_engine-0.2.0/src/sanctum/aether/errors.py +13 -0
- sanctum_engine-0.2.0/src/sanctum/aether/reducers.py +43 -0
- sanctum_engine-0.2.0/src/sanctum/codex/__init__.py +21 -0
- sanctum_engine-0.2.0/src/sanctum/codex/core.py +60 -0
- sanctum_engine-0.2.0/src/sanctum/codex/errors.py +14 -0
- sanctum_engine-0.2.0/src/sanctum/codex/memory.py +33 -0
- sanctum_engine-0.2.0/src/sanctum/codex/postgres.py +164 -0
- sanctum_engine-0.2.0/src/sanctum/codex/sqlite.py +126 -0
- sanctum_engine-0.2.0/src/sanctum/grimoire/__init__.py +25 -0
- sanctum_engine-0.2.0/src/sanctum/grimoire/core.py +290 -0
- sanctum_engine-0.2.0/src/sanctum/grimoire/errors.py +35 -0
- sanctum_engine-0.2.0/src/sanctum/grimoire/repair.py +117 -0
- sanctum_engine-0.2.0/src/sanctum/grimoire/summon.py +211 -0
- sanctum_engine-0.2.0/src/sanctum/omens/__init__.py +50 -0
- sanctum_engine-0.2.0/src/sanctum/omens/events.py +218 -0
- sanctum_engine-0.2.0/src/sanctum/omens/tracing.py +455 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/__init__.py +44 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/_shared.py +105 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/core.py +87 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/errors.py +39 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/llamacpp.py +154 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/ollama.py +215 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/openai_compat.py +227 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/robust.py +239 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/scripted.py +68 -0
- sanctum_engine-0.2.0/src/sanctum/oracle/transformers.py +89 -0
- sanctum_engine-0.2.0/src/sanctum/ritual/__init__.py +42 -0
- sanctum_engine-0.2.0/src/sanctum/ritual/constants.py +14 -0
- sanctum_engine-0.2.0/src/sanctum/ritual/core.py +624 -0
- sanctum_engine-0.2.0/src/sanctum/ritual/errors.py +58 -0
- sanctum_engine-0.2.0/src/sanctum/ritual/interrupt.py +42 -0
- sanctum_engine-0.2.0/src/sanctum/ritual/policies.py +80 -0
- sanctum_engine-0.2.0/src/sanctum/ritual/scheduler.py +573 -0
- sanctum_engine-0.2.0/src/sanctum/trace.py +41 -0
- sanctum_engine-0.2.0/src/sanctum/wards/__init__.py +24 -0
- sanctum_engine-0.2.0/src/sanctum/wards/audit.py +39 -0
- sanctum_engine-0.2.0/src/sanctum/wards/core.py +62 -0
- sanctum_engine-0.2.0/src/sanctum/wards/errors.py +14 -0
- sanctum_engine-0.2.0/src/sanctum/wards/redact.py +53 -0
- sanctum_engine-0.2.0/src/sanctum/wards/usage.py +63 -0
- sanctum_engine-0.2.0/tests/__init__.py +0 -0
- sanctum_engine-0.2.0/tests/aether/__init__.py +0 -0
- sanctum_engine-0.2.0/tests/aether/test_reducers.py +129 -0
- sanctum_engine-0.2.0/tests/codex/__init__.py +0 -0
- sanctum_engine-0.2.0/tests/codex/test_codex.py +168 -0
- sanctum_engine-0.2.0/tests/fixtures/ollama_chat_tools.json +26 -0
- sanctum_engine-0.2.0/tests/fixtures/ollama_stream.ndjson +4 -0
- sanctum_engine-0.2.0/tests/fixtures/openai_chat_completion_malformed_tool.json +26 -0
- sanctum_engine-0.2.0/tests/fixtures/openai_chat_completion_text.json +22 -0
- sanctum_engine-0.2.0/tests/fixtures/openai_chat_completion_tools.json +32 -0
- sanctum_engine-0.2.0/tests/fixtures/openai_stream.sse +12 -0
- sanctum_engine-0.2.0/tests/grimoire/__init__.py +0 -0
- sanctum_engine-0.2.0/tests/grimoire/fixtures/agentgrimoire/system/shout/spell.json +5 -0
- sanctum_engine-0.2.0/tests/grimoire/fixtures/agentgrimoire/system/shout/spell.py +9 -0
- sanctum_engine-0.2.0/tests/grimoire/fixtures/agentgrimoire/text/word_count/spell.json +12 -0
- sanctum_engine-0.2.0/tests/grimoire/fixtures/agentgrimoire/text/word_count/spell.py +6 -0
- sanctum_engine-0.2.0/tests/grimoire/test_robust.py +300 -0
- sanctum_engine-0.2.0/tests/grimoire/test_summon.py +144 -0
- sanctum_engine-0.2.0/tests/grimoire/test_tome.py +125 -0
- sanctum_engine-0.2.0/tests/omens/__init__.py +0 -0
- sanctum_engine-0.2.0/tests/omens/test_astream.py +133 -0
- sanctum_engine-0.2.0/tests/omens/test_tracing.py +115 -0
- sanctum_engine-0.2.0/tests/oracle/__init__.py +0 -0
- sanctum_engine-0.2.0/tests/oracle/test_integration_ollama.py +44 -0
- sanctum_engine-0.2.0/tests/oracle/test_integration_openai_compat.py +109 -0
- sanctum_engine-0.2.0/tests/oracle/test_llamacpp.py +24 -0
- sanctum_engine-0.2.0/tests/oracle/test_ollama.py +101 -0
- sanctum_engine-0.2.0/tests/oracle/test_openai_compat.py +192 -0
- sanctum_engine-0.2.0/tests/oracle/test_scripted.py +29 -0
- sanctum_engine-0.2.0/tests/ritual/__init__.py +0 -0
- sanctum_engine-0.2.0/tests/ritual/test_conditional.py +121 -0
- sanctum_engine-0.2.0/tests/ritual/test_core.py +105 -0
- sanctum_engine-0.2.0/tests/ritual/test_policies.py +192 -0
- sanctum_engine-0.2.0/tests/ritual/test_scheduler.py +107 -0
- sanctum_engine-0.2.0/tests/ritual/test_validation.py +140 -0
- sanctum_engine-0.2.0/tests/test_sanctum.py +30 -0
- sanctum_engine-0.2.0/tests/wards/__init__.py +0 -0
- sanctum_engine-0.2.0/tests/wards/test_wards.py +194 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
- name: Install (editable, with dev extras)
|
|
23
|
+
run: python -m pip install -e ".[dev]"
|
|
24
|
+
|
|
25
|
+
- name: Lint
|
|
26
|
+
run: ruff check .
|
|
27
|
+
|
|
28
|
+
# Integration tests need a live Ollama server and are opt-in
|
|
29
|
+
# (SANCTUM_TEST_OLLAMA_URL); see CONTRIBUTING.md.
|
|
30
|
+
- name: Tests with coverage (integration excluded)
|
|
31
|
+
run: pytest -m "not integration" --cov=sanctum --cov-report=term
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Build the mkdocs-material site and publish it to GitHub Pages.
|
|
2
|
+
# One-time setup: repository Settings -> Pages -> Source: "GitHub Actions".
|
|
3
|
+
|
|
4
|
+
name: Docs
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pages: write
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: pages
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
build:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
|
|
30
|
+
- name: Install package with docs extras
|
|
31
|
+
run: python -m pip install -e ".[docs]"
|
|
32
|
+
|
|
33
|
+
- name: Build site
|
|
34
|
+
run: mkdocs build
|
|
35
|
+
|
|
36
|
+
- uses: actions/upload-pages-artifact@v3
|
|
37
|
+
with:
|
|
38
|
+
path: site/
|
|
39
|
+
|
|
40
|
+
deploy:
|
|
41
|
+
needs: build
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
environment:
|
|
44
|
+
name: github-pages
|
|
45
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
46
|
+
steps:
|
|
47
|
+
- id: deployment
|
|
48
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Release to PyPI on version tags (v0.2.0, v1.0.0, ...).
|
|
2
|
+
#
|
|
3
|
+
# Uses PyPI *trusted publishing* — no API tokens stored in the repo.
|
|
4
|
+
# One-time manual setup required:
|
|
5
|
+
#
|
|
6
|
+
# 1. On PyPI (pypi.org), open the `sanctum-engine` project (or use
|
|
7
|
+
# "Add a new pending publisher" before the first release) and add a
|
|
8
|
+
# trusted publisher with:
|
|
9
|
+
# - Owner: zquintero246
|
|
10
|
+
# - Repository: sanctum-engine
|
|
11
|
+
# - Workflow: release.yml
|
|
12
|
+
# - Environment: pypi
|
|
13
|
+
# 2. On GitHub, create an environment named `pypi`
|
|
14
|
+
# (Settings -> Environments); optionally add required reviewers so a
|
|
15
|
+
# human approves every publish.
|
|
16
|
+
#
|
|
17
|
+
# After that: `git tag v0.2.0 && git push --tags` releases.
|
|
18
|
+
|
|
19
|
+
name: Release
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
push:
|
|
23
|
+
tags: ["v*"]
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
build:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
|
|
31
|
+
- uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.12"
|
|
34
|
+
|
|
35
|
+
- name: Build sdist and wheel
|
|
36
|
+
run: |
|
|
37
|
+
python -m pip install build
|
|
38
|
+
python -m build
|
|
39
|
+
|
|
40
|
+
- uses: actions/upload-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: dist
|
|
43
|
+
path: dist/
|
|
44
|
+
|
|
45
|
+
publish:
|
|
46
|
+
needs: build
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
environment: pypi
|
|
49
|
+
permissions:
|
|
50
|
+
id-token: write # required for PyPI trusted publishing
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/download-artifact@v4
|
|
53
|
+
with:
|
|
54
|
+
name: dist
|
|
55
|
+
path: dist/
|
|
56
|
+
|
|
57
|
+
- name: Publish to PyPI (trusted publishing)
|
|
58
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `sanctum-engine` are documented here. The format
|
|
4
|
+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
|
|
5
|
+
project adheres to [Semantic Versioning](https://semver.org/) (see
|
|
6
|
+
CONTRIBUTING.md for what counts as a breaking change pre-1.0).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] — 2026-07-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Production Oracle adapters, all optional (the core stays stdlib-only):
|
|
12
|
+
- `OpenAICompatibleOracle` (`[openai-compat]` extra): any
|
|
13
|
+
`/v1/chat/completions` server — Ollama `/v1`, llama-server, vLLM,
|
|
14
|
+
LM Studio — with OpenAI-format tools and SSE streaming.
|
|
15
|
+
- `LlamaCppOracle` (`[llamacpp]` extra): in-process GGUF inference, the
|
|
16
|
+
serverless option; grammar-backed tool calling where the chat format
|
|
17
|
+
supports it.
|
|
18
|
+
- `OracleError` family (`OracleConnectionError`, `OracleTimeoutError`,
|
|
19
|
+
`OracleResponseError`) with actionable messages.
|
|
20
|
+
- Robust spell-calling for local 7-14B models:
|
|
21
|
+
- `PromptedSpellCalling` wrapper (schemas in the system prompt, calls
|
|
22
|
+
parsed from delimited blocks) with `"auto"` fallback when the
|
|
23
|
+
endpoint rejects tools.
|
|
24
|
+
- Repair layer applied to every spell call: tolerant JSON extraction
|
|
25
|
+
(fenced blocks, unbalanced braces, single quotes), correction
|
|
26
|
+
messages back to the Oracle for unknown Spells / invalid arguments,
|
|
27
|
+
bounded by `max_repair_rounds` before `SpellCallParseError`.
|
|
28
|
+
- `SpellCallRepaired` / `SpellCallRejected` Omens.
|
|
29
|
+
- Resilience policies: `SigilPolicy` (per-attempt timeout, retries with
|
|
30
|
+
exponential backoff and jitter, selective `retry_on`, `on_error`
|
|
31
|
+
fallback Sigil), `SigilTimeoutError`, `SigilRetried` Omen, and the
|
|
32
|
+
reserved `__errors__` Conduit.
|
|
33
|
+
- Wards middleware: `Ward` hooks (`before_sigil`, `after_sigil`,
|
|
34
|
+
`on_omen`, `on_compile`), `WardRejection` + `DeltaRejected` Omen, and
|
|
35
|
+
built-ins `AuditWard` (JSONL trail), `UsageWard` (token/call tally),
|
|
36
|
+
`RedactWard` (pattern masking before Seals and logs).
|
|
37
|
+
- Local-first tracing: `TraceRecorder` (full `.sanctum-trace.json` per
|
|
38
|
+
Invocation), `render_trace()` self-contained HTML viewer, and the
|
|
39
|
+
`python -m sanctum.trace render` CLI; tracing overhead case added to
|
|
40
|
+
the benchmark.
|
|
41
|
+
- Two-level test strategy: unit tests replay recorded fixtures through
|
|
42
|
+
`httpx.MockTransport`; opt-in integration tests against a live Ollama
|
|
43
|
+
(`SANCTUM_TEST_OLLAMA_URL`, `pytest -m integration`).
|
|
44
|
+
- Packaging: extras `openai-compat`, `ollama`, `llamacpp`, `all`;
|
|
45
|
+
CONTRIBUTING.md; MIT LICENSE; CI and trusted-publishing release
|
|
46
|
+
workflows.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
- `OllamaOracle` rewritten on the native `/api/chat` endpoint via httpx
|
|
50
|
+
(`[ollama]` extra; previously stdlib urllib): native tool calling,
|
|
51
|
+
`keep_alive`, and `options` (temperature, num_ctx, ...).
|
|
52
|
+
- `summon()` gained `spell_calling` ("native" / "prompted" / "auto"),
|
|
53
|
+
`max_repair_rounds`, and `wards`; Oracle `usage` counters are attached
|
|
54
|
+
to assistant messages.
|
|
55
|
+
- The `all` extra excludes `llama-cpp-python` (needs a C toolchain);
|
|
56
|
+
install `[llamacpp]` explicitly for the in-process backend.
|
|
57
|
+
|
|
58
|
+
## [0.1.0] — 2026-07-14
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
- Cyclic state-graph engine: `Ritual` builder and `Rite` executable plan,
|
|
62
|
+
compile-time validation, static fan-out edges, conditional edges with
|
|
63
|
+
`path_map`, cycles bounded by `recursion_limit`.
|
|
64
|
+
- BSP superstep scheduler: concurrent frontier execution
|
|
65
|
+
(`asyncio.TaskGroup`), deterministic delta merging in Sigil insertion
|
|
66
|
+
order, fan-in "any" semantics, `SigilExecutionError` with sibling
|
|
67
|
+
cancellation.
|
|
68
|
+
- State system: `AetherSchema` / `Conduit` with reducers (`overwrite`,
|
|
69
|
+
`append`, `add`, `merge_dict`, custom), `Annotated` sugar, runtime
|
|
70
|
+
schema validation naming the offending Sigil.
|
|
71
|
+
- Persistence: `Seal` checkpoints per superstep, `Codex` stores
|
|
72
|
+
(`MemoryCodex`, `SqliteCodex`, optional `PostgresCodex`), resumption,
|
|
73
|
+
`interrupt()` human-in-the-loop, and time-travel from any Seal.
|
|
74
|
+
- Streaming: `astream` with combinable modes (`updates`, `values`,
|
|
75
|
+
`omens`, `tokens`), typed timestamped Omens, Sigil `writer` injection
|
|
76
|
+
for live tokens, Flask/SSE example.
|
|
77
|
+
- LLM layer: abstract `Oracle` + deterministic `ScriptedOracle`;
|
|
78
|
+
Grimoire: `Spell` / `@spell` (schema from type hints), `Tome` with the
|
|
79
|
+
AgentGrimoire directory convention; `summon()` ReAct loop built on the
|
|
80
|
+
public API.
|
|
81
|
+
- Docs (`README`, `docs/architecture.md`), superstep-overhead benchmark,
|
|
82
|
+
98% core test coverage.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Contributing to Sanctum
|
|
2
|
+
|
|
3
|
+
The design rationale, execution model, trade-offs, and the full glossary
|
|
4
|
+
live in [`docs/architecture.md`](docs/architecture.md) — read it before
|
|
5
|
+
changing the core.
|
|
6
|
+
|
|
7
|
+
## Engineering principles
|
|
8
|
+
|
|
9
|
+
1. **Zero-dependency core**: the engine is Python stdlib only; extras
|
|
10
|
+
(HTTP, Postgres, in-process inference, docs) are optional modules with
|
|
11
|
+
lazy imports.
|
|
12
|
+
2. **Local-first**: never assume a proprietary LLM API in the core.
|
|
13
|
+
3. Everything public is **typed** and documented with dual docstrings
|
|
14
|
+
(metaphor first line, precise technical explanation below).
|
|
15
|
+
4. **Every feature ships with tests**; tests script LLM behavior with
|
|
16
|
+
`ScriptedOracle` and never touch real models or live services.
|
|
17
|
+
5. The **public API stays small and stable** (see Versioning below).
|
|
18
|
+
6. **Async-first**: the engine is native asyncio; synchronous entry
|
|
19
|
+
points are thin wrappers.
|
|
20
|
+
7. Spells load from directory trees by convention
|
|
21
|
+
(`Tome.load_from_directory`), keeping the tool library decoupled.
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
python -m venv .venv
|
|
27
|
+
.venv/Scripts/activate # Windows; use source .venv/bin/activate elsewhere
|
|
28
|
+
pip install -e ".[dev]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Running the tests
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pytest # unit suite — always green, no services needed
|
|
35
|
+
pytest --cov=sanctum # with core coverage
|
|
36
|
+
python benchmarks/superstep_overhead.py
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Unit tests never touch real models or servers: LLM behavior is scripted
|
|
40
|
+
with `ScriptedOracle`, and the HTTP Oracle adapters are tested by
|
|
41
|
+
replaying recorded responses (in `tests/fixtures/`) through
|
|
42
|
+
`httpx.MockTransport`.
|
|
43
|
+
|
|
44
|
+
## Integration tests (opt-in)
|
|
45
|
+
|
|
46
|
+
The suite includes smoke tests against **live local model servers**,
|
|
47
|
+
marked `@pytest.mark.integration` and skipped unless the matching
|
|
48
|
+
environment variable is set:
|
|
49
|
+
|
|
50
|
+
- `SANCTUM_TEST_OPENAI_COMPAT_URL` — exercises `OpenAICompatibleOracle`
|
|
51
|
+
and the robust tool-calling loop end-to-end against **any** server
|
|
52
|
+
exposing `/v1/chat/completions` (llama.cpp's `llama-server`, Ollama's
|
|
53
|
+
`/v1`, vLLM, LM Studio). Set it to the exact base URL, version prefix
|
|
54
|
+
included.
|
|
55
|
+
- `SANCTUM_TEST_OLLAMA_URL` — exercises the native `OllamaOracle`
|
|
56
|
+
(`/api/chat`) against an Ollama daemon. Base URL without `/v1`.
|
|
57
|
+
|
|
58
|
+
With llama.cpp's `llama-server` (any small instruct GGUF works):
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
llama-server -m qwen2.5-0.5b-instruct-q4_k_m.gguf --port 8080 -c 4096 --jinja
|
|
62
|
+
|
|
63
|
+
# bash
|
|
64
|
+
SANCTUM_TEST_OPENAI_COMPAT_URL=http://127.0.0.1:8080/v1 pytest -m integration
|
|
65
|
+
|
|
66
|
+
# PowerShell
|
|
67
|
+
$env:SANCTUM_TEST_OPENAI_COMPAT_URL = "http://127.0.0.1:8080/v1"
|
|
68
|
+
pytest -m integration
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
With Ollama (`ollama serve` + `ollama pull qwen2.5:0.5b`), both adapters
|
|
72
|
+
can run in one pass:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
SANCTUM_TEST_OLLAMA_URL=http://127.0.0.1:11434 \
|
|
76
|
+
SANCTUM_TEST_OPENAI_COMPAT_URL=http://127.0.0.1:11434/v1 \
|
|
77
|
+
pytest -m integration
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Model overrides: `SANCTUM_TEST_OLLAMA_MODEL` and
|
|
81
|
+
`SANCTUM_TEST_OPENAI_COMPAT_MODEL` (both default `qwen2.5:0.5b`;
|
|
82
|
+
`llama-server` ignores the name and serves whatever model it loaded).
|
|
83
|
+
These tests assert transport, parsing, and loop survival against a real
|
|
84
|
+
server — never model quality — so any chat-capable model works.
|
|
85
|
+
|
|
86
|
+
## Style
|
|
87
|
+
|
|
88
|
+
- `ruff check .` must pass (config in `pyproject.toml`; black-compatible
|
|
89
|
+
formatting, line length 88). CI runs it on every push and PR.
|
|
90
|
+
- Code and docstrings in English. The metaphor appears in names and in
|
|
91
|
+
the first line of each docstring, followed by the precise technical
|
|
92
|
+
explanation — never instead of it.
|
|
93
|
+
- Tone of the metaphor: classical occultism and alchemy — mystery and
|
|
94
|
+
precision. Avoid jokey copy, RPG/gamer aesthetics, pop-fantasy
|
|
95
|
+
references, and cartoonish names. Documentation is technically precise
|
|
96
|
+
first; the metaphor is identity, never a substitute for clarity.
|
|
97
|
+
- Error messages must be actionable: name the endpoint/file involved and
|
|
98
|
+
state the most likely fix.
|
|
99
|
+
|
|
100
|
+
### The two-layer naming policy
|
|
101
|
+
|
|
102
|
+
Domain concepts use the glossary vocabulary (Ritual, Sigil, Aether,
|
|
103
|
+
Conduit, Seal, Codex, Omen, Oracle, Spell, Tome, Ward, summon — full
|
|
104
|
+
glossary with technical equivalences in
|
|
105
|
+
[`docs/architecture.md`](docs/architecture.md) §8). Universal infrastructure stays
|
|
106
|
+
technical and is never themed: `compile`, `invoke`, `ainvoke`, `astream`,
|
|
107
|
+
`add_edge`, `add_conditional_edge`, `reducer`, `recursion_limit`,
|
|
108
|
+
`interrupt`, START, END, superstep. Exceptions combine a domain name with
|
|
109
|
+
a technical suffix (`RitualValidationError`, `SigilTimeoutError`,
|
|
110
|
+
`SpellCallParseError`, ...). Don't invent new glossary terms or mystical
|
|
111
|
+
synonyms for technical ones.
|
|
112
|
+
|
|
113
|
+
## Commit convention
|
|
114
|
+
|
|
115
|
+
Conventional Commits: `<type>(<scope>): <imperative summary>` where type
|
|
116
|
+
is one of `feat`, `fix`, `docs`, `test`, `refactor`, `perf`, `chore` and
|
|
117
|
+
scope is the subsystem (`ritual`, `aether`, `codex`, `omens`, `oracle`,
|
|
118
|
+
`grimoire`, `wards`, `ci`). Examples:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
feat(oracle): add OpenAI-compatible adapter with SSE streaming
|
|
122
|
+
fix(ritual): count fallback supersteps toward recursion_limit
|
|
123
|
+
docs(architecture): document fan-in trade-offs
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Breaking changes add a `!` (`feat(ritual)!: ...`) and a `BREAKING CHANGE:`
|
|
127
|
+
footer explaining the migration.
|
|
128
|
+
|
|
129
|
+
## Versioning (SemVer)
|
|
130
|
+
|
|
131
|
+
The **public API** is the contract (engineering principle 5):
|
|
132
|
+
`Ritual`, `Rite`, `add_sigil`, `add_edge`, `add_conditional_edge`,
|
|
133
|
+
`compile`, `invoke`/`ainvoke`, `astream`, `Codex` (and the `Seal` shape),
|
|
134
|
+
`Tome`, `Oracle` (and `OracleResponse`/`SpellCall`), `summon` — plus the
|
|
135
|
+
documented Omen types, `SigilPolicy`, `Ward` hooks, and the exception
|
|
136
|
+
hierarchy exported from `sanctum`.
|
|
137
|
+
|
|
138
|
+
A **breaking change** is anything that makes existing correct code fail
|
|
139
|
+
or change meaning: removing/renaming those names, changing signatures or
|
|
140
|
+
return shapes, changing documented semantics (delta merge order, Seal
|
|
141
|
+
format, stream mode contents), or raising different exception types.
|
|
142
|
+
|
|
143
|
+
While the project is pre-1.0: breaking changes bump the **minor** version
|
|
144
|
+
(0.2.0 → 0.3.0) and are listed under a "Changed"/"Removed" heading in
|
|
145
|
+
CHANGELOG.md with migration notes; everything else bumps the patch. From
|
|
146
|
+
1.0.0 on, standard SemVer applies (breaking = major).
|
|
147
|
+
|
|
148
|
+
## Releasing
|
|
149
|
+
|
|
150
|
+
1. Update `CHANGELOG.md` (move Unreleased to the new version) and the
|
|
151
|
+
version in `pyproject.toml` + `sanctum/__init__.py`.
|
|
152
|
+
2. Tag: `git tag vX.Y.Z && git push --tags`.
|
|
153
|
+
3. `release.yml` builds with `python -m build` and publishes to PyPI via
|
|
154
|
+
**trusted publishing** — the one-time PyPI/GitHub setup is documented
|
|
155
|
+
at the top of `.github/workflows/release.yml`.
|
|
156
|
+
|
|
157
|
+
## Scope guardrails
|
|
158
|
+
|
|
159
|
+
Sanctum competes in the local-first niche, not on feature parity with
|
|
160
|
+
larger frameworks. Priorities: reliability with local 7-14B models
|
|
161
|
+
(including their tool-calling failure modes), developer experience, and
|
|
162
|
+
observability without external services. Don't add advanced graph
|
|
163
|
+
features without a concrete use case that demands them. Use-case code
|
|
164
|
+
belongs in `examples/`, never in the core or the main docs.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zabdiel Quintero
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sanctum-engine
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A minimal, local-first orchestration engine for AI agents: cyclic state graphs executed by supersteps (Pregel/BSP).
|
|
5
|
+
Project-URL: Repository, https://github.com/zquintero246/sanctum-engine
|
|
6
|
+
Project-URL: Documentation, https://github.com/zquintero246/sanctum-engine/blob/main/docs/architecture.md
|
|
7
|
+
Project-URL: Changelog, https://github.com/zquintero246/sanctum-engine/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Zabdiel Quintero
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,llama-cpp,llm,local-first,ollama,orchestration,pregel,state-graph
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: httpx>=0.27; extra == 'all'
|
|
25
|
+
Requires-Dist: psycopg>=3.1; extra == 'all'
|
|
26
|
+
Requires-Dist: transformers>=4.40; extra == 'all'
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
33
|
+
Provides-Extra: docs
|
|
34
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
35
|
+
Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
|
|
36
|
+
Provides-Extra: llamacpp
|
|
37
|
+
Requires-Dist: llama-cpp-python>=0.3; extra == 'llamacpp'
|
|
38
|
+
Provides-Extra: ollama
|
|
39
|
+
Requires-Dist: httpx>=0.27; extra == 'ollama'
|
|
40
|
+
Provides-Extra: openai-compat
|
|
41
|
+
Requires-Dist: httpx>=0.27; extra == 'openai-compat'
|
|
42
|
+
Provides-Extra: postgres
|
|
43
|
+
Requires-Dist: psycopg>=3.1; extra == 'postgres'
|
|
44
|
+
Provides-Extra: transformers
|
|
45
|
+
Requires-Dist: transformers>=4.40; extra == 'transformers'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# Sanctum
|
|
49
|
+
|
|
50
|
+
[](https://github.com/zquintero246/sanctum-engine/actions/workflows/ci.yml)
|
|
51
|
+
[](https://pypi.org/project/sanctum-engine/)
|
|
52
|
+
[](https://github.com/zquintero246/sanctum-engine/actions/workflows/ci.yml)
|
|
53
|
+
[](LICENSE)
|
|
54
|
+
[](pyproject.toml)
|
|
55
|
+
|
|
56
|
+
*Where agents are summoned, bound, and set to work — a minimal, local-first
|
|
57
|
+
orchestration engine for cyclic state graphs.*
|
|
58
|
+
|
|
59
|
+
Sanctum models multi-agent orchestration as a ritual of invocation:
|
|
60
|
+
knowledge lives in the Grimoire (tools), the Sanctum prepares and controls
|
|
61
|
+
the ritual (the engine), entities are summoned (agents), and all of them
|
|
62
|
+
cooperate over a shared energy — the Aether (state) — until a result is
|
|
63
|
+
manifested. Beneath the metaphor sits a precise execution model: a **cyclic
|
|
64
|
+
state graph** run by supersteps (Pregel/BSP), where nodes execute in
|
|
65
|
+
parallel, return partial state deltas merged through per-channel reducers,
|
|
66
|
+
and conditional edges close the loops that make agentic behavior
|
|
67
|
+
(think → act → observe → …) possible. The core is pure Python standard
|
|
68
|
+
library — no proprietary APIs, no mandatory dependencies — designed to run
|
|
69
|
+
entirely on local models.
|
|
70
|
+
|
|
71
|
+
**[Documentation](https://zquintero246.github.io/sanctum-engine/)** ·
|
|
72
|
+
[Getting started](https://zquintero246.github.io/sanctum-engine/getting-started/) ·
|
|
73
|
+
[Comparison with LangGraph / n8n / ADK](https://zquintero246.github.io/sanctum-engine/comparison/) ·
|
|
74
|
+
[Design document](docs/architecture.md)
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
## Features
|
|
79
|
+
|
|
80
|
+
| | |
|
|
81
|
+
|---|---|
|
|
82
|
+
| **Cyclic state graphs** | BSP supersteps, static fan-out, conditional edges, cycles bounded by `recursion_limit` — not a DAG |
|
|
83
|
+
| **Deterministic state** | Per-Conduit reducers applied in Sigil insertion order; identical runs, even under parallelism |
|
|
84
|
+
| **Local-first Oracles** | Ollama (native & `/v1`), llama-server, vLLM, LM Studio, in-process GGUF — never a proprietary API in the core |
|
|
85
|
+
| **Robust tool-calling** | Malformed JSON repaired, unknown Spells corrected conversationally, prompted fallback for models without tools |
|
|
86
|
+
| **Seals & time-travel** | JSON checkpoints per superstep (memory/SQLite/Postgres), resume, `interrupt()` human-in-the-loop, replay from any Seal |
|
|
87
|
+
| **Streaming Omens** | Typed, timestamped events; combinable modes; live tokens from inside a Sigil |
|
|
88
|
+
| **Resilience policies** | Per-Sigil timeout, retries with backoff+jitter, fallback Sigils reading `__errors__` |
|
|
89
|
+
| **Wards middleware** | Transform or veto deltas, observe every event: audit JSONL, usage tally, redaction |
|
|
90
|
+
| **Local tracing** | One-file HTML viewer, zero external requests: `python -m sanctum.trace render run.sanctum-trace.json` |
|
|
91
|
+
| **Zero-dependency core** | Python stdlib only; everything else is an optional extra |
|
|
92
|
+
|
|
93
|
+
## Quickstart
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
pip install sanctum-engine
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from sanctum import END, Ritual
|
|
101
|
+
|
|
102
|
+
ritual = Ritual()
|
|
103
|
+
ritual.add_sigil("cleanse", lambda aether: {"text": aether["text"].strip()})
|
|
104
|
+
ritual.add_sigil("transmute", lambda aether: {"text": aether["text"].upper()})
|
|
105
|
+
ritual.set_entry_point("cleanse")
|
|
106
|
+
ritual.add_edge("cleanse", "transmute")
|
|
107
|
+
ritual.add_edge("transmute", END)
|
|
108
|
+
|
|
109
|
+
rite = ritual.compile()
|
|
110
|
+
print(rite.invoke({"text": " fiat lux "}))
|
|
111
|
+
# {'text': 'FIAT LUX'}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Summoning an Entity
|
|
115
|
+
|
|
116
|
+
`summon()` builds the canonical ReAct loop (oracle → spells → oracle → …
|
|
117
|
+
→ END) entirely on the public primitives:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import asyncio
|
|
121
|
+
from sanctum import Tome, spell, summon
|
|
122
|
+
from sanctum.oracle.ollama import OllamaOracle # pip install "sanctum-engine[ollama]"
|
|
123
|
+
|
|
124
|
+
@spell
|
|
125
|
+
def word_count(text: str) -> int:
|
|
126
|
+
"""Count the words in a text."""
|
|
127
|
+
return len(text.split())
|
|
128
|
+
|
|
129
|
+
entity = summon(
|
|
130
|
+
OllamaOracle(arcana="qwen2.5:7b"),
|
|
131
|
+
Tome([word_count]),
|
|
132
|
+
role="You are a scribe.",
|
|
133
|
+
spell_calling="auto", # prompted fallback if the model lacks native tools
|
|
134
|
+
)
|
|
135
|
+
result = asyncio.run(entity.ainvoke(
|
|
136
|
+
{"messages": [{"role": "user", "content": "How many words in 'fiat lux'?"}]}
|
|
137
|
+
))
|
|
138
|
+
print(result["messages"][-1]["content"])
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Examples
|
|
142
|
+
|
|
143
|
+
The [`examples/`](examples/) gallery runs on scripted oracles by default —
|
|
144
|
+
no model required — and every script takes `--oracle ollama`:
|
|
145
|
+
|
|
146
|
+
- [`quickstart_ollama.py`](examples/quickstart_ollama.py) — chat with a
|
|
147
|
+
tool in thirty lines.
|
|
148
|
+
- [`research_ritual/`](examples/research_ritual/) — two entities scout in
|
|
149
|
+
parallel (fan-out), a third synthesizes (fan-in, append reducer).
|
|
150
|
+
- [`human_in_the_loop/`](examples/human_in_the_loop/) — `interrupt()` +
|
|
151
|
+
Codex: pause for approval, resume where it stopped.
|
|
152
|
+
- [`resilient_pipeline/`](examples/resilient_pipeline/) — retries,
|
|
153
|
+
timeouts, and a fallback Sigil in one observable run.
|
|
154
|
+
- [`sse_flask.py`](examples/sse_flask.py) — bridge `astream` to
|
|
155
|
+
Server-Sent Events.
|
|
156
|
+
|
|
157
|
+
## Ecosystem
|
|
158
|
+
|
|
159
|
+
Sanctum owns execution; [AgentGrimoire](https://github.com/zquintero246/AgentGrimoire)
|
|
160
|
+
owns capability — a folder-per-Spell tool library loadable by convention
|
|
161
|
+
with `Tome.load_from_directory(path)`. Either side evolves without
|
|
162
|
+
touching the other.
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
pip install -e ".[dev]"
|
|
168
|
+
ruff check . && pytest --cov=sanctum # unit suite: no models, no services
|
|
169
|
+
python benchmarks/superstep_overhead.py # engine overhead: tens of µs/superstep
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) (includes
|
|
173
|
+
how to run the opt-in integration tests against a local Ollama) and the
|
|
174
|
+
[design document](docs/architecture.md) for the rationale behind every
|
|
175
|
+
trade-off. Licensed [MIT](LICENSE).
|