agent-safe-runner
agent-safe-runner is a small, local-first queue for automation commands proposed by AI agents. It stores jobs in SQLite, requires a separate approval decision and an explicit command policy before execution, and writes a redacted JSONL audit trail.
The project is intentionally narrow: it helps a local operator control which commands may run, when they may run, and what evidence is retained afterward.
[!WARNING] This project is an execution gate, not an operating-system sandbox. Run workers with a low-privilege account and use containers or OS isolation for untrusted code.
Why it exists
Agent workflows often grow from scripts into unattended queues. At that point, a plain subprocess.run() leaves important questions unanswered:
- Was the command explicitly allowed?
- Who reviewed it before execution?
- Could two workers run the same job?
- Did a retry happen, and why?
- Did logs accidentally store a token?
- Can the operator verify the event history?
This runner makes those controls explicit without adding a service, broker, or cloud dependency.
Features
- Deny-by-default JSON policy with command-prefix and working-directory rules
- Approval inbox, read-only assessment, and explicit approve/deny decisions
- Dry run by default; real execution requires approval, policy allowance, and
--execute - SQLite queue with idempotency keys and fail-closed schema migration from
0.1.x/0.2.x - Atomic leases, expired-lease recovery, bounded retries, and exponential backoff
- Job cancellation, manual retry, status filtering, and one-pass worker mode
- Secret-like argument rejection before persistence
- Minimal child-process environment and redacted output capture
- Append-only JSONL audit events with a verifiable SHA-256 hash chain
- Structured JSON output and errors for scripting
- Optional stdio MCP adapter with proposal/read-only tools; no approval or execution tools
- Standard-library runtime with no required third-party dependencies
Install
Python 3.11 or newer is required.
Install the latest version directly from GitHub:
python -m pip install "https://github.com/umefor-labs/agent-safe-runner/archive/refs/heads/main.zip"
Confirm that the command is available:
agent-safe --version
If your system does not expose the agent-safe command after installation,
use python -m agent_safe_runner in its place.
Short installation command (after PyPI publication)
Once a release is listed on PyPI, users can install it in isolation with:
pipx install agent-safe-runner
Until the first PyPI upload is verified, use the GitHub installation above. A GitHub push alone does not publish to PyPI; maintainers can follow the publishing checklist.
For AI-agent integrations, see the optional MCP adapter.
For an isolated installation with pipx, see the
getting-started guide. Contributors should use the
development setup.
Quick start
Create a dedicated workspace so the queue, policy, and audit files stay together:
mkdir agent-safe-workspace
cd agent-safe-workspace
agent-safe init-policy
The generated agent-safe-policy.json contains a few example rules. Review it
before use: python --version prints a version, but pytest executes project
code and is appropriate only in a trusted workspace.
Queue a command that prints the installed Python version:
agent-safe submit --cwd . --timeout 30 -- python --version
The command returns a JSON object. Copy its id, then inspect the job and perform
a dry run. Replace JOB_ID below with that value:
agent-safe show JOB_ID
agent-safe assess JOB_ID
agent-safe run JOB_ID
assess returns allowed, reason, and matched_rule. An allowed job is still
pending approval. run without --execute remains a dry run.
After reviewing the exact command, directory, timeout, and retry limit, record
your decision. Replace local-operator with a label meaningful to you:
agent-safe approve JOB_ID --by local-operator --reason "Reviewed version check"
agent-safe run JOB_ID --execute --worker local-1
--by is an audit label, not authentication. Anyone with access to the
approval CLI or writable database can approve jobs; this is a workflow gate.
The final JSON should report "status": "succeeded". Verify the audit chain's
integrity:
agent-safe audit-verify
See Getting started for installation isolation, upgrades, troubleshooting, and a complete first-run walkthrough.
Common commands
agent-safe list
agent-safe inbox
agent-safe list --approval pending
agent-safe list --status queued --status retry_wait
agent-safe show JOB_ID
agent-safe assess JOB_ID
agent-safe deny JOB_ID --by local-operator --reason "Not needed"
agent-safe cancel JOB_ID
agent-safe retry JOB_ID
agent-safe work --once --execute --worker local-1
agent-safe audit-verify
Global paths must appear before the subcommand:
agent-safe --db /path/to/jobs.sqlite3 --audit /path/to/audit.jsonl --policy /path/to/policy.json list
Everything after -- in submit is stored as an argument vector and is never
passed through a shell parser. All commands emit JSON. Expected input, state, and
policy errors return exit code 2 with a stable error code.
work --once --execute picks only approved jobs. Manual retry clears the old
decision and requires fresh approval; automatic retries keep the existing
approval for the unchanged job. deny cancels a pending job. To stop an already
approved queued job, use cancel.
Upgrade and uninstall
Upgrading from 0.2.x or older? Stop all workers and back up your local state before installing. Old queued jobs become pending and will not run until reviewed. See the 0.3 migration guide.
Upgrade to the latest GitHub version:
python -m pip install --upgrade "https://github.com/umefor-labs/agent-safe-runner/archive/refs/heads/main.zip"
Remove the command-line application:
python -m pip uninstall agent-safe-runner
Uninstalling does not delete your queue, policy, or audit files.
Policy
Execution is denied when the policy file is absent. A policy contains:
{
"version": 1,
"allowed_commands": [
{"program": "python", "args_prefix": ["--version"]},
{"program": "python", "args_prefix": ["-m", "pytest"]},
{"program": "git", "args_prefix": ["status"]}
],
"allowed_working_roots": ["."],
"denied_arguments": ["--force", "--hard"],
"environment_allowlist": ["PATH", "PATHEXT", "SYSTEMROOT", "WINDIR", "TEMP", "TMP"],
"max_timeout_seconds": 300,
"max_output_chars": 8000
}
Rules compare resolved executable paths and the case-sensitive beginning of the argument list. Missing executables do not match. An empty args_prefix allows every argument for that executable and should be used cautiously.
Never place credentials in a command. The runner rejects common secret flags and token formats, but detection cannot identify every secret. Use a dedicated secret provider and grant the worker only the environment variables it needs.
Job states
queued -> running -> succeeded
-> retry_wait -> running
-> failed
queued/retry_wait -> cancelled -> queued (manual retry)
queued/retry_wait -> dead_letter (policy denial)
Approval is separate from execution status: pending, approved, denied, or
legacy for historical records. New jobs start queued + pending.
Policy-invalid approved jobs become dead_letter without spawning a process.
Nonzero exits, timeouts, and process-start failures retry up to max_attempts.
Data files
agent-safe.sqlite3: queue state, commands, and redacted resultsaudit.jsonl: redacted event records and hash-chain metadataagent-safe-policy.json: local execution policy
These runtime files are ignored by Git. SQLite commands are stored in plain text, so do not submit secrets or place the database in a public or broadly synchronized directory.
Current limits
- The audit chain detects accidental edits; it is not a cryptographic signature and an attacker with write access can rebuild it.
- Audit appends use advisory file locking on Windows and POSIX; filesystems that ignore advisory locks are unsupported for multi-process writers.
- Running jobs cannot currently be interrupted by
cancel; cancellation applies to queued and retry-wait jobs. - Approval records are not signatures or user authentication. This gate cannot constrain an agent that already has unrestricted terminal or file access.
- SQLite state and JSONL audit are separate stores, not a single crash-atomic transaction. See Architecture.
- Output limits bound stored text, not peak capture memory; lease recovery is at-least-once, not an exactly-once guarantee for external side effects.
- MCP is local stdio only. There is no remote API, scheduler daemon, plugin system, or secret-provider integration yet.
See Architecture, Threat model, Roadmap, Contributing, and Security policy.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agent_safe_runner-0.4.0.tar.gz.
File metadata
- Download URL: agent_safe_runner-0.4.0.tar.gz
- Upload date:
- Size: 41.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22e9605dbc666040f733287ba2dbf474a5f797ae6c38446a4d9b05e4635f5afc
|
|
| MD5 |
d1a719111fbef1d991eb531bf8fd79fc
|
|
| BLAKE2b-256 |
15dda24d325b53146f7ad13c508b15bf2357bc14e17ff2d634121f7ec106f897
|
Provenance
The following attestation bundles were made for agent_safe_runner-0.4.0.tar.gz:
Publisher:
publish.yml on umefor-labs/agent-safe-runner
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_safe_runner-0.4.0.tar.gz -
Subject digest:
22e9605dbc666040f733287ba2dbf474a5f797ae6c38446a4d9b05e4635f5afc - Sigstore transparency entry: 2700102782
- Sigstore integration time:
-
Permalink:
umefor-labs/agent-safe-runner@852f5d83d5d172f0272375f2291bee3dfe692548 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/umefor-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@852f5d83d5d172f0272375f2291bee3dfe692548 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file agent_safe_runner-0.4.0-py3-none-any.whl.
File metadata
- Download URL: agent_safe_runner-0.4.0-py3-none-any.whl
- Upload date:
- Size: 23.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85169075c7a8b2b2c22e85563130635cb93d548d9fcbd3660d2e14df3cc88d54
|
|
| MD5 |
d1ec04dc3a4101afb7c98dc156cad54f
|
|
| BLAKE2b-256 |
6836f303c0f046c8407fbed5d62863041e6aed04d073926f37fb1406b981ea85
|
Provenance
The following attestation bundles were made for agent_safe_runner-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on umefor-labs/agent-safe-runner
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_safe_runner-0.4.0-py3-none-any.whl -
Subject digest:
85169075c7a8b2b2c22e85563130635cb93d548d9fcbd3660d2e14df3cc88d54 - Sigstore transparency entry: 2700102880
- Sigstore integration time:
-
Permalink:
umefor-labs/agent-safe-runner@852f5d83d5d172f0272375f2291bee3dfe692548 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/umefor-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@852f5d83d5d172f0272375f2291bee3dfe692548 -
Trigger Event:
workflow_dispatch
-
Statement type: