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.
- superwait-0.3.1/PKG-INFO +121 -0
- superwait-0.3.1/README.md +107 -0
- superwait-0.3.1/docs/assets/superwait-banner.png +0 -0
- {superwait-0.2.0 → superwait-0.3.1}/docs/hosts.md +74 -1
- superwait-0.3.1/docs/reference.md +39 -0
- superwait-0.3.1/docs/savings.md +46 -0
- {superwait-0.2.0 → superwait-0.3.1}/pyproject.toml +1 -1
- superwait-0.3.1/src/superwait/SKILL.md +30 -0
- {superwait-0.2.0 → superwait-0.3.1}/src/superwait/cli.py +9 -2
- {superwait-0.2.0 → superwait-0.3.1}/src/superwait/engine.py +27 -3
- {superwait-0.2.0 → superwait-0.3.1}/src/superwait/models.py +21 -6
- superwait-0.3.1/src/superwait/references/api.md +258 -0
- {superwait-0.2.0 → superwait-0.3.1}/src/superwait/server.py +5 -3
- {superwait-0.2.0 → superwait-0.3.1}/src/superwait/setup.py +29 -12
- {superwait-0.2.0 → superwait-0.3.1}/src/superwait/store.py +13 -0
- superwait-0.3.1/tests/test_hooks_setup.py +230 -0
- superwait-0.3.1/tests/test_observations.py +123 -0
- {superwait-0.2.0 → superwait-0.3.1}/tests/test_transport.py +14 -0
- {superwait-0.2.0 → superwait-0.3.1}/uv.lock +1 -1
- superwait-0.2.0/PKG-INFO +0 -146
- superwait-0.2.0/README.md +0 -132
- superwait-0.2.0/src/superwait/SKILL.md +0 -95
- superwait-0.2.0/tests/test_hooks_setup.py +0 -91
- {superwait-0.2.0 → superwait-0.3.1}/.gitignore +0 -0
- {superwait-0.2.0 → superwait-0.3.1}/CONTRIBUTING.md +0 -0
- {superwait-0.2.0 → superwait-0.3.1}/LICENSE +0 -0
- {superwait-0.2.0 → superwait-0.3.1}/src/superwait/__init__.py +0 -0
- {superwait-0.2.0 → superwait-0.3.1}/src/superwait/__main__.py +0 -0
- {superwait-0.2.0 → superwait-0.3.1}/src/superwait/hooks.py +0 -0
- {superwait-0.2.0 → superwait-0.3.1}/tests/test_ergonomics.py +0 -0
- {superwait-0.2.0 → superwait-0.3.1}/tests/test_wait.py +0 -0
superwait-0.3.1/PKG-INFO
ADDED
|
@@ -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)
|
|
Binary file
|
|
@@ -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.
|
|
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.
|
|
@@ -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
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
|
|
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,
|
|
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"
|