slackwater-tminus
Predict-and-confirm timing that replaces polling. Declare future events with predicted completion times in beats. Subscribers confirm readiness. When quorum is met and the countdown reaches zero, precompiled scripts fire — zero latency, one notification, no polling. Countdowns are measured in beats, not seconds, integrating directly with slackwater-tempo's BeatClock.
Installation
pip install slackwater-tminus
Core Concept
Polling: "Is it done yet? Is it done yet? Is it done yet?" — N messages.
T-Minus: "It will be done at beat 16." + "Confirmed." — 2 messages. The fire is just the trigger pull.
For a 60-second job polled at 0.5s intervals, T-Minus saves 118 messages (60× reduction).
API Reference
CountdownEvent
from slackwater_tminus import CountdownEvent, CountdownState
CountdownEvent(
name: str,
predicted_beat: float,
id: str = <auto>,
quorum: int = 1,
script: Callable | None = None,
metadata: dict = {},
)
The core primitive. A future event with a predicted completion beat.
State machine:
PENDING → PREDICTED → CONFIRMED → FIRED
↘ ↘
MISSED MISSED
| State | Meaning |
|---|---|
PENDING |
Event declared, prediction not committed |
PREDICTED |
Prediction committed, awaiting confirmations |
CONFIRMED |
Quorum reached, will fire at predicted beat |
FIRED |
Beat arrived, scripts executed, subscribers notified |
MISSED |
Prediction wrong or quorum not reached in time |
Lifecycle methods:
event.commit_prediction() -> None
event.subscribe(subscriber_id: str) -> None # auto-commits if PENDING
event.confirm(subscriber_id: str) -> bool # True if quorum reached
event.defer(subscriber_id: str, reason: str = "") -> None
event.miss(subscriber_id: str = "", reason: str = "") -> None
event.fire(actual_beat: float | None = None) -> Any # executes script
event.force_miss(reason: str = "forced") -> None
Properties:
event.state -> CountdownState
event.quorum_met -> bool
event.is_terminal -> bool # FIRED or MISSED
event.accuracy -> float | None # 1.0 = perfect, None if not fired
event.subscribers -> frozenset[str]
event.confirmations -> frozenset[str]
event.deferrals -> dict[str, str]
event.missed_by -> frozenset[str]
event.result -> Any # script return value
Accuracy formula:
accuracy = max(0.0, 1.0 − |predicted_beat − actual_beat| / max(predicted_beat, 1.0))
TMinusPredictor
from slackwater_tminus import TMinusPredictor, PredictionResult
TMinusPredictor(
bpm: float = 60.0,
start_beat: float = 0.0,
)
Orchestrates countdown events. Maintains a beat timeline, advances through it, and fires events when their predicted beats arrive.
Prediction:
event = predictor.predict(
name: str,
beats_ahead: float,
*,
quorum: int = 1,
script: Callable | None = None,
confidence: float = 0.8,
metadata: dict | None = None,
) -> CountdownEvent
Creates an event at current_beat + beats_ahead and immediately commits to PREDICTED.
Advancing time:
fired: list[CountdownEvent] = predictor.advance(beats: float)
fired: list[CountdownEvent] = predictor.tick() # shortcut for advance(1)
Returns events that FIRED at this tick. Events whose predicted beat arrived with quorum met → FIRED. Without quorum → MISSED.
Queries:
predictor.predict_next() -> CountdownEvent | None # nearest pending
predictor.countdown_beats() -> float | None # beats to next event
predictor.countdown_seconds() -> float | None # wall-clock estimate
predictor.get(event_id) -> CountdownEvent | None
predictor.get_by_name(name: str) -> list[CountdownEvent]
predictor.pending_events -> list[CountdownEvent]
predictor.fired_events -> list[CountdownEvent]
predictor.missed_events -> list[CountdownEvent]
predictor.avg_accuracy -> float
Calibration:
predictor.calibrate() -> dict[str, float]
Returns avg_accuracy, avg_lead_time_beats, fire_rate, total_predictions, fired, missed.
predictor.message_savings(polling_interval_s: float = 0.5) -> dict[str, int]
Compares T-Minus message count vs polling. Returns polling_messages, tminus_messages, savings_ratio.
PrecompiledScript
from slackwater_tminus import PrecompiledScript
from slackwater_tminus.precompiled import compile
PrecompiledScript(
action: Callable[..., Any],
label: str = "",
args: tuple = (),
kwargs: dict = {},
)
# Convenience function
script = compile("tower_build", build_function, height=10, material="stone)
Actions attached to predictions, ready for zero-latency execution. The work of figuring out WHAT to do is done during the countdown. The fire is the trigger pull.
script.execute() -> Any # idempotent — returns cached result
script.is_executed -> bool
script.result -> Any
script.latency_ms -> float | None # lead time: compile→execute
Subscriber
from slackwater_tminus import Subscriber, SubscriberState
Subscriber(id: str)
A participant in the predict-and-confirm cycle. Tracks state across multiple events independently.
Subscriber state machine (per event):
UNINFORMED → PENDING → CONFIRMED → NOTIFIED
↘ ↘
DEFERRED MISSED
sub.subscribe(event_id: str) -> None
sub.confirm(event_id: str) -> SubscriberState # → CONFIRMED
sub.defer(event_id: str, reason: str = "") -> SubscriberState # → DEFERRED
sub.miss(event_id: str, reason: str = "") -> SubscriberState # → MISSED (terminal)
sub.notify(event_id: str) -> SubscriberState # → NOTIFIED
Bulk operations:
sub.confirm_all(event_ids: list[str]) -> None
sub.stats() -> dict[str, int] # counts by state name
Properties:
sub.event_ids -> frozenset[str]
sub.confirmed_events -> frozenset[str]
sub.deferred_events -> frozenset[str]
sub.missed_events -> frozenset[str]
sub.state_for(event_id) -> SubscriberState
BeatClock
from slackwater_tminus import BeatClock
BeatClock(bpm: float = 60.0, current_beat: float = 0.0)
A clock that counts beats instead of seconds. Can auto-sync to wall-clock time or be advanced manually.
clock.sync() -> float # sync with wall-clock time
clock.advance(beats: float) -> float # manual advance
clock.beats_to_seconds(beats: float) -> float
clock.seconds_to_beats(seconds: float) -> float
clock.set_bpm(bpm: float, *, resync: bool = True) -> None
clock.beat_duration -> float # 60.0 / bpm
BeatCountdown
from slackwater_tminus import BeatCountdown
BeatCountdown(clock: BeatClock | None = None, bpm: float = 60.0)
Thin wrapper around TMinusPredictor using a BeatClock for timing. All durations are in beats. Clock and events are coupled — advancing the clock fires events.
event = bc.schedule("build_complete", beats=16, quorum=1) -> CountdownEvent
bc.subscribe(event_id, subscriber_id) -> CountdownEvent
bc.confirm(event_id, subscriber_id) -> bool # True if quorum reached
bc.defer(event_id, subscriber_id, reason) -> None
bc.advance(beats) -> list[CountdownEvent] # advances clock + predictor
bc.tick() -> list[CountdownEvent] # advance one beat
bc.sync() -> list[CountdownEvent] # wall-clock sync
bc.remaining_beats(event_id) -> float | None
bc.remaining_seconds(event_id) -> float | None
Examples
Full predict → confirm → fire cycle
from slackwater_tminus import TMinusPredictor, PrecompiledScript
predictor = TMinusPredictor(bpm=60)
script = PrecompiledScript(
action=lambda: print("Castle complete!"),
label="castle_build",
)
event = predictor.predict("castle_complete", beats_ahead=16, script=script.execute)
event.subscribe("player_1")
event.confirm("player_1") # quorum=1, so this confirms
assert event.state.name == "CONFIRMED"
fired = predictor.advance(16)
assert event in fired
assert script.is_executed # script ran with zero planning latency
Quorum with multiple subscribers
predictor = TMinusPredictor(bpm=60)
event = predictor.predict("consensus_vote", beats_ahead=32, quorum=3)
for agent in ["alice", "bob", "carol"]:
event.subscribe(agent)
event.confirm(agent)
# After all 3 confirm → CONFIRMED. Advance to fire.
fired = predictor.advance(32)
Beat-space scheduling with tempo change
from slackwater_tminus import BeatCountdown
bc = BeatCountdown(bpm=60)
event = bc.schedule("build", beats=16)
bc.confirm(event.id, "a")
bc.advance(8) # halfway
print(bc.remaining_beats(event.id)) # 8.0
bc.clock.set_bpm(120, resync=False) # tempo doubles
print(bc.remaining_seconds(event.id)) # 4.0 (halved)
print(bc.remaining_beats(event.id)) # 8.0 (unchanged — beats are tempo-relative)
fired = bc.advance(8) # fire
Message savings vs polling
predictor = TMinusPredictor(bpm=60)
event = predictor.predict("long_job", beats_ahead=60) # 60-second job
event.subscribe("a")
event.confirm("a")
predictor.advance(60)
savings = predictor.message_savings(polling_interval_s=0.5)
print(savings)
# {'polling_messages': 120, 'tminus_messages': 2, 'savings_ratio': 60.0}
License
MIT
Metadata
Release files for slackwater-tminus 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| slackwater_tminus-0.1.0.tar.gz | 23.5 kB | Without provenance | Details for slackwater_tminus-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| slackwater_tminus-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for slackwater_tminus-0.1.0-py3-none-any.whl |
Total release size: 41.9 kB
Release files / slackwater_tminus-0.1.0.tar.gz
| Download URL | slackwater_tminus-0.1.0.tar.gz |
|---|---|
| Size | 23.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
475089ab7430c3048d01c79e9be258bcde1e9b7e645b33c55d0c8fd4e5481703
|
|
BLAKE2b-256 checksum How to use checksums |
4699e0ead490ab18e7138fcac1fd22144206876acaeb2c63ff7c1420594473cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / slackwater_tminus-0.1.0-py3-none-any.whl
| Download URL | slackwater_tminus-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
503f298ef59b1683645211fd18aac4b5efb4bceb096fc844ef315043771c61da
|
|
BLAKE2b-256 checksum How to use checksums |
7a06f355ab18c35fea8d819b06e46b95e1241bd9cd6127d94b32de8d17ec055e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Security for slackwater-tminus 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.