This release is a pre-release and may not be stable for production use.
Protocore
Protocore is the agent loop, and nothing else.
It is a Python 3.12+ library holding one thing: the ReAct runtime that drives an
LLM agent turn by turn — the loop, the context budget, the tool surface, the
compaction, the stop conditions. Everything the loop touches from outside is a
Protocol you implement: the model client, the stores, the event transport, the
tools themselves. There is no database driver here, no HTTP endpoint, no
deployment logic, and no import that reaches upward out of the package.
That constraint is the point. An agent loop is where the hard, unglamorous correctness lives — what to do when the model answers with prose instead of the tool it was told to call, when a tool result is a hundred kilobytes, when the context window fills mid-turn, when a run must be snapshotted and resumed on another process. Protocore isolates that from the plumbing so it can be tested exhaustively and reused across products.
Русская версия:
README.md· Docs:docs/index.md(EN) ·docs/ru/index.md(RU)
What you get
- 20 interface Protocols —
ILLMProvider,IRunStore,ISessionStore,IToolRegistry,IMemory,IWorkspace,ISearchIndex,IEventStream,ISkillStore,IHookManager, and the rest, plus anIBlobStoreABC. They are the whole outward surface; the core never learns what is behind them. - A ReAct runtime —
QueryEngineowns the per-run mutable state,query()drives one turn and yields a stream of typedTurnEvents. Snapshot and resume are first-class, so a run survives a process restart. - A three-layer tool surface — tenant policy, then a lean clipped surface, then progressive discovery over BM25 retrieval, with a permission gate in front of dispatch.
- Two-tier context compaction — the loop keeps working when the transcript outgrows the window, and the compaction is deterministic enough to test.
- 524 runtime constants — every tunable value is a field on a frozen
RuntimeConstantssnapshot injected per tenant. No magic numbers in the executable path, and new behaviour defaults off. - In-memory adapters, shipped inside the package, so you can drive a real turn end to end with no external services at all.
Install
pip install protocore==2.0.0a4
Name the version explicitly. The published release is a pre-release, and pip
skips those unless you ask — but do not ask with a bare --pre, because
that flag applies to the whole resolution and will pull pre-release builds of
pydantic too. The pin comes off when there is a stable release.
Or, to work on it, with uv:
uv sync --extra dev
Python ≥ 3.12. Runtime dependencies are pydantic, jinja2, and
typing-extensions — nothing else.
Extras
pip install "protocore[testing]==2.0.0a4" # run the conformance suites against your adapters
testing adds a test runner and nothing more. protocore.conformance is a
pytest suite that ships inside the wheel, and a host points it at its own
implementations of the contracts (pytest --pyargs protocore.conformance). The
core's linter and type checker are deliberately not in it: those belong to
someone changing the core, not to someone using it.
Token estimation also has an optional native implementation, published as the
separate distribution protocore-native. Wheels for it are not on the index
yet, so for now it is built from source; the core stays pure Python and selects
the extension only when it can import it, so having it changes speed and nothing
else — same numbers, same contract. When the extension is installed and you want
the Python implementation anyway — to compare the two, to rule it out as the
cause of a discrepancy, or to build a reproducible environment — the environment
variable PROTOCORE_DISABLE_NATIVE=1 keeps it in force. It is read once,
at import, and answers "which build of this function am I running" rather than
tuning behaviour: changing it inside a live process does nothing.
Quickstart
The core is adapter-driven: build a QueryEngine with your adapters, seed the
history with a user message, then iterate. query(engine) is a sync
function that resets per-turn state and returns an async iterator — it is
deliberately not an async generator, so the reset happens at the call rather
than on the first __anext__.
The example below uses the bundled in-memory adapters, so it runs as-is:
import asyncio
from protocore import (
Message, MessageRole, StopReason, TextBlock, default_runtime_constants,
)
from protocore.runtime.query_engine import QueryEngine, QueryEngineConfig
from protocore.runtime.query import query
from protocore.tests_support.adapters import (
InMemoryBlobStore, InMemoryEventStream, InMemoryHookManager,
InMemoryLLMProvider, InMemorySkillStore, InMemoryToolRegistry,
)
async def main() -> None:
llm = InMemoryLLMProvider()
llm.queue_response(text="Hello from Protocore.", stop_reason=StopReason.end_turn)
engine = QueryEngine(
config=QueryEngineConfig(
run_id="run-1",
tenant_id="default",
session_id="sess-1",
model_name="smoke-model",
rc=default_runtime_constants(),
),
llm_provider=llm,
tool_registry=InMemoryToolRegistry(),
event_stream=InMemoryEventStream(),
hook_manager=InMemoryHookManager(),
skill_store=InMemorySkillStore(),
blob_store=InMemoryBlobStore(),
)
engine.history.append(
Message(role=MessageRole.user, content_blocks=[TextBlock(text="Say hello.")])
)
async for event in query(engine):
print(event.type)
print("final state:", engine.state)
asyncio.run(main())
Swap InMemoryLLMProvider for an adapter over a real model client and the same
code answers real prompts. Getting started walks
through what each event means and what to replace next.
Import location matters.
QueryEngine,QueryEngineConfig, andquerycome fromprotocore.runtime.*; they are not top-level re-exports. The contract types (Message,StopReason,RuntimeConstants, …) are.
Documentation
| Document | What it covers |
|---|---|
| Documentation hub | start here — reading order and a map |
| Getting started | install plus a runnable example |
| Architecture | the deep reference |
| Contracts | the protocol boundary and the type system |
| Tools | the lean tool surface and the permission gate |
| Runtime constants | the configuration model |
| Extending the core | adapters, hooks, toggles, prompt sections |
| Testing | running the suite and the import-boundary guard |
| Glossary | key terms |
A full Russian mirror lives under docs/ru/.
Development
uv sync --extra dev
uv run pytest . # 3215 tests
uv run ruff check .
uv run mypy --strict
uv run bandit -r protocore -q -c pyproject.toml
All four gates run on every pull request across Python 3.12, 3.13, and 3.14. Coverage is enforced at 90%.
The guard worth knowing about is tests/test_core_import_boundary.py: it
AST-parses every module in the package and fails if any of them imports a
package that sits above the core — anything sharing the core's name with an
underscore after it. That test is what keeps the rest of this README true.
Contributions are welcome; see CONTRIBUTING.md.
License
The MPL is a file-level copyleft. In practice that means: build whatever you
like on top of Protocore and keep it closed — your adapters, your service, your
product are yours. But if you modify a Protocore file itself, that file's source
stays open under the same license, and the notices travel with it. See
NOTICE for what that asks of you in concrete terms.
Security issues go to SECURITY.md, not to the public issue
tracker.
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 protocore-2.0.0a4.tar.gz.
File metadata
- Download URL: protocore-2.0.0a4.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8daacebea66a0afffb453d90d461bf0de85e6c264e2ed9d8b1575a95eba3d85c
|
|
| MD5 |
92d99b45c20ebb4a9bad4a2d37a8ea4e
|
|
| BLAKE2b-256 |
bfe7eca772ce9f19aed9127c86ff0af38e7738899d4f995deed1c676f4a09361
|
Provenance
The following attestation bundles were made for protocore-2.0.0a4.tar.gz:
Publisher:
publish.yml on ascorblack-labs/protocore-community
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
protocore-2.0.0a4.tar.gz -
Subject digest:
8daacebea66a0afffb453d90d461bf0de85e6c264e2ed9d8b1575a95eba3d85c - Sigstore transparency entry: 2749522039
- Sigstore integration time:
-
Permalink:
ascorblack-labs/protocore-community@1dab86fe1b24faaf0ee41653dbb95bd2a42bfab8 -
Branch / Tag:
refs/tags/v2.0.0a4 - Owner: https://github.com/ascorblack-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1dab86fe1b24faaf0ee41653dbb95bd2a42bfab8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file protocore-2.0.0a4-py3-none-any.whl.
File metadata
- Download URL: protocore-2.0.0a4-py3-none-any.whl
- Upload date:
- Size: 780.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
31a7d7634eab50126df4b185445959b03bbbe1a4f4d20b12229f849773138932
|
|
| MD5 |
4bdbd7d5cfe90ede55ccb7f05a5a1546
|
|
| BLAKE2b-256 |
b9563615e53808b7f13b47e021968e17b284ae4a10d76e28c9136b62394ec3d9
|
Provenance
The following attestation bundles were made for protocore-2.0.0a4-py3-none-any.whl:
Publisher:
publish.yml on ascorblack-labs/protocore-community
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
protocore-2.0.0a4-py3-none-any.whl -
Subject digest:
31a7d7634eab50126df4b185445959b03bbbe1a4f4d20b12229f849773138932 - Sigstore transparency entry: 2749522070
- Sigstore integration time:
-
Permalink:
ascorblack-labs/protocore-community@1dab86fe1b24faaf0ee41653dbb95bd2a42bfab8 -
Branch / Tag:
refs/tags/v2.0.0a4 - Owner: https://github.com/ascorblack-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1dab86fe1b24faaf0ee41653dbb95bd2a42bfab8 -
Trigger Event:
release
-
Statement type: