runsentry 0.1.0a1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RunSentry contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: runsentry
3
+ Version: 0.1.0a1
4
+ Summary: A lightweight local health observer for long-running commands and Python jobs.
5
+ Author: RunSentry contributors
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: MacOS
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: System :: Monitoring
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: psutil>=5.9
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest>=7; extra == "test"
24
+ Dynamic: license-file
25
+
26
+ # RunSentry
27
+
28
+ A lightweight local observer for long-running commands.
29
+
30
+ Wrap any command without modifying it:
31
+
32
+ ```bash
33
+ runsentry run --name training -- python train.py
34
+ ```
35
+
36
+ RunSentry records:
37
+
38
+ - process/resource activity;
39
+ - stdout/stderr activity;
40
+ - watched file/directory changes;
41
+ - conservative health state;
42
+ - local JSONL telemetry;
43
+ - final `summary.json`.
44
+
45
+ No daemon.
46
+ No cloud.
47
+ No automatic killing.
48
+
49
+ It is for developers, researchers, and local AI/agent users who want factual evidence
50
+ about commands that may stay active for minutes or hours.
51
+
52
+ ```bash
53
+ runsentry run \
54
+ --name demo \
55
+ --watch /tmp/runsentry-demo-output.txt \
56
+ -- python examples/demo_long_job.py
57
+ ```
58
+
59
+ ## Public alpha status
60
+
61
+ RunSentry is in conservative public alpha. The P0 scope is useful for local observation
62
+ and debugging, but it is not a production supervisor or a guaranteed stuck-process
63
+ detector.
64
+
65
+ ## Current P0 capabilities
66
+
67
+ RunSentry P0 can:
68
+
69
+ - launch a command directly with `shell=False`;
70
+ - preserve the child command argv after an explicit `--` boundary;
71
+ - tee child stdout and stderr back to the terminal as byte streams;
72
+ - count stdout/stderr bytes and chunks without storing output content;
73
+ - observe process/resource facts with `psutil`;
74
+ - observe watched file/directory size, mtime, file count, and disk usage facts;
75
+ - write local `.runsentry/runs/<run_id>/telemetry.jsonl`;
76
+ - write local `.runsentry/runs/<run_id>/summary.json`;
77
+ - emit conservative health states:
78
+ - `STARTING`
79
+ - `HEALTHY`
80
+ - `QUIET`
81
+ - `SUSPECTED_STALL`
82
+ - `FAILED`
83
+ - `COMPLETE`
84
+
85
+ `SUSPECTED_STALL` is a conservative suspicion, not proof. Silence alone is not a stall.
86
+ No watch path is not stall evidence. Unavailable metrics reduce confidence.
87
+
88
+ ## Install from source
89
+
90
+ RunSentry currently targets Python 3.10+ on macOS and Linux.
91
+
92
+ Current source installation:
93
+
94
+ ```bash
95
+ python -m pip install -e .
96
+ ```
97
+
98
+ For development tests:
99
+
100
+ ```bash
101
+ python -m pip install -e ".[test]"
102
+ python -m pytest -q
103
+ ```
104
+
105
+ Future PyPI installation is planned, but the package has not been published to PyPI yet.
106
+ Until that happens, source installation is required.
107
+
108
+ ## Basic usage
109
+
110
+ Always put the command to run after the explicit `--` boundary:
111
+
112
+ ```bash
113
+ runsentry run --name demo -- python3 -c "import time; time.sleep(2)"
114
+ ```
115
+
116
+ Watch an output file or directory:
117
+
118
+ ```bash
119
+ runsentry run --name io-demo --watch out.log -- python3 job.py
120
+ ```
121
+
122
+ Use more than one watch path:
123
+
124
+ ```bash
125
+ runsentry run --watch out.log --watch output_dir -- python3 job.py
126
+ ```
127
+
128
+ Everything after `--` belongs to the child command unchanged:
129
+
130
+ ```bash
131
+ runsentry run --name outer -- python3 job.py --watch child-value
132
+ ```
133
+
134
+ RunSentry does not invoke a shell. If you need shell syntax, launch the shell explicitly:
135
+
136
+ ```bash
137
+ runsentry run --name shell-demo -- bash -lc 'python3 job.py | tee output.log'
138
+ ```
139
+
140
+ In that case, the shell is the observed root process and shell quoting rules are your
141
+ responsibility.
142
+
143
+ ## Output artifacts
144
+
145
+ By default, each run writes:
146
+
147
+ ```text
148
+ .runsentry/
149
+ runs/
150
+ <run_id>/
151
+ telemetry.jsonl
152
+ summary.json
153
+ ```
154
+
155
+ Use `--output-dir` to place artifacts elsewhere:
156
+
157
+ ```bash
158
+ runsentry run --output-dir /tmp/runsentry-demo -- python3 job.py
159
+ ```
160
+
161
+ Telemetry is local. RunSentry does not upload data or contact a service.
162
+
163
+ RunSentry stores stdout/stderr activity counters, not stdout/stderr content. Command argv
164
+ is stored because it is part of the factual launch record, so avoid putting secrets in
165
+ command arguments.
166
+
167
+ Generated `.runsentry/` artifacts are ignored by this repository and should normally
168
+ stay out of commits.
169
+
170
+ ## Health states
171
+
172
+ - `STARTING`: the run is inside the initial observation window.
173
+ - `HEALTHY`: recent positive activity was observed.
174
+ - `QUIET`: little activity is visible, but evidence is insufficient to suspect a stall.
175
+ - `SUSPECTED_STALL`: multiple independent available signals show sustained inactivity.
176
+ - `FAILED`: the wrapped root command failed to launch or exited nonzero.
177
+ - `COMPLETE`: the wrapped root command completed successfully under current P0 semantics.
178
+
179
+ Only `FAILED` and `COMPLETE` are ordinary terminal success/failure states. `SUSPECTED_STALL`
180
+ can recover if activity resumes.
181
+
182
+ ## P0 limitations
183
+
184
+ RunSentry P0 is intentionally small:
185
+
186
+ - local machine only;
187
+ - macOS and Linux target;
188
+ - no Windows compatibility promise yet;
189
+ - no web dashboard;
190
+ - no SaaS or remote monitoring;
191
+ - no notifications;
192
+ - no database;
193
+ - no workflow orchestration;
194
+ - no AI/LLM health judgment;
195
+ - no automatic kill or recovery;
196
+ - no OOM prediction;
197
+ - no disk exhaustion prediction;
198
+ - no generic job-completion ETA.
199
+
200
+ Process/resource visibility may be partial due to OS permissions. The health logic is
201
+ conservative and may miss real stalls rather than creating aggressive false positives.
202
+
203
+ ## More detail
204
+
205
+ See [docs/public-alpha.md](docs/public-alpha.md) for the public-alpha readiness notes.
206
+
207
+ Historical design and implementation records live in `docs/RS-P0-*.md`.
208
+
209
+ ## Reporting bugs
210
+
211
+ Use the GitHub issue templates. For behavior bugs, include:
212
+
213
+ - OS and Python version;
214
+ - the `runsentry run ...` command, with secrets removed;
215
+ - expected versus actual behavior;
216
+ - a sanitized `summary.json` excerpt if useful.
217
+
218
+ Do not share command arguments or paths that contain secrets.
219
+
220
+ ## Feedback wanted
221
+
222
+ If you try RunSentry on a real local command, feedback on install friction, health-state
223
+ accuracy, `summary.json` usefulness, and missing P0 facts is especially useful.
224
+
225
+ ## License
226
+
227
+ MIT.
@@ -0,0 +1,202 @@
1
+ # RunSentry
2
+
3
+ A lightweight local observer for long-running commands.
4
+
5
+ Wrap any command without modifying it:
6
+
7
+ ```bash
8
+ runsentry run --name training -- python train.py
9
+ ```
10
+
11
+ RunSentry records:
12
+
13
+ - process/resource activity;
14
+ - stdout/stderr activity;
15
+ - watched file/directory changes;
16
+ - conservative health state;
17
+ - local JSONL telemetry;
18
+ - final `summary.json`.
19
+
20
+ No daemon.
21
+ No cloud.
22
+ No automatic killing.
23
+
24
+ It is for developers, researchers, and local AI/agent users who want factual evidence
25
+ about commands that may stay active for minutes or hours.
26
+
27
+ ```bash
28
+ runsentry run \
29
+ --name demo \
30
+ --watch /tmp/runsentry-demo-output.txt \
31
+ -- python examples/demo_long_job.py
32
+ ```
33
+
34
+ ## Public alpha status
35
+
36
+ RunSentry is in conservative public alpha. The P0 scope is useful for local observation
37
+ and debugging, but it is not a production supervisor or a guaranteed stuck-process
38
+ detector.
39
+
40
+ ## Current P0 capabilities
41
+
42
+ RunSentry P0 can:
43
+
44
+ - launch a command directly with `shell=False`;
45
+ - preserve the child command argv after an explicit `--` boundary;
46
+ - tee child stdout and stderr back to the terminal as byte streams;
47
+ - count stdout/stderr bytes and chunks without storing output content;
48
+ - observe process/resource facts with `psutil`;
49
+ - observe watched file/directory size, mtime, file count, and disk usage facts;
50
+ - write local `.runsentry/runs/<run_id>/telemetry.jsonl`;
51
+ - write local `.runsentry/runs/<run_id>/summary.json`;
52
+ - emit conservative health states:
53
+ - `STARTING`
54
+ - `HEALTHY`
55
+ - `QUIET`
56
+ - `SUSPECTED_STALL`
57
+ - `FAILED`
58
+ - `COMPLETE`
59
+
60
+ `SUSPECTED_STALL` is a conservative suspicion, not proof. Silence alone is not a stall.
61
+ No watch path is not stall evidence. Unavailable metrics reduce confidence.
62
+
63
+ ## Install from source
64
+
65
+ RunSentry currently targets Python 3.10+ on macOS and Linux.
66
+
67
+ Current source installation:
68
+
69
+ ```bash
70
+ python -m pip install -e .
71
+ ```
72
+
73
+ For development tests:
74
+
75
+ ```bash
76
+ python -m pip install -e ".[test]"
77
+ python -m pytest -q
78
+ ```
79
+
80
+ Future PyPI installation is planned, but the package has not been published to PyPI yet.
81
+ Until that happens, source installation is required.
82
+
83
+ ## Basic usage
84
+
85
+ Always put the command to run after the explicit `--` boundary:
86
+
87
+ ```bash
88
+ runsentry run --name demo -- python3 -c "import time; time.sleep(2)"
89
+ ```
90
+
91
+ Watch an output file or directory:
92
+
93
+ ```bash
94
+ runsentry run --name io-demo --watch out.log -- python3 job.py
95
+ ```
96
+
97
+ Use more than one watch path:
98
+
99
+ ```bash
100
+ runsentry run --watch out.log --watch output_dir -- python3 job.py
101
+ ```
102
+
103
+ Everything after `--` belongs to the child command unchanged:
104
+
105
+ ```bash
106
+ runsentry run --name outer -- python3 job.py --watch child-value
107
+ ```
108
+
109
+ RunSentry does not invoke a shell. If you need shell syntax, launch the shell explicitly:
110
+
111
+ ```bash
112
+ runsentry run --name shell-demo -- bash -lc 'python3 job.py | tee output.log'
113
+ ```
114
+
115
+ In that case, the shell is the observed root process and shell quoting rules are your
116
+ responsibility.
117
+
118
+ ## Output artifacts
119
+
120
+ By default, each run writes:
121
+
122
+ ```text
123
+ .runsentry/
124
+ runs/
125
+ <run_id>/
126
+ telemetry.jsonl
127
+ summary.json
128
+ ```
129
+
130
+ Use `--output-dir` to place artifacts elsewhere:
131
+
132
+ ```bash
133
+ runsentry run --output-dir /tmp/runsentry-demo -- python3 job.py
134
+ ```
135
+
136
+ Telemetry is local. RunSentry does not upload data or contact a service.
137
+
138
+ RunSentry stores stdout/stderr activity counters, not stdout/stderr content. Command argv
139
+ is stored because it is part of the factual launch record, so avoid putting secrets in
140
+ command arguments.
141
+
142
+ Generated `.runsentry/` artifacts are ignored by this repository and should normally
143
+ stay out of commits.
144
+
145
+ ## Health states
146
+
147
+ - `STARTING`: the run is inside the initial observation window.
148
+ - `HEALTHY`: recent positive activity was observed.
149
+ - `QUIET`: little activity is visible, but evidence is insufficient to suspect a stall.
150
+ - `SUSPECTED_STALL`: multiple independent available signals show sustained inactivity.
151
+ - `FAILED`: the wrapped root command failed to launch or exited nonzero.
152
+ - `COMPLETE`: the wrapped root command completed successfully under current P0 semantics.
153
+
154
+ Only `FAILED` and `COMPLETE` are ordinary terminal success/failure states. `SUSPECTED_STALL`
155
+ can recover if activity resumes.
156
+
157
+ ## P0 limitations
158
+
159
+ RunSentry P0 is intentionally small:
160
+
161
+ - local machine only;
162
+ - macOS and Linux target;
163
+ - no Windows compatibility promise yet;
164
+ - no web dashboard;
165
+ - no SaaS or remote monitoring;
166
+ - no notifications;
167
+ - no database;
168
+ - no workflow orchestration;
169
+ - no AI/LLM health judgment;
170
+ - no automatic kill or recovery;
171
+ - no OOM prediction;
172
+ - no disk exhaustion prediction;
173
+ - no generic job-completion ETA.
174
+
175
+ Process/resource visibility may be partial due to OS permissions. The health logic is
176
+ conservative and may miss real stalls rather than creating aggressive false positives.
177
+
178
+ ## More detail
179
+
180
+ See [docs/public-alpha.md](docs/public-alpha.md) for the public-alpha readiness notes.
181
+
182
+ Historical design and implementation records live in `docs/RS-P0-*.md`.
183
+
184
+ ## Reporting bugs
185
+
186
+ Use the GitHub issue templates. For behavior bugs, include:
187
+
188
+ - OS and Python version;
189
+ - the `runsentry run ...` command, with secrets removed;
190
+ - expected versus actual behavior;
191
+ - a sanitized `summary.json` excerpt if useful.
192
+
193
+ Do not share command arguments or paths that contain secrets.
194
+
195
+ ## Feedback wanted
196
+
197
+ If you try RunSentry on a real local command, feedback on install friction, health-state
198
+ accuracy, `summary.json` usefulness, and missing P0 facts is especially useful.
199
+
200
+ ## License
201
+
202
+ MIT.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "runsentry"
7
+ version = "0.1.0a1"
8
+ description = "A lightweight local health observer for long-running commands and Python jobs."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ {name = "RunSentry contributors"}
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: MacOS",
20
+ "Operating System :: POSIX :: Linux",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: System :: Monitoring",
26
+ "Topic :: Utilities"
27
+ ]
28
+ dependencies = [
29
+ "psutil>=5.9"
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ test = [
34
+ "pytest>=7"
35
+ ]
36
+
37
+ [project.scripts]
38
+ runsentry = "runsentry.cli:main"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .version import __version__
2
+
3
+ __all__ = ["__version__"]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import sys
5
+ from collections.abc import Sequence
6
+
7
+ from .execution import EXIT_USAGE, LaunchError, RunSpec, run_command
8
+ from .observation import DEFAULT_RESOURCE_SAMPLE_INTERVAL_S
9
+ from .version import __version__
10
+
11
+
12
+ def build_parser() -> argparse.ArgumentParser:
13
+ parser = argparse.ArgumentParser(
14
+ prog="runsentry",
15
+ description="Local conservative health observer for one long-running command.",
16
+ )
17
+ parser.add_argument(
18
+ "--version",
19
+ action="version",
20
+ version=f"%(prog)s {__version__}",
21
+ )
22
+
23
+ subparsers = parser.add_subparsers(dest="command_name")
24
+ run_parser = subparsers.add_parser(
25
+ "run",
26
+ help="observe a command after an explicit -- boundary",
27
+ description=(
28
+ "Launch COMMAND directly with shell=False, tee stdout/stderr, "
29
+ "observe local facts, and write .runsentry telemetry."
30
+ ),
31
+ )
32
+ run_parser.add_argument("--name", default=None, help="optional human-readable run name")
33
+ run_parser.add_argument(
34
+ "--interval",
35
+ type=float,
36
+ default=DEFAULT_RESOURCE_SAMPLE_INTERVAL_S,
37
+ help="resource/watch/telemetry sampling interval in seconds",
38
+ )
39
+ run_parser.add_argument(
40
+ "--watch",
41
+ action="append",
42
+ default=[],
43
+ help="path to observe for factual size/mtime/disk data",
44
+ )
45
+ run_parser.add_argument(
46
+ "--output-dir",
47
+ default=None,
48
+ help="directory for RunSentry telemetry artifacts",
49
+ )
50
+ run_parser.add_argument(
51
+ "run_args",
52
+ nargs=argparse.REMAINDER,
53
+ help="must contain -- followed by COMMAND [ARG ...]; all arguments after -- belong to the child",
54
+ )
55
+ run_parser.set_defaults(func=_run)
56
+ return parser
57
+
58
+
59
+ def _parse_run_args(
60
+ name: str | None,
61
+ interval: float,
62
+ watches: Sequence[str],
63
+ output_dir: str | None,
64
+ run_args: Sequence[str],
65
+ ) -> RunSpec:
66
+ if interval < 1.0 or interval > 60.0:
67
+ raise LaunchError("runsentry run --interval must be between 1.0 and 60.0.", EXIT_USAGE)
68
+
69
+ if "--" not in run_args:
70
+ raise LaunchError(
71
+ "runsentry run requires an explicit -- before the command.",
72
+ EXIT_USAGE,
73
+ )
74
+
75
+ boundary_index = list(run_args).index("--")
76
+ option_args = list(run_args[:boundary_index])
77
+ command_argv = list(run_args[boundary_index + 1 :])
78
+ if not command_argv:
79
+ raise LaunchError("runsentry run requires a command after --.", EXIT_USAGE)
80
+
81
+ watch_paths = list(watches) + _parse_watch_options(option_args)
82
+
83
+ return RunSpec(
84
+ name=name,
85
+ argv=command_argv,
86
+ sample_interval_s=interval,
87
+ watch_paths=watch_paths,
88
+ output_dir=output_dir,
89
+ )
90
+
91
+
92
+ def _parse_watch_options(option_args: list[str]) -> list[str]:
93
+ watch_paths: list[str] = []
94
+ index = 0
95
+ while index < len(option_args):
96
+ arg = option_args[index]
97
+ if arg != "--watch":
98
+ raise LaunchError(f"unexpected argument before --: {arg}", EXIT_USAGE)
99
+ index += 1
100
+ if index >= len(option_args):
101
+ raise LaunchError("runsentry run --watch requires a path.", EXIT_USAGE)
102
+ watch_paths.append(option_args[index])
103
+ index += 1
104
+ return watch_paths
105
+
106
+
107
+ def _run(args: argparse.Namespace) -> int:
108
+ try:
109
+ run_spec = _parse_run_args(
110
+ args.name,
111
+ args.interval,
112
+ args.watch,
113
+ args.output_dir,
114
+ args.run_args,
115
+ )
116
+ return run_command(run_spec)
117
+ except LaunchError as exc:
118
+ print(f"runsentry: {exc}", file=sys.stderr)
119
+ return exc.exit_code
120
+
121
+
122
+ def main(argv: Sequence[str] | None = None) -> int:
123
+ parser = build_parser()
124
+ args = parser.parse_args(argv)
125
+ if not hasattr(args, "func"):
126
+ parser.print_help()
127
+ return 0
128
+ return int(args.func(args))