intervalguard
An agent fetches a fact, does three more things, and then acts on that fact — but the fact changed while the agent was busy. Nothing errors, nothing looks wrong, and the agent confidently commits to a stale belief. intervalguard makes that failure loud: it tracks when each tool call read the world, and raises StaleReadError the moment a decision depends on a read that time or a later write has invalidated.
A TTL alone will not catch this. A read can be well inside its validity window and still be wrong, because something else wrote to the same resource in the meantime. intervalguard checks both.
The failure mode
Delayed verification destabilizing multi-agent belief: a "corrector" agent fetches ground truth, but its fetch is slow and its correction lands later. By then the ground truth has moved, so the correction actively pushes the group toward the wrong consensus — and it does so with the authority of having "checked."
The runnable illustration is demo/debate.py: two debaters, one corrector, and an evidence store that flips mid-flight. The same scenario runs twice — once bare, once instrumented.
Install
pip install -e ".[dev]"
Python 3.12+. Standard library only; pytest is the sole dev dependency.
Usage
from intervalguard import tracked, last_event, StaleReadError
@tracked(name="evidence:reactor_2_status", validity_window_seconds=30, writes=False)
def fetch_status():
return evidence_store["reactor_2_status"]
@tracked(name="evidence:reactor_2_status", validity_window_seconds=30, writes=True)
def write_status(value):
evidence_store["reactor_2_status"] = value
@tracked(name="debate:consensus", validity_window_seconds=30, writes=False)
def apply_correction(observed):
return f"consensus based on {observed}"
status = fetch_status()
read_event = last_event()
write_status("OFFLINE") # someone else changes the world
try:
apply_correction(status, depends_on=[read_event.id])
except StaleReadError as exc:
print(exc)
# event 'evidence:reactor_2_status' (id=4dcecf80) is stale:
# superseded by 'evidence:reactor_2_status' (id=190c1b88) at 2026-07-30T18:44:41Z
depends_on is consumed by the decorator and never passed to your function. last_event() returns the Event your most recent tracked call produced, so the next call can declare what it read from. It is context-local, so concurrent callers of the same function each see their own event.
What it checks
A dependency is reported stale when either:
- Expiry —
now - read.end_timeexceeds the read'svalidity_window_seconds. - Supersession — a later write to the same
nameended after the read did, meaning the underlying resource changed while the result was in hand. This fires even when the read is still inside its window. Only writes supersede: another agent reading the same resource never invalidates your read.
A declared dependency id that is not in the registry raises UnknownDependencyError rather than being skipped — an unverifiable dependency is not a verified one.
API
Event—id,name,start_time,end_time,validity_window_seconds,depends_on,kind("read"or"write"), andseq, a monotonic counter that breaks ties when two events carry identical timestamps.relation(a, b) -> str— one ofbefore,meets,overlaps,during,after, from the two intervals alone.is_stale(event, now) -> boolcheck_dependencies(event, all_events, now=None) -> list[str]— names of dependencies that expired or were superseded.tracked(name, validity_window_seconds, writes=False)— decorator; registers anEventper call and raisesStaleReadErrorbefore returning if any declared dependency is stale.last_event()— theEventproduced by the most recent tracked call in the current context.registry/clear_registry()— the in-memory event log.
Run the demo
python demo/debate.py
Run 1 reaches a wrong consensus. Run 2 raises StaleReadError before the wrong correction is applied, naming the read that went stale and when.
Tests
pytest
Scope
v1, built for a demo and an open-source release. In-memory registry, single process, five interval relations rather than the full Allen set. No hosted service, dashboard, or framework integrations.
License
MIT
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 intervalguard-0.1.1.tar.gz.
File metadata
- Download URL: intervalguard-0.1.1.tar.gz
- Upload date:
- Size: 10.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67a9cc1c82a6d04aab179fb270beeb5fd720666dd3acbbb0db8fc34ddbc4e0eb
|
|
| MD5 |
66419ae57c209a20a8e6f7b22af3eeef
|
|
| BLAKE2b-256 |
a2978ff098428be9ff2936a0b6e8b64f3156700c014c440746dcd614ae3cf4b3
|
File details
Details for the file intervalguard-0.1.1-py3-none-any.whl.
File metadata
- Download URL: intervalguard-0.1.1-py3-none-any.whl
- Upload date:
- Size: 7.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe81f58ab81142d6bf5a7a824163f27dc03b736b6df0418033fad9c8b2332aec
|
|
| MD5 |
747622831c7dbd59786d1678abf83607
|
|
| BLAKE2b-256 |
b0d67302ba52bbf4f2f7815b654d74d1c93491fe675845d3eb313a057e3bb551
|