A lightweight agent runtime that turns any machine into an AI-agent-ready compute target. Zero dependencies. Self-updating. Works with any ACP-compatible coding agent.
agent-runtime is a sidecar you install on any machine (Dev Box, VM, cloud instance) to make it remotely controllable by AI coding agents. It provides:
- Async command server —
POST /exec→ job ID,GET /jobs/{id}→ poll for results - Persistent PowerShell-family sessions — named
pwsh/ Windows PowerShell workflow shells with session health + delete APIs - WebSocket terminal — interactive ConPTY sessions over WebSocket (xterm.js compatible)
- ACP client — structured Agent Client Protocol communication with any ACP agent
- Self-updating —
POST /updatetriggers graceful upgrade via pip - Devtunnel integration — persistent tunnels, token rotation, challenge-code registration
- API key auth — auto-generated keys, persisted across restarts
# Core (zero dependencies)
pip install devpilot-agent
# With interactive terminal support (Windows)
pip install devpilot-agent[terminal]# Start the server
agent-server
# Start on a custom port
agent-server --port 9090
# Start with devtunnel + auto-registration
agent-server --wrapper --tunnel --register https://your-dashboard.example.com
# Start with self-update wrapper
agent-server --wrapperThe package also includes a small stdlib-only HTTP client for callers that want to talk to the runtime API directly from Python:
from agent_runtime.client import RuntimeClient
client = RuntimeClient("https://your-runtime.example.com", api_key="secret")
job = client.submit("echo hello", workdir=r"C:\repo")
health = client.health_check()RuntimeClient covers the same async job, ACP, health, persistent-session, and
update endpoints that DevPilot uses.
GET /healthworks without an API key, but unauthenticated callers only get a minimal response:{"status":"ok","authenticated":false}- All other endpoints require
X-API-Key. - With a valid API key,
GET /healthreturns the full server capabilities payload.
Submit a shell, persistent-shell, or ACP job. Successful requests return 202 Accepted with:
{"jobId":"abc123","status":"pending"}Common request fields:
| Field | Type | Required | Notes |
|---|---|---|---|
mode |
string | no | "shell" by default; set to "persistent_shell" or "acp" for the other execution paths |
workdir |
string | no | Working directory for the child process |
timeout |
integer | no | Timeout in seconds; defaults to 300 |
env |
object | no | Optional environment overlay. Keys and values must both be strings or the request fails with 400. |
Shell mode runs the command in a background subprocess.
| Field | Type | Required | Notes |
|---|---|---|---|
command |
string | yes | Shell command to execute |
Example:
curl -X POST http://localhost:8585/exec \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"command": "python -c \"import os; print(os.environ.get(\\\"DEVPILOT_TEST_ENV\\\", \\\"\\\"))\"",
"workdir": "C:/src/repo",
"timeout": 300,
"env": {
"DEVPILOT_TEST_ENV": "hello-from-env"
}
}'The env overlay is merged onto the runtime process environment before the shell subprocess starts.
Persistent shell mode creates or reuses a named PowerShell-family session keyed by sessionKey. Within the same named session, shell-local state such as functions, aliases, imported modules, current location, and in-shell environment mutations persists across commands.
Important behavior:
envandworkdirare treated as session seed state. They apply when the named session is first created, and they are replayed only if the underlying shell process must be restarted.- If a persistent-shell command times out, or an ACP terminal kill resets the shared shell process, the runtime recreates the process on the next command and replays the remembered seed state.
- Optional
bootstrapCommandlets callers register an init script body that should be replayed after such resets. UsebootstrapCurrentCommand: truewhen the current request is itself the bootstrap command.
| Field | Type | Required | Notes |
|---|---|---|---|
command |
string | yes | PowerShell command/script to execute in the named session |
sessionKey |
string | yes | Stable session identifier |
shell |
string | yes | pwsh, pwsh.exe, powershell, powershell.exe, or windows-powershell |
bootstrapCommand |
string | no | Script body to remember and replay after process resets |
bootstrapCurrentCommand |
boolean | no | Set true when command already executed the bootstrap script |
Example:
# Seed a reusable workflow shell and register the bootstrap script
curl -X POST http://localhost:8585/exec \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "persistent_shell",
"sessionKey": "workflow-123",
"shell": "pwsh",
"command": ". C:/src/repo/init.ps1",
"bootstrapCommand": ". C:/src/repo/init.ps1",
"bootstrapCurrentCommand": true,
"workdir": "C:/src/repo",
"env": {
"DOTNET_ROOT": "C:/Users/alice/AppData/Local/Microsoft/dotnet"
}
}'
# Reuse the same session later — functions/modules/cwd state persist
curl -X POST http://localhost:8585/exec \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "persistent_shell",
"sessionKey": "workflow-123",
"shell": "pwsh",
"command": "Get-Location; Get-WorkflowValue"
}'ACP mode launches an ACP-compatible agent and returns structured execution results.
| Field | Type | Required | Notes |
|---|---|---|---|
agent |
string | no | ACP launcher command; defaults to copilot --acp --stdio |
prompt |
string | no | Prompt text to send after session creation/load |
acp_session_id |
string | no | Resume an existing ACP session |
model |
string | no | ACP config option |
effort |
string | no | ACP reasoning effort |
persistent_session_key |
string | no | Reuse a named persistent shell for ACP terminal/* requests |
persistent_shell |
string | no | PowerShell host for the shared ACP terminal session; provide it when persistent_session_key is set |
Example:
curl -X POST http://localhost:8585/exec \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "acp",
"agent": "copilot --acp --stdio",
"prompt": "Implement the login feature",
"workdir": "C:/src/repo",
"env": {
"DOTNET_ROOT": "C:/Users/alice/AppData/Local/Microsoft/dotnet"
}
}'In ACP mode, the env overlay is applied to:
- the ACP agent subprocess itself
- any terminal subprocesses created through ACP
terminal/create
When persistent_session_key + persistent_shell are supplied, ACP terminal/create calls use the same named persistent shell session as POST /exec persistent-shell mode. If an ACP terminal is killed or times out, the runtime resets the shared shell process and replays the remembered seed/bootstrap state on the next command instead of discarding the named session.
Poll a single job by ID.
Common response fields:
| Field | Type | Notes |
|---|---|---|
jobId |
string | Job identifier |
status |
string | pending, running, completed, failed, or timeout |
command |
string | Original shell command or [acp] ... launcher |
submittedAt |
number | Unix timestamp |
lastOutputAt |
number | Updated when output arrives |
processAlive |
boolean | Whether the tracked subprocess is still running |
Shell jobs add fields such as stdout, stderr, exitCode, durationMs, and timedOut.
ACP jobs add fields such as:
acpSessionIdstopReasoneventsacpError(when ACP execution fails)
Persistent-shell jobs use the same result fields as shell jobs, plus mode, sessionKey, and shell.
Example:
curl http://localhost:8585/jobs/abc123 -H "X-API-Key: $KEY"Returns the most recent jobs (up to 20), newest first. Each item is the same sanitized shape returned by GET /jobs/{id}.
Writes a newline to the running job's stdin. This is mainly useful for shell or persistent-shell commands that are waiting for input or need output to flush.
Example:
curl -X POST http://localhost:8585/jobs/abc123/nudge -H "X-API-Key: $KEY"Response shape:
{"nudged":true,"processAlive":true}If the process is already finished or no stdin is available, the endpoint returns nudged: false with a reason.
Unauthenticated example:
curl http://localhost:8585/health{"status":"ok","authenticated":false}Authenticated example:
curl http://localhost:8585/health -H "X-API-Key: $KEY"{
"status": "ok",
"version": "0.3.0",
"hostname": "devbox-01",
"jobs": 0,
"cwd": "C:\\\\src\\\\agent-runtime",
"authenticated": true,
"terminal_supported": true,
"terminal_sessions": 0,
"acp_supported": true,
"execution_modes": ["shell", "acp", "persistent_shell"],
"persistent_shell_supported": true,
"persistent_shell_shells": {"pwsh": true, "powershell": true},
"persistent_shell_default_shell": "pwsh",
"persistent_shell_sessions": 0,
"persistent_shell_max_sessions": 32,
"persistent_shell_idle_timeout_seconds": 0.0
}Returns the currently tracked persistent shell sessions.
curl http://localhost:8585/sessions -H "X-API-Key: $KEY"Each item includes sessionKey, shell, status, createdAt, lastUsedAt, lastOutputAt, processAlive, and activeCommand.
Returns one persistent shell session record or 404 if the key is unknown.
curl http://localhost:8585/sessions/workflow-123 -H "X-API-Key: $KEY"Deletes a named persistent shell session and terminates its shell process.
curl -X DELETE http://localhost:8585/sessions/workflow-123 -H "X-API-Key: $KEY"Triggers a graceful restart for self-update. The server returns 409 instead when jobs are still running, terminal sessions are active, or named persistent shell sessions still exist.
curl -X POST http://localhost:8585/update -H "X-API-Key: $KEY"The headless ACP client can be used independently for driving any ACP agent from Python:
from agent_runtime.acp_client import run_acp_session_sync, HeadlessApprovePolicy
result = run_acp_session_sync(
agent_cmd=["copilot", "--acp", "--stdio"],
prompt="Refactor the auth module",
workdir="/path/to/repo",
timeout=600,
env={"DOTNET_ROOT": "/home/alice/.dotnet"},
permission_policy=HeadlessApprovePolicy("/path/to/repo"),
)
print(result.output_text) # Agent's response
print(result.session_id) # For session continuity
print(result.tool_calls) # Structured tool call data
print(result.stop_reason) # "end_turn", "timeout", "error"env is optional and overlays string key/value pairs onto the current process environment for the ACP agent subprocess and ACP-created terminal subprocesses.
┌─────────────────────────────────────────────────────┐
│ Your Orchestrator / Dashboard / CI │
│ (any HTTP client) │
└────────┬─────────────────────┬──────────────────────┘
│ HTTP │ WebSocket
▼ ▼
┌─────────────────────────────────────────────────────┐
│ agent-runtime (on the target machine) │
│ │
│ ┌───────────────────┐ ┌─────────────────────────┐ │
│ │ Command Server │ │ Terminal Server │ │
│ │ :8585 │ │ :8586 (WebSocket) │ │
│ │ │ │ │ │
│ │ POST /exec │ │ ConPTY ↔ xterm.js │ │
│ │ GET /jobs/{id} │ │ JSON input/raw output │ │
│ │ GET/DELETE /sessions│ │ Resize, idle timeout │ │
│ │ POST /nudge │ │ Max 2 sessions │ │
│ │ POST /update │ │ │ │
│ └───────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Persistent Shell Manager │ │
│ │ Named pwsh / Windows PowerShell sessions │ │
│ │ Seed env/workdir + optional bootstrap replay │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ ACP Client (headless) │ │
│ │ JSON-RPC over stdio → any ACP agent │ │
│ │ Permission policies, session continuity │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
- Zero runtime dependencies — stdlib-only Python. Terminal extras are opt-in.
- Async job pattern — fire-and-forget commands avoid tunnel timeout issues.
- Headless persistent shells — workflow shell reuse stays separate from the browser terminal subsystem.
- Self-updating — exit code 42 triggers pip upgrade + restart via the wrapper loop.
- Security — auto-generated API keys, workdir-scoped permission policies, path traversal protection.
| Environment Variable | Default | Description |
|---|---|---|
agent_runtime_API_KEY |
auto-generated | API key for authenticating requests |
DEVPILOT_TUNNEL_URL |
— | Devtunnel URL (set automatically with --tunnel) |
DEVPILOT_TUNNEL_TOKEN |
— | Devtunnel access token |
DEVPILOT_PERSISTENT_SHELL_MAX_SESSIONS |
32 |
Maximum number of tracked named persistent shell sessions |
DEVPILOT_PERSISTENT_SHELL_IDLE_TIMEOUT_SECONDS |
0 |
Idle timeout for pruning named persistent shell sessions; 0 disables idle expiry |
Apache 2.0 — see LICENSE for details.
Releases are automated via GitHub Actions. The checked-in publish workflow runs when a version tag is pushed, and the normal flow is:
- Bump
agent_runtime/__init__.py - Run tests and build locally
- Commit and push
main - Fast-forward and push the
releasebranch - Push
vX.Y.Zto trigger PyPI publish
Example for 0.3.0:
# 1. Bump version in agent_runtime/__init__.py
# __version__ = "0.3.0"
# 2. Validate the release locally
python -m pytest tests/ -q
python -m build
# 3. Commit and push main
git checkout main
git add agent_runtime/__init__.py README.md agent_runtime/ tests/
git commit -m "Release 0.3.0"
git push origin main
# 4. Fast-forward the shared release branch
git checkout release
git merge --ff-only main
git push origin release
# 5. Push the version tag — this triggers .github/workflows/publish.yml
git checkout main
git tag v0.3.0
git push origin refs/tags/v0.3.0The publish workflow runs:
- Windows tests
- Source distribution + wheel build
- PyPI publish via the
PYPI_API_TOKENGitHub secret
If the GitHub secret is not configured or you want to publish directly from a workstation, the fallback is:
python -m build
python -m twine upload dist/devpilot_agent-0.3.0*Use a PyPI API token for twine upload.
GitHub Actions setup:
- In the
agent-runtimeGitHub repo, add a repository secret namedPYPI_API_TOKEN - Set the value to a PyPI API token for the
devpilot-agentproject - Push a
v*tag or runPublish to PyPImanually withref=v0.3.0(or the tag/commit you want to publish)