You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is the standing entry point for contributing a policy. A policy is the smallest useful unit of TokenOps and needs no changes to the core, so it is the best place to start.
The vertical, end to end
A policy is a (Detector, Policy) pair plus a build() factory. That is the whole contract.
config YAML -> build_governor -> Governor
policies: (config.py) |
my_policy: v
threshold: 3 Detector.observe(attr, step, view)
| returns Signal | None
v
Policy.decide(signal, view)
| returns Action
v
controls.apply(action)
Detector reads the ledger and returns a Signal (or None). It never writes and never decides. Keep it cheap: cost_micros, step_count, budget_left and inflight are O(1); recent and velocity are O(window); window is O(N), do not call it on every observe.
Policy turns a Signal into an Action. The ActionKind you pick is the whole design decision. See the actuator issue for what each one does.
Moment decides when your detector runs:
Moment
Runs
Use for
pre_call
before a model call is dispatched
anything preventive
stream
while tokens arrive
runaway output
observe
after a boundary crossing is recorded
accounting, trajectory
Preventive work belongs in pre_call. observe is a backstop and always overshoots by one call, which is exactly why cost_budget can end a run a little over its cap and pre_call_worst_case exists alongside it.
Files
What
Where
Your policy
src/tokenops/control/policies/<name>.py
Register it
src/tokenops/control/policies/__init__.py and _TEMPLATES in src/tokenops/control/config.py
Vocabulary you must not change
src/tokenops/control/core.py
Seed config
src/tokenops/config/default.yaml
Docs, one page per policy
docs/policies/<name>.md
Unit test
tests/test_<name>.py
Integration through a real Governor
tests/test_policies_wrap_integration.py
src/tokenops/control/policies/step_cap.py is 60 lines and the best template to copy.
Unit tests use FakeView and cover detect and decide only. The integration suite wires a real Governor and a scripted dispatch, offline, no API keys.
What is worth adding
Pick one, or propose your own. Comment before you start so we can agree on the moment and the ActionKind.
1. time_budget — halt a run that has been alive past a wall-clock ceiling. Cheap, no pricing dependency, and the natural sibling of step_cap for workflows whose cost is latency rather than tokens. observe moment, HALT.
2. cache_guard — detect when a prompt prefix stops being stable across calls, so the provider's cache discount silently disappears and cost jumps with no visible change in behaviour. pre_call, MUTATE or a telemetry-only signal to start.
3. tool_error_budget — tool_fix catches identical bad calls. It does not catch a tool that fails 40% of the time with different arguments each time. Trip on an error rate over a window. observe, INJECT then HALT.
4. model_downgrade_ladder — cost_guard minimizes at 80% of budget. A ladder would step down through an ordered list of models as the budget drains, instead of one jump. pre_call, MUTATE with downgrade_to.
5. fan_out_cap scoped by intent — concurrency_cap is global per segment. A per-intent cap would let a research phase run wide while a review phase stays serial. Depends on #54.
6. retrieval_budget — cap the number of retrieved context chunks admitted per run. Retrieval is where a lot of token spend hides and no policy sees it today. observe, MUTATE.
Definition of done
A build() factory returning (Detector, Policy).
Registered in policies/__init__.py and in _TEMPLATES.
A unit test for detect and decide, plus a case in tests/test_policies_wrap_integration.py.
A page in docs/policies/ following the shape of the existing ten.
This is the standing entry point for contributing a policy. A policy is the smallest useful unit of TokenOps and needs no changes to the core, so it is the best place to start.
The vertical, end to end
A policy is a
(Detector, Policy)pair plus abuild()factory. That is the whole contract.Detector reads the ledger and returns a
Signal(orNone). It never writes and never decides. Keep it cheap:cost_micros,step_count,budget_leftandinflightare O(1);recentandvelocityare O(window);windowis O(N), do not call it on every observe.Policy turns a
Signalinto anAction. TheActionKindyou pick is the whole design decision. See the actuator issue for what each one does.Moment decides when your detector runs:
pre_callstreamobservePreventive work belongs in
pre_call.observeis a backstop and always overshoots by one call, which is exactly whycost_budgetcan end a run a little over its cap andpre_call_worst_caseexists alongside it.Files
src/tokenops/control/policies/<name>.pysrc/tokenops/control/policies/__init__.pyand_TEMPLATESinsrc/tokenops/control/config.pysrc/tokenops/control/core.pysrc/tokenops/config/default.yamldocs/policies/<name>.mdtests/test_<name>.pytests/test_policies_wrap_integration.pysrc/tokenops/control/policies/step_cap.pyis 60 lines and the best template to copy.Focused loop
Unit tests use
FakeViewand cover detect and decide only. The integration suite wires a realGovernorand a scripted dispatch, offline, no API keys.What is worth adding
Pick one, or propose your own. Comment before you start so we can agree on the moment and the
ActionKind.1.
time_budget— halt a run that has been alive past a wall-clock ceiling. Cheap, no pricing dependency, and the natural sibling ofstep_capfor workflows whose cost is latency rather than tokens.observemoment,HALT.2.
cache_guard— detect when a prompt prefix stops being stable across calls, so the provider's cache discount silently disappears and cost jumps with no visible change in behaviour.pre_call,MUTATEor a telemetry-only signal to start.3.
tool_error_budget—tool_fixcatches identical bad calls. It does not catch a tool that fails 40% of the time with different arguments each time. Trip on an error rate over a window.observe,INJECTthenHALT.4.
model_downgrade_ladder—cost_guardminimizes at 80% of budget. A ladder would step down through an ordered list of models as the budget drains, instead of one jump.pre_call,MUTATEwithdowngrade_to.5.
fan_out_capscoped by intent —concurrency_capis global per segment. A per-intent cap would let a research phase run wide while a review phase stays serial. Depends on #54.6.
retrieval_budget— cap the number of retrieved context chunks admitted per run. Retrieval is where a lot of token spend hides and no policy sees it today.observe,MUTATE.Definition of done
build()factory returning(Detector, Policy).policies/__init__.pyand in_TEMPLATES.tests/test_policies_wrap_integration.py.docs/policies/following the shape of the existing ten.make lintandmake testgreen.Ask here if any of this is unclear. A question on this issue is a contribution.