Event-driven notify layer for agentic CLIs. Turn passive AI assistants into tapped-on-the-shoulder agents.
Project description
waitless
Turn your AI assistant into a tapped-on-the-shoulder agent.
When something finishes in the background - a build, a test suite, a deploy, a scrape, an email arriving, a long-running Codex/Claude task - waitless makes sure the active session finds out on its very next tool call. No polling. No HTTP server. No middleware. Just a file and a hook.
Born out of Pileless (the agent-to-agent workflow platform) and spun off free under the -less brand.
Why
Claude Code, Codex CLI, and similar agentic tools are passive by default - they wait for you to type. If a background job finishes while you're not looking, neither of you notices. waitless flips that: the moment a thing happens, the next tool call surfaces it in the model's attention. You stop being the middleman.
The pattern is deliberately small:
- The file IS the bus. Every event is one JSON line appended to
~/.waitless/events.jsonl. - The hook IS the tap. A
PostToolUsehook reads the file on every tool call and surfaces undelivered events as a system-reminder. - No server. No port, no auth surface, no subscriber registry, no process to crash.
Install
pip install pileless-waitless
Or vendor the three files in waitless/ directly into your project - no dependencies beyond Python 3.9+.
Wire it into Claude Code
Add to .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash|Edit|Write|MultiEdit",
"hooks": [
{ "type": "command", "command": "python -m waitless.tap" }
]
}
]
}
}
That's it. Now anything that writes to ~/.waitless/events.jsonl will tap your session.
Emit events
From Python:
from waitless import emit
emit(
source="my-build",
lane="default",
severity="attention",
title="deploy finished",
body="shipped v1.2.3 to prod",
action_hint="verify the rollout",
)
From the CLI:
python -m waitless.core \
--source my-build \
--lane default \
--severity attention \
--title "deploy finished" \
--action-hint "verify the rollout"
Wrap any command so it taps on completion:
# (use waitless.wrap - ships with the package)
python -m waitless.wrap --lane default --source build --title "tests" -- pytest
Severity ladder
You decide how urgent each event is. The hook decides what to do with it:
| Severity | What happens |
|---|---|
info |
Queue silently; drain at session start |
attention |
Surface to the active session - the model acts on it |
emergency |
Surface + the model acts NOW |
operator-needed |
The only rung that pages the human (via a Telegram bot, webhook relay, or any URL you point it at) |
The whole point: the AI handles everything it can. The human only hears about things that genuinely need a human.
Lanes
Events are tagged with a lane (e.g. default, scrape, money, pile). The hook only surfaces events on the active session's lane, so multiple sessions don't cross-talk. Set WAITLESS_LANE=your-lane per session, or write ~/.waitless/lane.json keyed by session id.
Security model
The events file is writable by your user. That means any process running as you can emit events - including, in theory, a malicious one trying to prompt-inject the model via a crafted event title. waitless defends against this:
- Event titles/bodies are sanitized (brackets + newlines stripped, length-capped).
- Every surfaced line is labeled
(untrusted source=X)so the model knows not to treat event content as a system instruction.
If you want stronger isolation, restrict the events file's permissions to a dedicated user, or run waitless under a separate account.
Self-monitoring
waitless ships with a deadman (waitless.deadman) that checks the tap is actually draining. If a live session ran and still failed to clear a stale attention+ event, or the events file grows unbounded, the deadman emits an operator-needed event that pages the human. It won't page just because nobody's been using their agent - it only fires when a consumer demonstrably ran and the backlog still didn't clear. Wire it as a cron job / scheduled task every 30 minutes.
What's NOT in waitless
- No HTTP server. (Use ntfy, ifttt, or any webhook relay if you need cross-machine paging.)
- No real-time interrupt. The tap lands at the next tool boundary, not mid-call. Reviewers agreed this is the right tradeoff.
- No subscriber registry. Lane env-var is the only per-session state.
License
MIT. Free as in beer and speech. Use it, fork it, sell it, just don't blame us.
Acknowledgements
Spun off from Pileless - if waitless is useful, you'll probably love Pileless for the full agent-to-agent workflow (structured handoffs, approvals, real-time piles). Check it out.
Project details
Release history Release notifications | RSS feed
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 pileless_waitless-0.1.0.tar.gz.
File metadata
- Download URL: pileless_waitless-0.1.0.tar.gz
- Upload date:
- Size: 11.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1c59c7aee8e8ad200ceb24ca36d535ba7424bb89c826b6b2494700ac50f664f
|
|
| MD5 |
0677f07a7361ec3ccd412393101cc4ad
|
|
| BLAKE2b-256 |
568451b5726ff4044a6c2ca70804fe185af84422fbe820d49355718433e934c0
|
File details
Details for the file pileless_waitless-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pileless_waitless-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45a8ce2bf50b1075e5ab2ecde769d6e71bd23d0b72852a4a79081e5c93a6a37e
|
|
| MD5 |
cdf4f4b292147328adf599439930e538
|
|
| BLAKE2b-256 |
31cb5727755600bddbc6a70be3a5225b935b87305495b5e33eb0b875754067a3
|