Deterministic tool-calling control plane for agent loops.
The agent LLM proposes tool calls (via a Proposer). The control plane decides (policy), dispatches execution to server or client venues, and appends every step to an audit-grade event log. Same mental model as “Kubernetes for your tool loop”: one authority, delegated execution, one source of truth.
| Role | API in this library | What it does |
|---|---|---|
| Agent (main loop) | Proposer |
Calls your chat completion (OpenAI-compatible, etc.), returns Proposal(tool_id, args) or “done”. This is where the agent model runs every step. |
| Task classification (optional) | Classifier + OpenAIClassifier |
One structured call up front: label → ClassificationPolicyRegistry picks profile and merged trace quotas. Not the same model as the agent unless you wire it that way. |
The control plane (LoopRunner) does not call an LLM by itself — it calls Proposer and Classifier you provide.
ProfileConfigonToolRegistry— Register named profiles in code, e.g.ToolRegistry(profiles={"bug_profile": ProfileConfig(name="bug_profile", tools=[...], trace_requirements={...}), ...}).- JSON on disk — Same object:
profile_to_json/profile_from_json, or a bundle{"profiles": { "bug_profile": {...}, "write_profile": {...} }}viaload_profile_bundle/dump_profile_bundle(seeprofilesmodule). - Classification routing —
PolicyRule: maps classification label → profile name + optional trace_requirements overrides. Built in Python withClassificationPolicyRegistry(label → rule). There is no separate JSON loader for that map yet; keep it in code or load your own JSON and construct the registry at startup.
Trace quotas (trace_requirements: min counts per tag) live on ProfileConfig (and can be overridden per label via PolicyRule). Tags are declared on ToolDef.tags.
- Governed execution — Stochasticity stays in proposal; what runs is policy-bound.
- One log — Append-only events: classification (optional) → proposal → decision → outcome → venue.
- Two venues, one loop — Server tools and Cursor-style client tools; same policy and log.
- Pluggable LLMs — Bring your own
Proposer(agent) and optionalClassifier; optionalOpenAIClassifierfor a batteries-included classification call (pip install 'toolcallcontrol[openai]').
Read the full design in ARCHITECTURE.md.
pip install toolcallcontrolOptional OpenAI SDK (for OpenAIClassifier):
pip install 'toolcallcontrol[openai]'From source:
pip install -e ".[dev]"from toolcallcontrol import (
ConstraintPipeline,
EventLog,
LoopRunner,
ToolRegistry,
)
log = EventLog()
registry = ToolRegistry()
runner = LoopRunner(log, registry, ConstraintPipeline(registry))
session = runner.create_session("Do something useful.", profile="safe_default")
result = runner.run(session)
print(result["log"]) # append-only list of dicts per stepCLI demo:
python -m toolcallcontrol
# or
tcc-demoFull end-to-end example (offline mocks + optional live OpenAI): see examples/full_agent_example.py.
| Module | Role |
|---|---|
LoopRunner |
Loop: classify (optional) → propose → aggregate → constrain → execute → log; blocks done until trace_requirements met. |
EventLog |
Append-only log per session_id (in-memory default; swap for your DB). |
ToolRegistry |
tool_id → ToolDef (venue, tags, …) + profiles (ProfileConfig: tools, harness, trace_requirements). |
ConstraintPipeline |
Allow/reject before execution (allowlist by profile). |
Proposer / MockProposer |
Agent LLM adapter: propose(..., session=session) → Proposals. |
Classifier / MockClassifier / CallableClassifier / OpenAIClassifier |
Task label → policy resolution. |
ClassificationPolicyRegistry / PolicyRule |
Label → profile + trace overrides. |
profiles |
JSON load/save for ProfileConfig (single file or bundle). |
majority_aggregate |
Optional N-sample consensus before constraints. |
execute |
Runs server tools or returns a client dispatch stub. |
- Persistence adapters for
EventLog(SQLAlchemy / SQLite recipe). - JSON loader for classification rules (
PolicyRulemap), if the community wants it. - Built-in streaming protocol sketches for client execution (WebSocket messages).
- More constraint types and golden-path testing helpers.
Contributions welcome — see CONTRIBUTING.md.
MIT — see LICENSE.
- Docs: this README + ARCHITECTURE.md
- Changelog: CHANGELOG.md
Replace your-org in badges and pyproject.toml [project.urls] with your GitHub org before publishing.