superwait 0.2.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. superwait-0.3.1/PKG-INFO +121 -0
  2. superwait-0.3.1/README.md +107 -0
  3. superwait-0.3.1/docs/assets/superwait-banner.png +0 -0
  4. {superwait-0.2.0 → superwait-0.3.1}/docs/hosts.md +74 -1
  5. superwait-0.3.1/docs/reference.md +39 -0
  6. superwait-0.3.1/docs/savings.md +46 -0
  7. {superwait-0.2.0 → superwait-0.3.1}/pyproject.toml +1 -1
  8. superwait-0.3.1/src/superwait/SKILL.md +30 -0
  9. {superwait-0.2.0 → superwait-0.3.1}/src/superwait/cli.py +9 -2
  10. {superwait-0.2.0 → superwait-0.3.1}/src/superwait/engine.py +27 -3
  11. {superwait-0.2.0 → superwait-0.3.1}/src/superwait/models.py +21 -6
  12. superwait-0.3.1/src/superwait/references/api.md +258 -0
  13. {superwait-0.2.0 → superwait-0.3.1}/src/superwait/server.py +5 -3
  14. {superwait-0.2.0 → superwait-0.3.1}/src/superwait/setup.py +29 -12
  15. {superwait-0.2.0 → superwait-0.3.1}/src/superwait/store.py +13 -0
  16. superwait-0.3.1/tests/test_hooks_setup.py +230 -0
  17. superwait-0.3.1/tests/test_observations.py +123 -0
  18. {superwait-0.2.0 → superwait-0.3.1}/tests/test_transport.py +14 -0
  19. {superwait-0.2.0 → superwait-0.3.1}/uv.lock +1 -1
  20. superwait-0.2.0/PKG-INFO +0 -146
  21. superwait-0.2.0/README.md +0 -132
  22. superwait-0.2.0/src/superwait/SKILL.md +0 -95
  23. superwait-0.2.0/tests/test_hooks_setup.py +0 -91
  24. {superwait-0.2.0 → superwait-0.3.1}/.gitignore +0 -0
  25. {superwait-0.2.0 → superwait-0.3.1}/CONTRIBUTING.md +0 -0
  26. {superwait-0.2.0 → superwait-0.3.1}/LICENSE +0 -0
  27. {superwait-0.2.0 → superwait-0.3.1}/src/superwait/__init__.py +0 -0
  28. {superwait-0.2.0 → superwait-0.3.1}/src/superwait/__main__.py +0 -0
  29. {superwait-0.2.0 → superwait-0.3.1}/src/superwait/hooks.py +0 -0
  30. {superwait-0.2.0 → superwait-0.3.1}/tests/test_ergonomics.py +0 -0
  31. {superwait-0.2.0 → superwait-0.3.1}/tests/test_wait.py +0 -0
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.5
2
+ Name: superwait
3
+ Version: 0.3.1
4
+ Summary: Conditional waiting for coding agents
5
+ Project-URL: Repository, https://github.com/ctxrs/superwait
6
+ Project-URL: Issues, https://github.com/ctxrs/superwait/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: httpx2<3,>=2.13
11
+ Requires-Dist: mcp<3,>=2.2
12
+ Requires-Dist: pydantic<3,>=2.12
13
+ Description-Content-Type: text/markdown
14
+
15
+ <img src="https://raw.githubusercontent.com/ctxrs/superwait/main/docs/assets/superwait-banner.png" alt="20% of your tokens are spent on the wait tool call. superwait cuts that in half, so you save 10% of total spend." width="100%">
16
+
17
+ **superwait gives coding agents one place to wait for the outcome they need.** It works with Codex, Claude Code, and Cursor, runs locally, and makes no model calls while checking conditions.
18
+
19
+ [Install](#install) · [How it works](#how-it-works) · [Host setup](https://github.com/ctxrs/superwait/blob/main/docs/hosts.md) · [Reference](https://github.com/ctxrs/superwait/blob/main/docs/reference.md) · [About the numbers](https://github.com/ctxrs/superwait/blob/main/docs/savings.md)
20
+
21
+ ## Why use superwait?
22
+
23
+ A build is still running. A reviewer hasn't finished. A service isn't ready yet. Your agent checks, reads “still running,” and calls another wait.
24
+
25
+ Each trip back to the model can process the conversation again just to decide to keep waiting. With several workers, it also has to track which results arrived, which ones matter, and how much time is left.
26
+
27
+ superwait lets the agent describe that outcome once:
28
+
29
+ > Wait for two of the three reviewers. Wake early if the test worker reports a blocker. Stop after 30 minutes.
30
+
31
+ Local code checks the conditions. The agent gets the completed results and remaining work when there is something to act on. If a blocker interrupts the wait, it can resume with the original deadline intact.
32
+
33
+ ## Install
34
+
35
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if you don't have it. uv manages Python for you, so there is no separate Python installation step.
36
+
37
+ ```sh
38
+ uv tool install --python 3.12 superwait
39
+ ```
40
+
41
+ ### Set up your host once (recommended)
42
+
43
+ Run **one** command for your coding agent. It applies across your projects for your user account:
44
+
45
+ ```sh
46
+ superwait setup codex
47
+ # Or, for your host:
48
+ superwait setup claude
49
+ superwait setup cursor
50
+ ```
51
+
52
+ Restart your coding agent and review its normal MCP and hook trust prompts. Set up before spawning workers so their lifecycle events can be recorded.
53
+ For Codex, open `/hooks` and trust the three superwait hooks. After spawning a
54
+ worker, `superwait doctor codex` checks whether its events are reaching superwait.
55
+ See [installation checks](https://github.com/ctxrs/superwait/blob/main/docs/hosts.md#confirm-the-installation-is-active) if they aren't.
56
+
57
+ Then ask your agent:
58
+
59
+ > Use superwait to wait for both reviewers, but return early if either reports a blocker. Stop after 30 minutes.
60
+
61
+ Setup adds the tool, lifecycle hooks, and instructions to your host's user configuration. It preserves unrelated settings, backs up files it changes, and doesn't add files to your repositories. There is no superwait account, API key, or hosted service to configure.
62
+
63
+ ### Only need it in one repository?
64
+
65
+ Use project-only setup instead:
66
+
67
+ ```sh
68
+ superwait setup codex --project .
69
+ ```
70
+
71
+ Use `claude` or `cursor` for those hosts. Choose one scope for each project to avoid duplicate hooks. See [setup locations and existing installations](https://github.com/ctxrs/superwait/blob/main/docs/hosts.md#installation-scope) for details.
72
+
73
+ ## How it works
74
+
75
+ 1. **Describe the result.** Wait for any, all, or a chosen number of workers and conditions. Add a deadline and anything that should wake the agent early.
76
+ 2. **Let local code watch.** The wait engine checks conditions concurrently. Hooks record worker events; file, HTTP, and command checks run locally as requested.
77
+ 3. **Continue with useful results.** The response explains why the wait ended, includes completed reports, and lists what remains pending. A ready-to-use continuation preserves the deadline and remaining count.
78
+
79
+ The same engine is available as an MCP tool and a CLI. For a long wait that exceeds your host's tool timeout, the agent can use its background terminal.
80
+
81
+ ### What can you wait for?
82
+
83
+ | You need… | superwait watches… |
84
+ | --- | --- |
85
+ | Enough reviews to proceed | Any, all, or a quorum of subagents |
86
+ | A checkpoint or blocker | An explicit signal from a worker or script |
87
+ | An artifact to be ready | A file appearing, changing, disappearing, or containing text |
88
+ | A service to come online | An HTTP response with the requested status |
89
+ | A custom readiness check | A command returning the requested exit code |
90
+
91
+ Early-wake conditions can be combined with any of these. A timeout returns the partial results; it does not cancel the workers.
92
+
93
+ See the [request and result reference](https://github.com/ctxrs/superwait/blob/main/docs/reference.md) for examples, continuation behavior, and CLI usage.
94
+
95
+ ## When does it help most?
96
+
97
+ Use superwait when an agent keeps checking unchanged state, or when several workers and external conditions need to be coordinated together. A single native wait is often enough for one worker. superwait adds the compound conditions, early wakeups, and shared deadline.
98
+
99
+ It does not replace your host's built-in wait tools automatically. Your agent needs to use it, and the condition must be observable through a supported hook or check.
100
+
101
+ ## Works with ctx
102
+
103
+ superwait is made by **ctx engineering**, the team behind [ctx](https://github.com/ctxrs/ctx).
104
+
105
+ We recommend installing ctx alongside superwait. ctx gives your coding agents fast, local search across their past sessions, so they can recover earlier decisions, reuse investigations, and find solutions they've already worked out. Results link back to the original messages and tool calls.
106
+
107
+ [Install ctx and get started →](https://github.com/ctxrs/ctx#install-and-set-up-ctx)
108
+
109
+ ## About the numbers
110
+
111
+ Our corpus study found that wait-only model responses accounted for about **20% of input tokens**. Auditing repeated waits identified roughly half of that input as a consolidation opportunity—about **9–10% of total input**, before replacement overhead.
112
+
113
+ Actual token and spend savings depend on your corpus, model pricing, caching, and host integration; input-token savings are not the same as measured bill savings. [See the study and how to evaluate your own workload](https://github.com/ctxrs/superwait/blob/main/docs/savings.md).
114
+
115
+ ## Host support and details
116
+
117
+ Linux and Codex have live integration coverage. Claude Code and Cursor have adapter tests; full live workflows in those hosts, other operating systems, and multi-hour host waits still need qualification.
118
+
119
+ - [Host setup, permissions, and compatibility](https://github.com/ctxrs/superwait/blob/main/docs/hosts.md)
120
+ - [API, CLI, long waits, and local data](https://github.com/ctxrs/superwait/blob/main/docs/reference.md)
121
+ - [Contributing](https://github.com/ctxrs/superwait/blob/main/CONTRIBUTING.md) · [PyPI](https://pypi.org/project/superwait/) · [MIT license](https://github.com/ctxrs/superwait/blob/main/LICENSE)
@@ -0,0 +1,107 @@
1
+ <img src="https://raw.githubusercontent.com/ctxrs/superwait/main/docs/assets/superwait-banner.png" alt="20% of your tokens are spent on the wait tool call. superwait cuts that in half, so you save 10% of total spend." width="100%">
2
+
3
+ **superwait gives coding agents one place to wait for the outcome they need.** It works with Codex, Claude Code, and Cursor, runs locally, and makes no model calls while checking conditions.
4
+
5
+ [Install](#install) · [How it works](#how-it-works) · [Host setup](https://github.com/ctxrs/superwait/blob/main/docs/hosts.md) · [Reference](https://github.com/ctxrs/superwait/blob/main/docs/reference.md) · [About the numbers](https://github.com/ctxrs/superwait/blob/main/docs/savings.md)
6
+
7
+ ## Why use superwait?
8
+
9
+ A build is still running. A reviewer hasn't finished. A service isn't ready yet. Your agent checks, reads “still running,” and calls another wait.
10
+
11
+ Each trip back to the model can process the conversation again just to decide to keep waiting. With several workers, it also has to track which results arrived, which ones matter, and how much time is left.
12
+
13
+ superwait lets the agent describe that outcome once:
14
+
15
+ > Wait for two of the three reviewers. Wake early if the test worker reports a blocker. Stop after 30 minutes.
16
+
17
+ Local code checks the conditions. The agent gets the completed results and remaining work when there is something to act on. If a blocker interrupts the wait, it can resume with the original deadline intact.
18
+
19
+ ## Install
20
+
21
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if you don't have it. uv manages Python for you, so there is no separate Python installation step.
22
+
23
+ ```sh
24
+ uv tool install --python 3.12 superwait
25
+ ```
26
+
27
+ ### Set up your host once (recommended)
28
+
29
+ Run **one** command for your coding agent. It applies across your projects for your user account:
30
+
31
+ ```sh
32
+ superwait setup codex
33
+ # Or, for your host:
34
+ superwait setup claude
35
+ superwait setup cursor
36
+ ```
37
+
38
+ Restart your coding agent and review its normal MCP and hook trust prompts. Set up before spawning workers so their lifecycle events can be recorded.
39
+ For Codex, open `/hooks` and trust the three superwait hooks. After spawning a
40
+ worker, `superwait doctor codex` checks whether its events are reaching superwait.
41
+ See [installation checks](https://github.com/ctxrs/superwait/blob/main/docs/hosts.md#confirm-the-installation-is-active) if they aren't.
42
+
43
+ Then ask your agent:
44
+
45
+ > Use superwait to wait for both reviewers, but return early if either reports a blocker. Stop after 30 minutes.
46
+
47
+ Setup adds the tool, lifecycle hooks, and instructions to your host's user configuration. It preserves unrelated settings, backs up files it changes, and doesn't add files to your repositories. There is no superwait account, API key, or hosted service to configure.
48
+
49
+ ### Only need it in one repository?
50
+
51
+ Use project-only setup instead:
52
+
53
+ ```sh
54
+ superwait setup codex --project .
55
+ ```
56
+
57
+ Use `claude` or `cursor` for those hosts. Choose one scope for each project to avoid duplicate hooks. See [setup locations and existing installations](https://github.com/ctxrs/superwait/blob/main/docs/hosts.md#installation-scope) for details.
58
+
59
+ ## How it works
60
+
61
+ 1. **Describe the result.** Wait for any, all, or a chosen number of workers and conditions. Add a deadline and anything that should wake the agent early.
62
+ 2. **Let local code watch.** The wait engine checks conditions concurrently. Hooks record worker events; file, HTTP, and command checks run locally as requested.
63
+ 3. **Continue with useful results.** The response explains why the wait ended, includes completed reports, and lists what remains pending. A ready-to-use continuation preserves the deadline and remaining count.
64
+
65
+ The same engine is available as an MCP tool and a CLI. For a long wait that exceeds your host's tool timeout, the agent can use its background terminal.
66
+
67
+ ### What can you wait for?
68
+
69
+ | You need… | superwait watches… |
70
+ | --- | --- |
71
+ | Enough reviews to proceed | Any, all, or a quorum of subagents |
72
+ | A checkpoint or blocker | An explicit signal from a worker or script |
73
+ | An artifact to be ready | A file appearing, changing, disappearing, or containing text |
74
+ | A service to come online | An HTTP response with the requested status |
75
+ | A custom readiness check | A command returning the requested exit code |
76
+
77
+ Early-wake conditions can be combined with any of these. A timeout returns the partial results; it does not cancel the workers.
78
+
79
+ See the [request and result reference](https://github.com/ctxrs/superwait/blob/main/docs/reference.md) for examples, continuation behavior, and CLI usage.
80
+
81
+ ## When does it help most?
82
+
83
+ Use superwait when an agent keeps checking unchanged state, or when several workers and external conditions need to be coordinated together. A single native wait is often enough for one worker. superwait adds the compound conditions, early wakeups, and shared deadline.
84
+
85
+ It does not replace your host's built-in wait tools automatically. Your agent needs to use it, and the condition must be observable through a supported hook or check.
86
+
87
+ ## Works with ctx
88
+
89
+ superwait is made by **ctx engineering**, the team behind [ctx](https://github.com/ctxrs/ctx).
90
+
91
+ We recommend installing ctx alongside superwait. ctx gives your coding agents fast, local search across their past sessions, so they can recover earlier decisions, reuse investigations, and find solutions they've already worked out. Results link back to the original messages and tool calls.
92
+
93
+ [Install ctx and get started →](https://github.com/ctxrs/ctx#install-and-set-up-ctx)
94
+
95
+ ## About the numbers
96
+
97
+ Our corpus study found that wait-only model responses accounted for about **20% of input tokens**. Auditing repeated waits identified roughly half of that input as a consolidation opportunity—about **9–10% of total input**, before replacement overhead.
98
+
99
+ Actual token and spend savings depend on your corpus, model pricing, caching, and host integration; input-token savings are not the same as measured bill savings. [See the study and how to evaluate your own workload](https://github.com/ctxrs/superwait/blob/main/docs/savings.md).
100
+
101
+ ## Host support and details
102
+
103
+ Linux and Codex have live integration coverage. Claude Code and Cursor have adapter tests; full live workflows in those hosts, other operating systems, and multi-hour host waits still need qualification.
104
+
105
+ - [Host setup, permissions, and compatibility](https://github.com/ctxrs/superwait/blob/main/docs/hosts.md)
106
+ - [API, CLI, long waits, and local data](https://github.com/ctxrs/superwait/blob/main/docs/reference.md)
107
+ - [Contributing](https://github.com/ctxrs/superwait/blob/main/CONTRIBUTING.md) · [PyPI](https://pypi.org/project/superwait/) · [MIT license](https://github.com/ctxrs/superwait/blob/main/LICENSE)
@@ -1,7 +1,34 @@
1
1
  # Host integration
2
2
 
3
3
  All three hosts use the same `wait_for`, `list_agents`, and `signal` MCP API.
4
- The CLI runs the same wait engine. Setup writes only to the project you name.
4
+ The CLI runs the same wait engine.
5
+
6
+ ## Installation scope
7
+
8
+ **Host-wide setup is recommended:** run `superwait setup codex`, `superwait setup
9
+ claude`, or `superwait setup cursor` once. This installs into the selected host's
10
+ user configuration and applies across projects for that OS user. It does not
11
+ configure other users, remote machines, or cloud agent VMs.
12
+
13
+ Host-wide setup requires superwait 0.3.0 or newer. If you installed 0.2.0, run
14
+ `uv tool upgrade superwait` before following the [README](../README.md#install).
15
+
16
+ | Host | User MCP configuration | User lifecycle hooks | User skill |
17
+ | --- | --- | --- | --- |
18
+ | Codex | `~/.codex/config.toml` | `~/.codex/hooks.json` | `~/.agents/skills/superwait/SKILL.md` |
19
+ | Claude Code | `~/.claude.json` | `~/.claude/settings.json` | `~/.claude/skills/superwait/SKILL.md` |
20
+ | Cursor | `~/.cursor/mcp.json` | `~/.cursor/hooks.json` | `~/.cursor/skills/superwait/SKILL.md` |
21
+
22
+ Codex honors `CODEX_HOME` for its config and hooks; its user skill stays in
23
+ `~/.agents/skills`. Claude Code honors `CLAUDE_CONFIG_DIR`: when set, the MCP
24
+ entry goes into `.claude.json` inside that directory, with settings and skills
25
+ alongside it. Cursor uses its standard `~/.cursor` directory.
26
+
27
+ ### Project-only setup
28
+
29
+ Add `--project /path/to/repo` to limit setup to one repository. This explicitly
30
+ uses the repository paths below, even if a user configuration directory is set
31
+ in your environment.
5
32
 
6
33
  | Host | MCP configuration | Lifecycle hook configuration | Skill |
7
34
  | --- | --- | --- | --- |
@@ -9,6 +36,29 @@ The CLI runs the same wait engine. Setup writes only to the project you name.
9
36
  | Claude Code | `.mcp.json` | `.claude/settings.json` | `.claude/skills/superwait/SKILL.md` |
10
37
  | Cursor | `.cursor/mcp.json` | `.cursor/hooks.json` | `.cursor/skills/superwait/SKILL.md` |
11
38
 
39
+ Setup preserves unrelated settings, creates owner-only `.superwait-backup`
40
+ copies of existing files before changing them, and can be run again without
41
+ duplicating its hook entries in the same scope. It does not change host trust
42
+ or approval settings.
43
+
44
+ ### Moving an existing project installation to host-wide setup
45
+
46
+ Setup does not remove existing project installations. Hosts can load hooks from
47
+ both scopes, which would record events twice. Remove only the old superwait MCP
48
+ entry, its hook entries, and its installed skill from the project before using
49
+ the host-wide installation there. Keep unrelated settings and hooks. For Codex,
50
+ the managed MCP entry is between `# BEGIN superwait` and `# END superwait`.
51
+ Then restart the host and review its normal trust prompts.
52
+
53
+ Sources: [Codex config](https://learn.chatgpt.com/docs/config-file/config-reference),
54
+ [Codex hooks](https://developers.openai.com/codex/hooks),
55
+ [Codex skills](https://learn.chatgpt.com/docs/build-skills),
56
+ [Claude Code MCP scopes](https://code.claude.com/docs/en/mcp#user-scope),
57
+ [Claude Code settings](https://code.claude.com/docs/en/settings),
58
+ [Cursor MCP](https://cursor.com/docs/context/mcp),
59
+ [Cursor hooks](https://cursor.com/docs/hooks),
60
+ [Cursor skills](https://cursor.com/docs/context/skills).
61
+
12
62
  ## Codex
13
63
 
14
64
  `SubagentStart` and `SubagentStop` supply the agent ID and parent session ID.
@@ -26,6 +76,11 @@ no history. It is version-dependent; absent or unsupported metadata leaves
26
76
  UUID-based waiting and explicit signals available.
27
77
 
28
78
  A previously unknown task path remains pending until that mapping arrives.
79
+ If no worker in that parent is running, an unobserved path instead returns a
80
+ setup diagnostic after five seconds. Unobserved native IDs use the same grace
81
+ period; already observed workers can run for the full deadline.
82
+ An `any` or quorum wait can continue without an unobserved target when its other
83
+ targets can still satisfy the required count.
29
84
  Task paths require `session` even if only one old mapping exists: an unscoped
30
85
  name could otherwise match a previous conversation before the new worker stops.
31
86
  Setup installs a SessionStart hook that supplies this context to Codex; it
@@ -52,6 +107,24 @@ Sources: [hooks](https://developers.openai.com/codex/hooks),
52
107
  [MCP settings](https://learn.chatgpt.com/docs/extend/mcp),
53
108
  [skill discovery](https://learn.chatgpt.com/docs/build-skills).
54
109
 
110
+ ### Confirm the installation is active
111
+
112
+ Installing configuration does not approve its hooks. Open `/hooks`, review and
113
+ trust the three superwait definitions, then start a fresh session. Already-open
114
+ sessions may still use the previous configuration. Spawn a worker and check:
115
+
116
+ ```sh
117
+ superwait doctor codex
118
+ superwait agents codex
119
+ ```
120
+
121
+ Check that the new worker appears and its final report arrives. `doctor` shows
122
+ the package version, exact database, event count and latest event time; stored
123
+ old events alone do not prove the current host is recording. If observations
124
+ are missing, check hook trust, host reload, and matching database paths. Use
125
+ native agent waiting until this is fixed; file and command waits still work.
126
+ The CLI is also available when an existing session has not loaded the MCP tool.
127
+
55
128
  ## Claude Code
56
129
 
57
130
  The same lifecycle event names provide `agent_id` and `session_id`. Setup also
@@ -0,0 +1,39 @@
1
+ # Superwait reference
2
+
3
+ The [full API and CLI reference](../src/superwait/references/api.md) is also
4
+ installed beside the skill as `references/api.md`, so agents can read it locally
5
+ only when needed.
6
+
7
+ ## Install from source
8
+
9
+ To install the current source instead of the PyPI release:
10
+
11
+ ```sh
12
+ uv tool install 'git+https://github.com/ctxrs/superwait'
13
+ ```
14
+
15
+ ## Python and package installation
16
+
17
+ [uv](https://docs.astral.sh/uv/getting-started/installation/) is the recommended
18
+ installer. It creates a persistent, isolated tool environment and can download
19
+ Python automatically. You do not need to set up Python or a virtual environment
20
+ yourself. To explicitly select a runtime for the published package:
21
+
22
+ ```sh
23
+ uv tool install --python 3.12 superwait
24
+ ```
25
+
26
+ The [README](../README.md#install) covers host-wide setup, available in 0.3.0
27
+ and newer. Upgrade an existing uv installation with `uv tool upgrade superwait`.
28
+
29
+ If you already manage Python 3.11+ with pipx, `pipx install superwait` is another
30
+ option. Keep whichever tool environment you install: the generated MCP and hook
31
+ commands point at its Python executable. A temporary `uvx` run is not the
32
+ recommended setup path because its cached environment can be removed.
33
+
34
+ If `superwait` is not found after installation, follow uv's printed PATH
35
+ instructions or run `uv tool update-shell` and open a new terminal. Setup itself
36
+ does not edit shell profiles.
37
+
38
+ Sources: [uv tool environments](https://docs.astral.sh/uv/guides/tools/),
39
+ [automatic Python downloads](https://docs.astral.sh/uv/guides/install-python/#automatic-python-downloads).
@@ -0,0 +1,46 @@
1
+ # Where the numbers come from
2
+
3
+ [Back to the README](../README.md)
4
+
5
+ The banner is a rounded savings pitch based on a retrospective study of coding-agent history. The study measured how many tokens accompanied waiting responses and estimated which repeated responses could be consolidated. It did not measure a 10% reduction in bills from installing superwait.
6
+
7
+ ## What we measured
8
+
9
+ We froze a sample of 200 Codex sessions before analyzing their waiting behavior. After excluding one session with ambiguous turn and token attribution, the analysis retained 199 sessions and 24,897 attributed model responses.
10
+
11
+ A **wait-only response** calls only recognized waiting tools. Input counts include cached input; cached tokens are a subset, not an additional total.
12
+
13
+ | Finding | Result |
14
+ | --- | ---: |
15
+ | Input processed across the retained sample | 3.112 billion tokens |
16
+ | Input associated with wait-only responses | About 20% |
17
+ | Repeated-wait follow-ups eligible for investigation | 2,436 across 43 tasks |
18
+ | Probability-sampled transitions retained in the manual audit | 187 |
19
+ | Estimated input associated with skippable deterministic repeats | 281.6 million tokens, or 9.05% of total input |
20
+ | Including conditional cases that require preserving additional behavior | 317.9 million tokens, or 10.22% of total input |
21
+
22
+ The main estimate is about 46% of wait-only input; including the conditional cases reaches about 52%. That is the basis for the headline's “20%” and “half.”
23
+
24
+ The manual audit also included 20 supplemental cases for coverage. These did not enter the numerical projection. Separate reviewers checked overlapping cases and the accounting.
25
+
26
+ ## What that means for savings
27
+
28
+ Waiting time itself does not consume model tokens. The opportunity is avoiding another model response whose only useful action is to keep waiting.
29
+
30
+ The estimate is gross input-token opportunity, before the cost of instructions, monitor setup, result delivery, and changes to later context or cache use. Approximately 99.3% of the main projected input was cached. Cached input, fresh input, and output can have different prices, so a raw input percentage does not establish the same percentage reduction in spending.
31
+
32
+ The current package also cannot consume a host's opaque execution-cell or terminal-session handles directly. About 91.6% of the main input projection involved that integration requirement. The retrospective therefore does not establish that installing superwait delivers the full projected saving. Longer native waits can also eliminate some repeated checks.
33
+
34
+ The corpus deliberately oversampled parent tasks and covered Codex sessions from one workflow. It is not a representative benchmark for every developer, model, or supported host.
35
+
36
+ ## Evaluate your own workload
37
+
38
+ Use the same tasks, model, host configuration, and completion criteria for a baseline and a superwait run.
39
+
40
+ 1. **Start with a good baseline.** Use the host's supported native wait and timeout settings. Keep required progress updates and result delivery.
41
+ 2. **Use observable conditions.** Give superwait the worker, signal, file, HTTP, or command conditions that actually describe readiness.
42
+ 3. **Compare whole runs.** Record input, cached input, and output separately, including instructions and setup overhead. Also compare task completion, useful updates, failures, and elapsed time.
43
+ 4. **Apply your prices.** Compute each run's bill using its cached-input, uncached-input, and output rates. Divide the cost difference by the baseline cost to get spend savings.
44
+ 5. **Repeat across tasks.** Report how you selected the workload and which host integrations were available.
45
+
46
+ No corpus upload is required for this comparison. Use the usage records your host or provider makes available. superwait does not currently ship an automatic token-savings evaluator.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "superwait"
7
- version = "0.2.0"
7
+ version = "0.3.1"
8
8
  description = "Conditional waiting for coding agents"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: superwait
3
+ description: Prefer superwait for waiting on agents, files, services, or signals.
4
+ ---
5
+
6
+ # superwait
7
+
8
+ Prefer superwait for observable conditions. Use native tools to spawn or steer
9
+ agents, or when superwait reports unobserved workers. Don't retry missing hooks.
10
+
11
+ Call the `wait_for` MCP tool:
12
+
13
+ ```json
14
+ {"request":{"agents":["worker-a-id","worker-b-id"],"timeout":"10m"}}
15
+ ```
16
+
17
+ Use handles returned by the host. For Codex `/root/...` paths, add `session`
18
+ from startup context. The default waits for all agents. Read the `ready`
19
+ reports; use `continue_wait` as the next `request` for remaining work.
20
+ Choose the real task deadline, not repeated 30–60-second timeouts. An expired
21
+ continuation stays expired. Native process handles still need their native tools.
22
+
23
+ In Codex code mode, start `functions.exec` with
24
+ `// @exec: {"yield_time_ms":60000}`. If it yields, resume that cell with
25
+ `functions.wait` and `yield_time_ms: 60000`.
26
+ The wrapper can still wake the model; prefer a direct tool call when available.
27
+
28
+ For other conditions, quorum, early wakeups, continuation details, or CLI usage,
29
+ read the [local API reference](references/api.md) only when needed, including
30
+ the CLI fallback if the MCP tool is not exposed.
@@ -28,12 +28,15 @@ def parser():
28
28
  agents = commands.add_parser("agents", help="List recent hook-observed subagents.")
29
29
  agents.add_argument("provider", choices=["codex", "claude", "cursor"])
30
30
  agents.add_argument("--session")
31
+ doctor = commands.add_parser("doctor", help="Check whether lifecycle events are reaching this database.")
32
+ doctor.add_argument("provider", choices=["codex", "claude", "cursor"])
33
+ doctor.add_argument("--session", help="Check observations for one parent session.")
31
34
  hook = commands.add_parser("hook", help="Host lifecycle adapter; reads JSON on stdin.")
32
35
  hook.add_argument("provider", choices=["codex", "claude", "cursor"])
33
36
  commands.add_parser("serve", help="Serve the MCP API over stdio.")
34
- setup = commands.add_parser("setup", help="Configure hooks, MCP, and the skill in a project.")
37
+ setup = commands.add_parser("setup", help="Configure hooks, MCP, and the skill for your host.")
35
38
  setup.add_argument("provider", choices=["codex", "claude", "cursor"])
36
- setup.add_argument("--project", required=True, type=Path)
39
+ setup.add_argument("--project", type=Path, help="Only configure this project; default: all projects for your user.")
37
40
  setup.add_argument("--max-wait", default="24h", help="Host tool timeout, where supported.")
38
41
  prune = commands.add_parser("prune", help="Delete old local observations.")
39
42
  prune.add_argument("--days", type=float, default=30)
@@ -74,6 +77,10 @@ def main():
74
77
  result = {"seq": store.emit("signal", args.key, args.state, data=data)}
75
78
  elif args.command == "agents":
76
79
  result = {"agents": store.agents(args.provider, args.session), "limit": 50}
80
+ elif args.command == "doctor":
81
+ from importlib.metadata import version
82
+ result = {"version": version("superwait"), **store.health(args.provider, args.session)}
83
+ code = 0 if result["status"] == "observed" else 1
77
84
  elif args.command == "hook":
78
85
  from .hooks import record_hook
79
86
  result = record_hook(args.provider, json.load(sys.stdin), store)
@@ -15,6 +15,9 @@ from .models import Agent, Command, File, HTTP, Signal, WaitRequest
15
15
  from .store import AmbiguousAgent, Store
16
16
 
17
17
 
18
+ AGENT_OBSERVATION_GRACE = 5.0
19
+
20
+
18
21
  def file_stamp(path):
19
22
  try:
20
23
  stat = path.stat()
@@ -86,7 +89,13 @@ async def observe(target, store, client, baseline=None):
86
89
  else:
87
90
  event = store.signal(target.key, target.state, target.after)
88
91
  matched = bool(event)
89
- return {"matched": bool(matched), "state": event["state"] if event else "unknown", "event": event}
92
+ result = {"matched": bool(matched), "state": event["state"] if event else "unknown", "event": event}
93
+ if not event and isinstance(target, Agent) and target.provider == "codex" and target.id.startswith("/"):
94
+ # Codex task paths are learned at Stop, not Start. A live
95
+ # worker in this parent can still supply the mapping.
96
+ result["awaiting_alias"] = any(a["state"] == "running"
97
+ for a in store.agents("codex", target.session, None))
98
+ return result
90
99
  if isinstance(target, File):
91
100
  path = Path(target.path).expanduser().absolute()
92
101
  stamp = file_stamp(path)
@@ -143,8 +152,20 @@ async def wait_for(request: WaitRequest, store: Store, progress=None):
143
152
  while True:
144
153
  checks += 1
145
154
  results[i] = await observe(targets[i], store, client, baselines[i])
155
+ target, result = targets[i], results[i]
156
+ if isinstance(target, Agent) and result.get("state") == "unknown":
157
+ result["hint"] = ("No lifecycle observation for this handle. Check superwait doctor "
158
+ + target.provider + "; review hook trust and use native waiting until recording works.")
159
+ if time.monotonic() - started >= AGENT_OBSERVATION_GRACE:
160
+ if not result.get("awaiting_alias"):
161
+ result.update(state="unobserved", error=result["hint"])
146
162
  changed.set()
147
- await asyncio.sleep(request.interval)
163
+ delay = request.interval
164
+ if isinstance(target, Agent) and result.get("state") == "unknown":
165
+ remaining = AGENT_OBSERVATION_GRACE - (time.monotonic() - started)
166
+ if remaining > 0:
167
+ delay = min(delay, max(.05, remaining))
168
+ await asyncio.sleep(delay)
148
169
 
149
170
  async def keepalive():
150
171
  while True:
@@ -172,7 +193,7 @@ async def wait_for(request: WaitRequest, store: Store, progress=None):
172
193
  c["transcript_path"] = data["transcript_path"]
173
194
  elif isinstance(t, (Agent, Signal)) and e["seq"] <= t.after:
174
195
  c["state"] = "awaiting_new_event"
175
- for key in ("error", "status_code", "exit_code", "stdout", "stderr"):
196
+ for key in ("error", "hint", "status_code", "exit_code", "stdout", "stderr"):
176
197
  if key in r and r[key] != "":
177
198
  c[key] = r[key]
178
199
  return c
@@ -237,6 +258,9 @@ async def wait_for(request: WaitRequest, store: Store, progress=None):
237
258
  return outcome("interrupted")
238
259
  if sum(r["matched"] for r in results[:len(primary)]) >= needed:
239
260
  return outcome("matched")
261
+ observable = sum(r.get("state") != "unobserved" for r in results[:len(primary)])
262
+ if observable < needed or any(r.get("state") == "unobserved" for r in results[len(primary):]):
263
+ return outcome("error", "An agent has no lifecycle observations. Check its handle and hook setup, or use native waiting.")
240
264
  finally:
241
265
  for worker in workers:
242
266
  worker.cancel()
@@ -5,7 +5,7 @@ import re
5
5
  from datetime import datetime, timezone
6
6
  from typing import Annotated, Literal
7
7
 
8
- from pydantic import BaseModel, ConfigDict, Field, model_validator
8
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
9
9
 
10
10
 
11
11
  class Model(BaseModel):
@@ -67,10 +67,15 @@ Target = Annotated[Agent | Signal | File | HTTP | Command, Field(discriminator="
67
67
 
68
68
 
69
69
  def seconds(value: str) -> float:
70
- match = re.fullmatch(r"\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?\s*", value)
71
- if not match:
72
- raise ValueError("duration must be a number with ms, s, m, h, or d (for example 2h)")
73
- result = float(match[1]) * {None: 1, "ms": .001, "s": 1, "m": 60, "h": 3600, "d": 86400}[match[2]]
70
+ number = r"\d+(?:\.\d+)?"
71
+ if re.fullmatch(rf"\s*{number}\s*", value):
72
+ result = float(value)
73
+ else:
74
+ part = rf"({number})\s*(ms|s|m|h|d)"
75
+ if not re.fullmatch(rf"\s*(?:{part}\s*)+", value):
76
+ raise ValueError("duration must be seconds or use ms, s, m, h, d (for example 4m30s)")
77
+ result = sum(float(n) * {"ms": .001, "s": 1, "m": 60, "h": 3600, "d": 86400}[unit]
78
+ for n, unit in re.findall(part, value))
74
79
  if not math.isfinite(result) or result <= 0:
75
80
  raise ValueError("duration must be finite and positive")
76
81
  return result
@@ -86,10 +91,20 @@ class WaitRequest(Model):
86
91
  mode: Literal["any", "all", "quorum"] = "all"
87
92
  quorum: int | None = Field(default=None, gt=0)
88
93
  wake_on: list[Target] = Field(default_factory=list)
89
- interval: float = Field(default=1, ge=.05, allow_inf_nan=False)
94
+ interval: float | str = Field(default=1, description="Seconds between probes, as a number or duration such as 3s; minimum 0.05.")
90
95
  deadline: datetime | None = Field(default=None, description="Absolute deadline; survives a caller retry. Overrides timeout.")
91
96
  details: bool = Field(default=False, description="Include raw probe observations for troubleshooting.")
92
97
 
98
+ @field_validator("interval", mode="before")
99
+ @classmethod
100
+ def parse_interval(cls, value):
101
+ if not isinstance(value, (str, int, float)) or isinstance(value, bool):
102
+ raise ValueError("interval must be seconds or a duration string")
103
+ value = seconds(value) if isinstance(value, str) else float(value)
104
+ if not math.isfinite(value) or value < .05:
105
+ raise ValueError("interval must be finite and at least 0.05 seconds")
106
+ return value
107
+
93
108
  @property
94
109
  def conditions(self):
95
110
  provider = self.provider or "codex"