Skip to main content

slackwater-tminus

tests version python

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)

Source distribution for slackwater-tminus 0.1.0
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)

Table of built distributions (wheels) for slackwater-tminus 0.1.0
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page