agent-tokenops 0.2.1__tar.gz → 0.4.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.
- {agent_tokenops-0.2.1/src/agent_tokenops.egg-info → agent_tokenops-0.4.0}/PKG-INFO +24 -23
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/README.md +20 -21
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/pyproject.toml +16 -5
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0/src/agent_tokenops.egg-info}/PKG-INFO +24 -23
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/agent_tokenops.egg-info/SOURCES.txt +13 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/agent_tokenops.egg-info/requires.txt +4 -1
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/__init__.py +1 -1
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/config/default.yaml +1 -1
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/__init__.py +20 -10
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/boundary.py +7 -19
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/client.py +69 -32
- agent_tokenops-0.4.0/src/tokenops/control/config.py +279 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/context.py +17 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/core.py +18 -7
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/crossing.py +21 -26
- agent_tokenops-0.4.0/src/tokenops/control/dev_plane.py +70 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/engine.py +32 -13
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/http_store.py +12 -3
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/integration.py +34 -13
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/ledger.py +232 -27
- agent_tokenops-0.4.0/src/tokenops/control/ledger_backend.py +279 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/models.py +15 -2
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/__init__.py +7 -3
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/context_compaction.py +40 -11
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/step_cap.py +4 -4
- agent_tokenops-0.4.0/src/tokenops/control/policies/time_budget.py +57 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/trajectory_hint.py +4 -2
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/run.py +18 -3
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/store.py +19 -5
- agent_tokenops-0.4.0/src/tokenops/control/usage.py +43 -0
- agent_tokenops-0.4.0/src/tokenops/demo.py +194 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/providers/anthropic.py +5 -1
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/providers/openai.py +6 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/providers/types.py +4 -0
- agent_tokenops-0.4.0/src/tokenops/ui/policy_labels.py +18 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/ui/run_detail.py +7 -4
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/ui/views/admin.py +42 -28
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/ui/views/dashboard.py +2 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_actuators.py +1 -1
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_boundary.py +3 -3
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_chronicle_boundary.py +5 -5
- agent_tokenops-0.4.0/tests/test_context_compaction.py +121 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_control_plane_app.py +16 -11
- agent_tokenops-0.4.0/tests/test_control_plane_client.py +65 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_crossing_hook.py +5 -5
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_demo.py +9 -8
- agent_tokenops-0.4.0/tests/test_demo_policy_names.py +149 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_governance_config_cache.py +0 -4
- agent_tokenops-0.4.0/tests/test_governance_event_names.py +214 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_ledger.py +40 -0
- agent_tokenops-0.4.0/tests/test_ledger_backend_contract.py +142 -0
- agent_tokenops-0.4.0/tests/test_ledger_backend_mode.py +163 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_policies_wrap_integration.py +69 -1
- agent_tokenops-0.4.0/tests/test_policy_names.py +279 -0
- agent_tokenops-0.4.0/tests/test_provider_usage.py +161 -0
- agent_tokenops-0.4.0/tests/test_time_budget.py +73 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_tokenops_run.py +22 -14
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_trajectory_hint.py +12 -1
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_trajectory_hint_e2e.py +10 -1
- agent_tokenops-0.4.0/tests/test_ui_policy_names.py +177 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_wrap_adapters.py +4 -4
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_wrap_complete_chronicle.py +4 -4
- agent_tokenops-0.2.1/src/tokenops/control/config.py +0 -178
- agent_tokenops-0.2.1/src/tokenops/demo.py +0 -113
- agent_tokenops-0.2.1/tests/test_context_compaction.py +0 -38
- agent_tokenops-0.2.1/tests/test_control_plane_client.py +0 -88
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/LICENSE.txt +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/setup.cfg +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/agent_tokenops.egg-info/dependency_links.txt +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/agent_tokenops.egg-info/top_level.txt +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/adapters/__init__.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/adapters/langchain.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/config/__init__.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/config/loader.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/attribution.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/governance_cache.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/http.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/instrument.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/_util.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/concurrency_cap.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/cost_budget.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/cost_guard.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/output_runaway.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/pre_call_worst_case.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/progress_guard.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/tool_fix.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/policies/tool_output_cap.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/pricing.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/propagate.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/request_context.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/trajectory/__init__.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/trajectory/compress.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/trajectory/enqueue.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/trajectory/gates.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/trajectory/hint.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/trajectory/scope.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/trajectory/serialize.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/control/trajectory/worker.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/env.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/providers/__init__.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/providers/factory.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/server/__init__.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/server/__main__.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/server/app.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/ui/__init__.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/ui/app.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/ui/store_client.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/ui/theme.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/src/tokenops/ui/views/__init__.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_a2a_health.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_apply.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_attribution.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_attribution_ledger_policies_e2e.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_concurrency_cap.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_config.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_cost_budget.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_cost_guard.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_cross_process_budget_gating.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_governance_per_agent.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_integration.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_llm_boundary_precall.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_output_runaway.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_policy_coordination.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_pre_call_worst_case.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_preview_mode.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_pricing.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_progress_guard.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_propagate.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_reason_encoding.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_server_enforcement.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_step_cap.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_store.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_thread_safety.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_tool_fix.py +0 -0
- {agent_tokenops-0.2.1 → agent_tokenops-0.4.0}/tests/test_tool_output_cap.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-tokenops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: TokenOps control plane and SDK for run-aware agent token governance
|
|
5
5
|
Author: Susheem Koul, Tisha Chawla
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: System :: Systems Administration
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE.txt
|
|
23
|
-
Requires-Dist: agent-chronicle
|
|
23
|
+
Requires-Dist: agent-chronicle<0.6,>=0.5.0
|
|
24
24
|
Requires-Dist: openai>=1.0
|
|
25
25
|
Requires-Dist: anthropic>=0.40
|
|
26
26
|
Requires-Dist: pyyaml>=6.0
|
|
@@ -38,6 +38,8 @@ Requires-Dist: ruff>=0.8; extra == "dev"
|
|
|
38
38
|
Requires-Dist: mypy>=1.13; extra == "dev"
|
|
39
39
|
Requires-Dist: pre-commit>=4.0; extra == "dev"
|
|
40
40
|
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
41
|
+
Provides-Extra: contract
|
|
42
|
+
Requires-Dist: agentplane-control-plane>=0.2.0; extra == "contract"
|
|
41
43
|
Provides-Extra: examples
|
|
42
44
|
Requires-Dist: ddgs>=9.0; extra == "examples"
|
|
43
45
|
Requires-Dist: langchain-core>=0.3; extra == "examples"
|
|
@@ -100,7 +102,10 @@ add your own.
|
|
|
100
102
|
|
|
101
103
|
## 🚀 Quickstart
|
|
102
104
|
|
|
103
|
-
Requires Python 3.10
|
|
105
|
+
Requires Python 3.10+ and a running control plane — TokenOps has no ledger of its
|
|
106
|
+
own, so every agent (even a single process) governs against one. Zero-setup taste:
|
|
107
|
+
`python -m tokenops.demo` launches a throwaway plane for you automatically. For your
|
|
108
|
+
own agent, start one first (see [Quickdeploy](#-quickdeploy) below), then:
|
|
104
109
|
|
|
105
110
|
### 1. Put it in your agent
|
|
106
111
|
|
|
@@ -123,14 +128,16 @@ Anywhere else (Cursor, Copilot, ...), paste this:
|
|
|
123
128
|
<details>
|
|
124
129
|
<summary><b>Manual</b>, about ten lines</summary>
|
|
125
130
|
|
|
126
|
-
Wrap your model call once, then hand the wrapped version to your agent.
|
|
131
|
+
Wrap your model call once, then hand the wrapped version to your agent. Needs
|
|
132
|
+
`CONTROL_PLANE_URL` (or `TOKENOPS_URL`) pointing at a running control plane —
|
|
133
|
+
see [Quickdeploy](#-quickdeploy).
|
|
127
134
|
|
|
128
135
|
```python
|
|
129
136
|
from tokenops import ControlPlaneClient, tokenops_run
|
|
130
137
|
from tokenops.control import Halt, wrap_complete
|
|
131
138
|
from tokenops.providers import complete
|
|
132
139
|
|
|
133
|
-
client = ControlPlaneClient.from_env()
|
|
140
|
+
client = ControlPlaneClient.from_env() # raises if CONTROL_PLANE_URL isn't set
|
|
134
141
|
|
|
135
142
|
with tokenops_run(client=client, service="my-agent", intent="research",
|
|
136
143
|
provider="openai", model="gpt-4o") as bound:
|
|
@@ -178,9 +185,9 @@ run is out, even from another process.
|
|
|
178
185
|
## 🐳 Quickdeploy
|
|
179
186
|
|
|
180
187
|
> [!TIP]
|
|
181
|
-
>
|
|
182
|
-
>
|
|
183
|
-
>
|
|
188
|
+
> Every agent needs a control plane running — TokenOps has no ledger of its own.
|
|
189
|
+
> A single-process agent still needs one, just not the multi-process sharing this
|
|
190
|
+
> section is about. `python -m tokenops.server` powers the dashboard too.
|
|
184
191
|
|
|
185
192
|
One command, plane + dashboard:
|
|
186
193
|
|
|
@@ -231,8 +238,8 @@ export TOKENOPS_URL=http://localhost:7700
|
|
|
231
238
|
export TOKENOPS_DB=tokenops.db # plane and every agent read the same file
|
|
232
239
|
```
|
|
233
240
|
|
|
234
|
-
>
|
|
235
|
-
>
|
|
241
|
+
> tokenops has no ledger of its own — every process governs against the plane at
|
|
242
|
+
> `TOKENOPS_URL`. There is no local-fallback env var to accidentally leave unset.
|
|
236
243
|
|
|
237
244
|
PyPI name is `agent-tokenops`; the import is `tokenops`. Extras:
|
|
238
245
|
`pip install "agent-tokenops[examples]"` for the LangChain benches,
|
|
@@ -289,7 +296,7 @@ flowchart LR
|
|
|
289
296
|
| **Control plane** (`python -m tokenops.server`) | `POST /v1/runs`, shared SQLite, Admin/Dashboard | Agent loops, LLM calls, tools |
|
|
290
297
|
| **SDK (in agents)** | `tokenops_run`, `wrap_complete`, ledger/policies, Chronicle crossing hook | Ad-hoc run IDs; mounting `/v1/runs` when `TOKENOPS_URL` is set |
|
|
291
298
|
|
|
292
|
-
Chronicle records decision boundaries; TokenOps attaches as the cost/governance observer on live crossings. See [Chronicle](https://github.com/theagentplane/chronicle) for record-and-replay.
|
|
299
|
+
Chronicle records decision boundaries; TokenOps attaches as the cost/governance observer on live crossings. See [Chronicle](https://github.com/theagentplane/chronicle) for record-and-replay, accepted at [REALM](https://realm-workshop.github.io/) (Research on Agent Language Models), a workshop at EMNLP 2026.
|
|
293
300
|
|
|
294
301
|
</details>
|
|
295
302
|
|
|
@@ -298,22 +305,15 @@ Chronicle records decision boundaries; TokenOps attaches as the cost/governance
|
|
|
298
305
|
|
|
299
306
|
| Variable | Purpose |
|
|
300
307
|
|---|---|
|
|
301
|
-
| `TOKENOPS_URL` |
|
|
302
|
-
| `TOKENOPS_EMBEDDED` | Set to `1` to force in-process `Store` (tests / single-process) |
|
|
303
|
-
| `TOKENOPS_DB` | SQLite path shared by plane + agents |
|
|
308
|
+
| `TOKENOPS_URL` (or `CONTROL_PLANE_URL`) | The control plane's base URL (e.g. `http://localhost:8800`) — **required** |
|
|
304
309
|
| `TOKENOPS_CONFIG` | YAML for governance seed (core: `src/tokenops/config/default.yaml`) |
|
|
305
310
|
|
|
306
|
-
|
|
311
|
+
tokenops has no ledger or run registry of its own — `ControlPlaneClient.from_env()`
|
|
312
|
+
raises if `TOKENOPS_URL`/`CONTROL_PLANE_URL` isn't set, rather than silently falling
|
|
313
|
+
back to anything local. Every process talks to the same plane, so they share one
|
|
314
|
+
budget by construction. `TOKENOPS_URL` also accepts the alias
|
|
307
315
|
`TOKENOPS_CONTROL_PLANE_URL`.
|
|
308
316
|
|
|
309
|
-
Production / multi-process: set `TOKENOPS_URL`; agents must **not** mount `/v1/runs`. Tests: `TOKENOPS_EMBEDDED=1` (or omit URL).
|
|
310
|
-
|
|
311
|
-
> **Precedence.** `ControlPlaneClient.from_env` takes the HTTP path only when a
|
|
312
|
-
> URL is set **and** `TOKENOPS_EMBEDDED` is not `1`. Setting both falls back to a
|
|
313
|
-
> local SQLite file with no warning, and every process then gets its own full
|
|
314
|
-
> budget. Check with
|
|
315
|
-
> `print("embedded" if client.embedded else client.url)`.
|
|
316
|
-
|
|
317
317
|
</details>
|
|
318
318
|
|
|
319
319
|
<details>
|
|
@@ -372,6 +372,7 @@ tests/ # unit + e2e
|
|
|
372
372
|
[LinkedIn](https://www.linkedin.com/posts/microsoft-developers_who-spent-all-the-tokens-tokenops-gives-activity-7499191980715982848-224b) and [X](https://x.com/msdev/status/2093425027500978292).
|
|
373
373
|
- **[Who spent all the tokens? Real-time, run-scoped cost control for AI agents](https://commandline.microsoft.com/tokenops-real-time-run-scoped-cost-control-ai-agents/)**: *Command Line*, a Microsoft publication.
|
|
374
374
|
- **[FinOps for AI Agents: Who Spent All the Tokens?](https://www.youtube.com/watch?v=GJX19pNhmSw)**: talk at the **AI Engineer World's Fair**, San Francisco.
|
|
375
|
+
- **Selected to participate in the [GitHub Secure Open Source Fund](https://github.com/open-source/github-secure-open-source-fund)** (Session 5), GitHub Security Lab's open source security program.
|
|
375
376
|
|
|
376
377
|
## 🛟 Support
|
|
377
378
|
|
|
@@ -53,7 +53,10 @@ add your own.
|
|
|
53
53
|
|
|
54
54
|
## 🚀 Quickstart
|
|
55
55
|
|
|
56
|
-
Requires Python 3.10
|
|
56
|
+
Requires Python 3.10+ and a running control plane — TokenOps has no ledger of its
|
|
57
|
+
own, so every agent (even a single process) governs against one. Zero-setup taste:
|
|
58
|
+
`python -m tokenops.demo` launches a throwaway plane for you automatically. For your
|
|
59
|
+
own agent, start one first (see [Quickdeploy](#-quickdeploy) below), then:
|
|
57
60
|
|
|
58
61
|
### 1. Put it in your agent
|
|
59
62
|
|
|
@@ -76,14 +79,16 @@ Anywhere else (Cursor, Copilot, ...), paste this:
|
|
|
76
79
|
<details>
|
|
77
80
|
<summary><b>Manual</b>, about ten lines</summary>
|
|
78
81
|
|
|
79
|
-
Wrap your model call once, then hand the wrapped version to your agent.
|
|
82
|
+
Wrap your model call once, then hand the wrapped version to your agent. Needs
|
|
83
|
+
`CONTROL_PLANE_URL` (or `TOKENOPS_URL`) pointing at a running control plane —
|
|
84
|
+
see [Quickdeploy](#-quickdeploy).
|
|
80
85
|
|
|
81
86
|
```python
|
|
82
87
|
from tokenops import ControlPlaneClient, tokenops_run
|
|
83
88
|
from tokenops.control import Halt, wrap_complete
|
|
84
89
|
from tokenops.providers import complete
|
|
85
90
|
|
|
86
|
-
client = ControlPlaneClient.from_env()
|
|
91
|
+
client = ControlPlaneClient.from_env() # raises if CONTROL_PLANE_URL isn't set
|
|
87
92
|
|
|
88
93
|
with tokenops_run(client=client, service="my-agent", intent="research",
|
|
89
94
|
provider="openai", model="gpt-4o") as bound:
|
|
@@ -131,9 +136,9 @@ run is out, even from another process.
|
|
|
131
136
|
## 🐳 Quickdeploy
|
|
132
137
|
|
|
133
138
|
> [!TIP]
|
|
134
|
-
>
|
|
135
|
-
>
|
|
136
|
-
>
|
|
139
|
+
> Every agent needs a control plane running — TokenOps has no ledger of its own.
|
|
140
|
+
> A single-process agent still needs one, just not the multi-process sharing this
|
|
141
|
+
> section is about. `python -m tokenops.server` powers the dashboard too.
|
|
137
142
|
|
|
138
143
|
One command, plane + dashboard:
|
|
139
144
|
|
|
@@ -184,8 +189,8 @@ export TOKENOPS_URL=http://localhost:7700
|
|
|
184
189
|
export TOKENOPS_DB=tokenops.db # plane and every agent read the same file
|
|
185
190
|
```
|
|
186
191
|
|
|
187
|
-
>
|
|
188
|
-
>
|
|
192
|
+
> tokenops has no ledger of its own — every process governs against the plane at
|
|
193
|
+
> `TOKENOPS_URL`. There is no local-fallback env var to accidentally leave unset.
|
|
189
194
|
|
|
190
195
|
PyPI name is `agent-tokenops`; the import is `tokenops`. Extras:
|
|
191
196
|
`pip install "agent-tokenops[examples]"` for the LangChain benches,
|
|
@@ -242,7 +247,7 @@ flowchart LR
|
|
|
242
247
|
| **Control plane** (`python -m tokenops.server`) | `POST /v1/runs`, shared SQLite, Admin/Dashboard | Agent loops, LLM calls, tools |
|
|
243
248
|
| **SDK (in agents)** | `tokenops_run`, `wrap_complete`, ledger/policies, Chronicle crossing hook | Ad-hoc run IDs; mounting `/v1/runs` when `TOKENOPS_URL` is set |
|
|
244
249
|
|
|
245
|
-
Chronicle records decision boundaries; TokenOps attaches as the cost/governance observer on live crossings. See [Chronicle](https://github.com/theagentplane/chronicle) for record-and-replay.
|
|
250
|
+
Chronicle records decision boundaries; TokenOps attaches as the cost/governance observer on live crossings. See [Chronicle](https://github.com/theagentplane/chronicle) for record-and-replay, accepted at [REALM](https://realm-workshop.github.io/) (Research on Agent Language Models), a workshop at EMNLP 2026.
|
|
246
251
|
|
|
247
252
|
</details>
|
|
248
253
|
|
|
@@ -251,22 +256,15 @@ Chronicle records decision boundaries; TokenOps attaches as the cost/governance
|
|
|
251
256
|
|
|
252
257
|
| Variable | Purpose |
|
|
253
258
|
|---|---|
|
|
254
|
-
| `TOKENOPS_URL` |
|
|
255
|
-
| `TOKENOPS_EMBEDDED` | Set to `1` to force in-process `Store` (tests / single-process) |
|
|
256
|
-
| `TOKENOPS_DB` | SQLite path shared by plane + agents |
|
|
259
|
+
| `TOKENOPS_URL` (or `CONTROL_PLANE_URL`) | The control plane's base URL (e.g. `http://localhost:8800`) — **required** |
|
|
257
260
|
| `TOKENOPS_CONFIG` | YAML for governance seed (core: `src/tokenops/config/default.yaml`) |
|
|
258
261
|
|
|
259
|
-
|
|
262
|
+
tokenops has no ledger or run registry of its own — `ControlPlaneClient.from_env()`
|
|
263
|
+
raises if `TOKENOPS_URL`/`CONTROL_PLANE_URL` isn't set, rather than silently falling
|
|
264
|
+
back to anything local. Every process talks to the same plane, so they share one
|
|
265
|
+
budget by construction. `TOKENOPS_URL` also accepts the alias
|
|
260
266
|
`TOKENOPS_CONTROL_PLANE_URL`.
|
|
261
267
|
|
|
262
|
-
Production / multi-process: set `TOKENOPS_URL`; agents must **not** mount `/v1/runs`. Tests: `TOKENOPS_EMBEDDED=1` (or omit URL).
|
|
263
|
-
|
|
264
|
-
> **Precedence.** `ControlPlaneClient.from_env` takes the HTTP path only when a
|
|
265
|
-
> URL is set **and** `TOKENOPS_EMBEDDED` is not `1`. Setting both falls back to a
|
|
266
|
-
> local SQLite file with no warning, and every process then gets its own full
|
|
267
|
-
> budget. Check with
|
|
268
|
-
> `print("embedded" if client.embedded else client.url)`.
|
|
269
|
-
|
|
270
268
|
</details>
|
|
271
269
|
|
|
272
270
|
<details>
|
|
@@ -325,6 +323,7 @@ tests/ # unit + e2e
|
|
|
325
323
|
[LinkedIn](https://www.linkedin.com/posts/microsoft-developers_who-spent-all-the-tokens-tokenops-gives-activity-7499191980715982848-224b) and [X](https://x.com/msdev/status/2093425027500978292).
|
|
326
324
|
- **[Who spent all the tokens? Real-time, run-scoped cost control for AI agents](https://commandline.microsoft.com/tokenops-real-time-run-scoped-cost-control-ai-agents/)**: *Command Line*, a Microsoft publication.
|
|
327
325
|
- **[FinOps for AI Agents: Who Spent All the Tokens?](https://www.youtube.com/watch?v=GJX19pNhmSw)**: talk at the **AI Engineer World's Fair**, San Francisco.
|
|
326
|
+
- **Selected to participate in the [GitHub Secure Open Source Fund](https://github.com/open-source/github-secure-open-source-fund)** (Session 5), GitHub Security Lab's open source security program.
|
|
328
327
|
|
|
329
328
|
## 🛟 Support
|
|
330
329
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent-tokenops"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "TokenOps control plane and SDK for run-aware agent token governance"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -34,7 +34,7 @@ classifiers = [
|
|
|
34
34
|
"Topic :: System :: Systems Administration",
|
|
35
35
|
]
|
|
36
36
|
dependencies = [
|
|
37
|
-
"agent-chronicle>=0.
|
|
37
|
+
"agent-chronicle>=0.5.0,<0.6",
|
|
38
38
|
"openai>=1.0",
|
|
39
39
|
"anthropic>=0.40",
|
|
40
40
|
"pyyaml>=6.0",
|
|
@@ -56,6 +56,14 @@ dev = [
|
|
|
56
56
|
"pre-commit>=4.0",
|
|
57
57
|
"types-PyYAML>=6.0",
|
|
58
58
|
]
|
|
59
|
+
# The verified-fake contract suite + e2e run against a real control plane in-process.
|
|
60
|
+
# Not in `dev` because the 0.2.0 line is not on PyPI yet — the tests that need it
|
|
61
|
+
# `pytest.importorskip("control_plane")`, so `[dev]` alone stays green. To run them:
|
|
62
|
+
# pip install -e ".[dev,contract]" # once 0.2.0 is released, or
|
|
63
|
+
# pip install "agentplane-control-plane @ git+https://github.com/theagentplane/control-plane@main"
|
|
64
|
+
contract = [
|
|
65
|
+
"agentplane-control-plane>=0.2.0",
|
|
66
|
+
]
|
|
59
67
|
examples = [
|
|
60
68
|
"ddgs>=9.0",
|
|
61
69
|
"langchain-core>=0.3",
|
|
@@ -74,10 +82,13 @@ testpaths = ["tests"]
|
|
|
74
82
|
pythonpath = ["src", "."]
|
|
75
83
|
asyncio_mode = "strict"
|
|
76
84
|
markers = [
|
|
77
|
-
"e2e: example / bench end-to-end tests
|
|
78
|
-
"
|
|
85
|
+
"e2e: example / bench end-to-end tests — real servers, real Governor/policy",
|
|
86
|
+
" enforcement, faked model+network only. Part of the default suite (see",
|
|
87
|
+
" addopts) precisely so real run+policy regressions are caught.",
|
|
88
|
+
"live: requires real API keys or vendored frameworks — self-skips without them,",
|
|
89
|
+
" excluded from the default run since it needs secrets to do anything.",
|
|
79
90
|
]
|
|
80
|
-
addopts = "-m 'not
|
|
91
|
+
addopts = "-m 'not live'"
|
|
81
92
|
|
|
82
93
|
[tool.ruff]
|
|
83
94
|
target-version = "py310"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-tokenops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: TokenOps control plane and SDK for run-aware agent token governance
|
|
5
5
|
Author: Susheem Koul, Tisha Chawla
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: System :: Systems Administration
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE.txt
|
|
23
|
-
Requires-Dist: agent-chronicle
|
|
23
|
+
Requires-Dist: agent-chronicle<0.6,>=0.5.0
|
|
24
24
|
Requires-Dist: openai>=1.0
|
|
25
25
|
Requires-Dist: anthropic>=0.40
|
|
26
26
|
Requires-Dist: pyyaml>=6.0
|
|
@@ -38,6 +38,8 @@ Requires-Dist: ruff>=0.8; extra == "dev"
|
|
|
38
38
|
Requires-Dist: mypy>=1.13; extra == "dev"
|
|
39
39
|
Requires-Dist: pre-commit>=4.0; extra == "dev"
|
|
40
40
|
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
41
|
+
Provides-Extra: contract
|
|
42
|
+
Requires-Dist: agentplane-control-plane>=0.2.0; extra == "contract"
|
|
41
43
|
Provides-Extra: examples
|
|
42
44
|
Requires-Dist: ddgs>=9.0; extra == "examples"
|
|
43
45
|
Requires-Dist: langchain-core>=0.3; extra == "examples"
|
|
@@ -100,7 +102,10 @@ add your own.
|
|
|
100
102
|
|
|
101
103
|
## 🚀 Quickstart
|
|
102
104
|
|
|
103
|
-
Requires Python 3.10
|
|
105
|
+
Requires Python 3.10+ and a running control plane — TokenOps has no ledger of its
|
|
106
|
+
own, so every agent (even a single process) governs against one. Zero-setup taste:
|
|
107
|
+
`python -m tokenops.demo` launches a throwaway plane for you automatically. For your
|
|
108
|
+
own agent, start one first (see [Quickdeploy](#-quickdeploy) below), then:
|
|
104
109
|
|
|
105
110
|
### 1. Put it in your agent
|
|
106
111
|
|
|
@@ -123,14 +128,16 @@ Anywhere else (Cursor, Copilot, ...), paste this:
|
|
|
123
128
|
<details>
|
|
124
129
|
<summary><b>Manual</b>, about ten lines</summary>
|
|
125
130
|
|
|
126
|
-
Wrap your model call once, then hand the wrapped version to your agent.
|
|
131
|
+
Wrap your model call once, then hand the wrapped version to your agent. Needs
|
|
132
|
+
`CONTROL_PLANE_URL` (or `TOKENOPS_URL`) pointing at a running control plane —
|
|
133
|
+
see [Quickdeploy](#-quickdeploy).
|
|
127
134
|
|
|
128
135
|
```python
|
|
129
136
|
from tokenops import ControlPlaneClient, tokenops_run
|
|
130
137
|
from tokenops.control import Halt, wrap_complete
|
|
131
138
|
from tokenops.providers import complete
|
|
132
139
|
|
|
133
|
-
client = ControlPlaneClient.from_env()
|
|
140
|
+
client = ControlPlaneClient.from_env() # raises if CONTROL_PLANE_URL isn't set
|
|
134
141
|
|
|
135
142
|
with tokenops_run(client=client, service="my-agent", intent="research",
|
|
136
143
|
provider="openai", model="gpt-4o") as bound:
|
|
@@ -178,9 +185,9 @@ run is out, even from another process.
|
|
|
178
185
|
## 🐳 Quickdeploy
|
|
179
186
|
|
|
180
187
|
> [!TIP]
|
|
181
|
-
>
|
|
182
|
-
>
|
|
183
|
-
>
|
|
188
|
+
> Every agent needs a control plane running — TokenOps has no ledger of its own.
|
|
189
|
+
> A single-process agent still needs one, just not the multi-process sharing this
|
|
190
|
+
> section is about. `python -m tokenops.server` powers the dashboard too.
|
|
184
191
|
|
|
185
192
|
One command, plane + dashboard:
|
|
186
193
|
|
|
@@ -231,8 +238,8 @@ export TOKENOPS_URL=http://localhost:7700
|
|
|
231
238
|
export TOKENOPS_DB=tokenops.db # plane and every agent read the same file
|
|
232
239
|
```
|
|
233
240
|
|
|
234
|
-
>
|
|
235
|
-
>
|
|
241
|
+
> tokenops has no ledger of its own — every process governs against the plane at
|
|
242
|
+
> `TOKENOPS_URL`. There is no local-fallback env var to accidentally leave unset.
|
|
236
243
|
|
|
237
244
|
PyPI name is `agent-tokenops`; the import is `tokenops`. Extras:
|
|
238
245
|
`pip install "agent-tokenops[examples]"` for the LangChain benches,
|
|
@@ -289,7 +296,7 @@ flowchart LR
|
|
|
289
296
|
| **Control plane** (`python -m tokenops.server`) | `POST /v1/runs`, shared SQLite, Admin/Dashboard | Agent loops, LLM calls, tools |
|
|
290
297
|
| **SDK (in agents)** | `tokenops_run`, `wrap_complete`, ledger/policies, Chronicle crossing hook | Ad-hoc run IDs; mounting `/v1/runs` when `TOKENOPS_URL` is set |
|
|
291
298
|
|
|
292
|
-
Chronicle records decision boundaries; TokenOps attaches as the cost/governance observer on live crossings. See [Chronicle](https://github.com/theagentplane/chronicle) for record-and-replay.
|
|
299
|
+
Chronicle records decision boundaries; TokenOps attaches as the cost/governance observer on live crossings. See [Chronicle](https://github.com/theagentplane/chronicle) for record-and-replay, accepted at [REALM](https://realm-workshop.github.io/) (Research on Agent Language Models), a workshop at EMNLP 2026.
|
|
293
300
|
|
|
294
301
|
</details>
|
|
295
302
|
|
|
@@ -298,22 +305,15 @@ Chronicle records decision boundaries; TokenOps attaches as the cost/governance
|
|
|
298
305
|
|
|
299
306
|
| Variable | Purpose |
|
|
300
307
|
|---|---|
|
|
301
|
-
| `TOKENOPS_URL` |
|
|
302
|
-
| `TOKENOPS_EMBEDDED` | Set to `1` to force in-process `Store` (tests / single-process) |
|
|
303
|
-
| `TOKENOPS_DB` | SQLite path shared by plane + agents |
|
|
308
|
+
| `TOKENOPS_URL` (or `CONTROL_PLANE_URL`) | The control plane's base URL (e.g. `http://localhost:8800`) — **required** |
|
|
304
309
|
| `TOKENOPS_CONFIG` | YAML for governance seed (core: `src/tokenops/config/default.yaml`) |
|
|
305
310
|
|
|
306
|
-
|
|
311
|
+
tokenops has no ledger or run registry of its own — `ControlPlaneClient.from_env()`
|
|
312
|
+
raises if `TOKENOPS_URL`/`CONTROL_PLANE_URL` isn't set, rather than silently falling
|
|
313
|
+
back to anything local. Every process talks to the same plane, so they share one
|
|
314
|
+
budget by construction. `TOKENOPS_URL` also accepts the alias
|
|
307
315
|
`TOKENOPS_CONTROL_PLANE_URL`.
|
|
308
316
|
|
|
309
|
-
Production / multi-process: set `TOKENOPS_URL`; agents must **not** mount `/v1/runs`. Tests: `TOKENOPS_EMBEDDED=1` (or omit URL).
|
|
310
|
-
|
|
311
|
-
> **Precedence.** `ControlPlaneClient.from_env` takes the HTTP path only when a
|
|
312
|
-
> URL is set **and** `TOKENOPS_EMBEDDED` is not `1`. Setting both falls back to a
|
|
313
|
-
> local SQLite file with no warning, and every process then gets its own full
|
|
314
|
-
> budget. Check with
|
|
315
|
-
> `print("embedded" if client.embedded else client.url)`.
|
|
316
|
-
|
|
317
317
|
</details>
|
|
318
318
|
|
|
319
319
|
<details>
|
|
@@ -372,6 +372,7 @@ tests/ # unit + e2e
|
|
|
372
372
|
[LinkedIn](https://www.linkedin.com/posts/microsoft-developers_who-spent-all-the-tokens-tokenops-gives-activity-7499191980715982848-224b) and [X](https://x.com/msdev/status/2093425027500978292).
|
|
373
373
|
- **[Who spent all the tokens? Real-time, run-scoped cost control for AI agents](https://commandline.microsoft.com/tokenops-real-time-run-scoped-cost-control-ai-agents/)**: *Command Line*, a Microsoft publication.
|
|
374
374
|
- **[FinOps for AI Agents: Who Spent All the Tokens?](https://www.youtube.com/watch?v=GJX19pNhmSw)**: talk at the **AI Engineer World's Fair**, San Francisco.
|
|
375
|
+
- **Selected to participate in the [GitHub Secure Open Source Fund](https://github.com/open-source/github-secure-open-source-fund)** (Session 5), GitHub Security Lab's open source security program.
|
|
375
376
|
|
|
376
377
|
## 🛟 Support
|
|
377
378
|
|
|
@@ -22,6 +22,7 @@ src/tokenops/control/config.py
|
|
|
22
22
|
src/tokenops/control/context.py
|
|
23
23
|
src/tokenops/control/core.py
|
|
24
24
|
src/tokenops/control/crossing.py
|
|
25
|
+
src/tokenops/control/dev_plane.py
|
|
25
26
|
src/tokenops/control/engine.py
|
|
26
27
|
src/tokenops/control/governance_cache.py
|
|
27
28
|
src/tokenops/control/http.py
|
|
@@ -29,12 +30,14 @@ src/tokenops/control/http_store.py
|
|
|
29
30
|
src/tokenops/control/instrument.py
|
|
30
31
|
src/tokenops/control/integration.py
|
|
31
32
|
src/tokenops/control/ledger.py
|
|
33
|
+
src/tokenops/control/ledger_backend.py
|
|
32
34
|
src/tokenops/control/models.py
|
|
33
35
|
src/tokenops/control/pricing.py
|
|
34
36
|
src/tokenops/control/propagate.py
|
|
35
37
|
src/tokenops/control/request_context.py
|
|
36
38
|
src/tokenops/control/run.py
|
|
37
39
|
src/tokenops/control/store.py
|
|
40
|
+
src/tokenops/control/usage.py
|
|
38
41
|
src/tokenops/control/policies/__init__.py
|
|
39
42
|
src/tokenops/control/policies/_util.py
|
|
40
43
|
src/tokenops/control/policies/concurrency_cap.py
|
|
@@ -45,6 +48,7 @@ src/tokenops/control/policies/output_runaway.py
|
|
|
45
48
|
src/tokenops/control/policies/pre_call_worst_case.py
|
|
46
49
|
src/tokenops/control/policies/progress_guard.py
|
|
47
50
|
src/tokenops/control/policies/step_cap.py
|
|
51
|
+
src/tokenops/control/policies/time_budget.py
|
|
48
52
|
src/tokenops/control/policies/tool_fix.py
|
|
49
53
|
src/tokenops/control/policies/tool_output_cap.py
|
|
50
54
|
src/tokenops/control/policies/trajectory_hint.py
|
|
@@ -66,6 +70,7 @@ src/tokenops/server/__main__.py
|
|
|
66
70
|
src/tokenops/server/app.py
|
|
67
71
|
src/tokenops/ui/__init__.py
|
|
68
72
|
src/tokenops/ui/app.py
|
|
73
|
+
src/tokenops/ui/policy_labels.py
|
|
69
74
|
src/tokenops/ui/run_detail.py
|
|
70
75
|
src/tokenops/ui/store_client.py
|
|
71
76
|
src/tokenops/ui/theme.py
|
|
@@ -89,28 +94,36 @@ tests/test_cost_guard.py
|
|
|
89
94
|
tests/test_cross_process_budget_gating.py
|
|
90
95
|
tests/test_crossing_hook.py
|
|
91
96
|
tests/test_demo.py
|
|
97
|
+
tests/test_demo_policy_names.py
|
|
92
98
|
tests/test_governance_config_cache.py
|
|
99
|
+
tests/test_governance_event_names.py
|
|
93
100
|
tests/test_governance_per_agent.py
|
|
94
101
|
tests/test_integration.py
|
|
95
102
|
tests/test_ledger.py
|
|
103
|
+
tests/test_ledger_backend_contract.py
|
|
104
|
+
tests/test_ledger_backend_mode.py
|
|
96
105
|
tests/test_llm_boundary_precall.py
|
|
97
106
|
tests/test_output_runaway.py
|
|
98
107
|
tests/test_policies_wrap_integration.py
|
|
99
108
|
tests/test_policy_coordination.py
|
|
109
|
+
tests/test_policy_names.py
|
|
100
110
|
tests/test_pre_call_worst_case.py
|
|
101
111
|
tests/test_preview_mode.py
|
|
102
112
|
tests/test_pricing.py
|
|
103
113
|
tests/test_progress_guard.py
|
|
104
114
|
tests/test_propagate.py
|
|
115
|
+
tests/test_provider_usage.py
|
|
105
116
|
tests/test_reason_encoding.py
|
|
106
117
|
tests/test_server_enforcement.py
|
|
107
118
|
tests/test_step_cap.py
|
|
108
119
|
tests/test_store.py
|
|
109
120
|
tests/test_thread_safety.py
|
|
121
|
+
tests/test_time_budget.py
|
|
110
122
|
tests/test_tokenops_run.py
|
|
111
123
|
tests/test_tool_fix.py
|
|
112
124
|
tests/test_tool_output_cap.py
|
|
113
125
|
tests/test_trajectory_hint.py
|
|
114
126
|
tests/test_trajectory_hint_e2e.py
|
|
127
|
+
tests/test_ui_policy_names.py
|
|
115
128
|
tests/test_wrap_adapters.py
|
|
116
129
|
tests/test_wrap_complete_chronicle.py
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
agent-chronicle
|
|
1
|
+
agent-chronicle<0.6,>=0.5.0
|
|
2
2
|
openai>=1.0
|
|
3
3
|
anthropic>=0.40
|
|
4
4
|
pyyaml>=6.0
|
|
@@ -8,6 +8,9 @@ httpx>=0.27
|
|
|
8
8
|
fastapi>=0.115
|
|
9
9
|
uvicorn>=0.32
|
|
10
10
|
|
|
11
|
+
[contract]
|
|
12
|
+
agentplane-control-plane>=0.2.0
|
|
13
|
+
|
|
11
14
|
[dev]
|
|
12
15
|
pytest>=8.0
|
|
13
16
|
pytest-asyncio>=0.24
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# TokenOps control plane seed. Consumed by tokenops.control.build_governor / Store auto-seed.
|
|
2
2
|
# Budgets are spend caps bound to a segment; policies are the (detect, fix) templates.
|
|
3
|
+
# Policy keys are canonical IDs, not display labels or instance IDs. See docs/product/policies-index.md.
|
|
3
4
|
governance:
|
|
4
5
|
budgets:
|
|
5
6
|
- id: run_llm_cap
|
|
@@ -31,7 +32,6 @@ governance:
|
|
|
31
32
|
mode: minimize
|
|
32
33
|
context_compaction:
|
|
33
34
|
ctx_max: 100000
|
|
34
|
-
has_hook: false
|
|
35
35
|
output_runaway:
|
|
36
36
|
repeats: 4
|
|
37
37
|
max_retries: 2
|
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
The Account (ledger) + Enforce (policies) half of the control plane. Consumes
|
|
4
4
|
attributed boundary crossings, records them, and decides: steer or stop.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
stream : output_runaway
|
|
10
|
-
observe : cost_budget, step_cap, tool_output_cap, progress_guard
|
|
11
|
-
|
|
12
|
-
Source of truth: docs/policies/ and docs/governance-policy.md. ``core.py`` is the
|
|
13
|
-
canonical vocabulary.
|
|
6
|
+
Built-in IDs and display labels live in ``config.POLICY_TEMPLATES``; see the single
|
|
7
|
+
glossary at ``docs/product/policies-index.md`` and behavior docs at ``docs/policies/``.
|
|
8
|
+
``core.py`` is the canonical runtime vocabulary.
|
|
14
9
|
"""
|
|
15
10
|
|
|
16
11
|
from tokenops.control.attribution import (
|
|
@@ -62,7 +57,15 @@ from tokenops.control.integration import (
|
|
|
62
57
|
wrap_complete,
|
|
63
58
|
wrap_stream,
|
|
64
59
|
)
|
|
65
|
-
from tokenops.control.ledger import Budget, Ledger,
|
|
60
|
+
from tokenops.control.ledger import Budget, Ledger, LocalRunState, segment_key
|
|
61
|
+
from tokenops.control.ledger_backend import (
|
|
62
|
+
AggregateState,
|
|
63
|
+
ApplyResult,
|
|
64
|
+
HttpLedgerBackend,
|
|
65
|
+
LedgerBackend,
|
|
66
|
+
LedgerEvent,
|
|
67
|
+
PrecheckRequest,
|
|
68
|
+
)
|
|
66
69
|
|
|
67
70
|
# Process-wide: re-attach after every reset_session (Chronicle clears on_crossing).
|
|
68
71
|
install_crossing_hook()
|
|
@@ -104,8 +107,15 @@ __all__ = [
|
|
|
104
107
|
# ledger
|
|
105
108
|
"Budget",
|
|
106
109
|
"Ledger",
|
|
107
|
-
"
|
|
110
|
+
"LocalRunState",
|
|
108
111
|
"segment_key",
|
|
112
|
+
# ledger backend (remote-only)
|
|
113
|
+
"LedgerBackend",
|
|
114
|
+
"HttpLedgerBackend",
|
|
115
|
+
"LedgerEvent",
|
|
116
|
+
"PrecheckRequest",
|
|
117
|
+
"AggregateState",
|
|
118
|
+
"ApplyResult",
|
|
109
119
|
# harness
|
|
110
120
|
"Governor",
|
|
111
121
|
"RaiseControls",
|
|
@@ -15,6 +15,7 @@ from typing import Any
|
|
|
15
15
|
from tokenops.control.attribution import _build_attribution, require_registration
|
|
16
16
|
from tokenops.control.context import current_governance, current_span
|
|
17
17
|
from tokenops.control.core import NodeType, Observation, Usage
|
|
18
|
+
from tokenops.control.usage import usage_from_counts
|
|
18
19
|
|
|
19
20
|
_KIND_MAP: dict[str, NodeType] = {
|
|
20
21
|
"llm": "llm",
|
|
@@ -55,6 +56,7 @@ def observation_from_crossing(
|
|
|
55
56
|
model: str = "",
|
|
56
57
|
ts: float | None = None,
|
|
57
58
|
extra_tags: Mapping[str, str] | None = None,
|
|
59
|
+
compaction: Mapping[str, int] | None = None,
|
|
58
60
|
) -> Observation:
|
|
59
61
|
reg = require_registration()
|
|
60
62
|
attr = _build_attribution(reg, service=service)
|
|
@@ -73,24 +75,9 @@ def observation_from_crossing(
|
|
|
73
75
|
|
|
74
76
|
if node_type == "llm":
|
|
75
77
|
usage_obj = getattr(result, "usage", None)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
getattr(usage_obj, "prompt_tokens", 0)
|
|
80
|
-
or getattr(usage_obj, "input_tokens", 0)
|
|
81
|
-
or 0
|
|
82
|
-
),
|
|
83
|
-
output=int(
|
|
84
|
-
getattr(usage_obj, "completion_tokens", 0)
|
|
85
|
-
or getattr(usage_obj, "output_tokens", 0)
|
|
86
|
-
or 0
|
|
87
|
-
),
|
|
88
|
-
)
|
|
89
|
-
else:
|
|
90
|
-
usage = Usage(
|
|
91
|
-
input=int(getattr(result, "input_tokens", 0) or 0),
|
|
92
|
-
output=int(getattr(result, "output_tokens", 0) or 0),
|
|
93
|
-
)
|
|
78
|
+
usage = usage_from_counts(
|
|
79
|
+
usage_obj if usage_obj is not None else result, native=usage_obj is not None
|
|
80
|
+
)
|
|
94
81
|
text = getattr(result, "content", None)
|
|
95
82
|
if text is None and hasattr(result, "completion"):
|
|
96
83
|
text = getattr(result, "completion", result)
|
|
@@ -116,7 +103,7 @@ def observation_from_crossing(
|
|
|
116
103
|
raw_roll = getattr(result, "rolled_up_cost_micros", None)
|
|
117
104
|
if raw_roll is None:
|
|
118
105
|
raw_roll = input_state.get("rolled_up_cost_micros", 0)
|
|
119
|
-
rolled_up = int(raw_roll) if isinstance(raw_roll,
|
|
106
|
+
rolled_up = int(raw_roll) if isinstance(raw_roll, int | float | str) else 0
|
|
120
107
|
output = dict(result) if isinstance(result, dict) else {"result": str(result)}
|
|
121
108
|
|
|
122
109
|
return Observation(
|
|
@@ -133,6 +120,7 @@ def observation_from_crossing(
|
|
|
133
120
|
result_hash=result_hash,
|
|
134
121
|
rolled_up_cost_micros=rolled_up,
|
|
135
122
|
boundary_tags=tags,
|
|
123
|
+
compaction=compaction,
|
|
136
124
|
**_span_fields(service),
|
|
137
125
|
)
|
|
138
126
|
|