A self-hosted coding agent that runs on your VPS, keeps working when you disconnect, and acts within boundaries you control.
TeleCoder turns a cheap Linux VPS into a persistent coding agent. You give it a task or set up a watch, close your laptop, and come back to results. Sessions survive disconnections. Watches monitor your CI and PRs while you sleep.
It delegates the actual coding to existing agents (Codex, Claude, OpenCode, etc.) through acpx, so you get one consistent interface regardless of which model is doing the work.
Two modes:
- Reactive -- send a prompt, get durable results you can inspect later
- Proactive -- set up watches that fire automatically on CI failures or new PRs
TeleCoder also stores structured session outcomes so you can review what changed, what was verified, what remains uncertain, and what should happen next without rereading a full transcript.
- Bun >= 1.3.5
- Git
- acpx installed and on your PATH
- An API key for at least one coding agent (e.g.
OPENAI_API_KEYfor Codex)
git clone <your-telecoder-repo-url>
cd telecoderNo bun install needed -- TeleCoder uses zero npm dependencies. Everything runs on Bun built-ins (SQLite, HTTP server, process spawning).
# Start the server
bun run serve
# In another terminal, run a task
bun src/cli.ts run --repo /path/to/your/repo "fix the failing test in auth.ts"TeleCoder will:
- Clone your repo into an isolated workspace
- Create a
telecoder/<session-id>branch - Run acpx with your prompt
- Stream events to your terminal
- Store the result in SQLite for later inspection
# List all sessions
bun run list
# Get details on a specific session
bun run status <session-id>
# See the event stream
bun run events <session-id>
# See the outcome-first inbox
bun run inbox
# Rerun a failed or completed session
bun src/cli.ts rerun <session-id>Start the server and interact via REST:
bun run serve
# Server starts on http://127.0.0.1:7080
# Create a task
curl -X POST http://localhost:7080/api/sessions \
-H "Content-Type: application/json" \
-d '{"repo": "/path/to/repo", "prompt": "refactor the billing module"}'
# List sessions
curl http://localhost:7080/api/sessions
# Get session details
curl http://localhost:7080/api/sessions/<id>
# Review the outcome-first inbox
curl http://localhost:7080/api/inbox
# Stream events (SSE)
curl -H "Accept: text/event-stream" http://localhost:7080/api/sessions/<id>/events
# Health check
curl http://localhost:7080/healthWatches are standing instructions. They fire automatically when conditions are met.
# Watch all CI on a repo
bun src/cli.ts watch-add-ci --repo /path/to/repo "investigate the failure and suggest a fix"
# Watch a specific workflow and branch
bun src/cli.ts watch-add-ci --repo /path/to/repo \
--workflow build --branch main \
"investigate the failure, run tests locally, suggest a fix"When CI fails, TeleCoder automatically spins up a session that investigates and reports back.
# Watch all PRs
bun src/cli.ts watch-add-pr --repo /path/to/repo "review the code changes for correctness and style"
# Watch PRs targeting a specific base branch
bun src/cli.ts watch-add-pr --repo /path/to/repo \
--base main \
"review the diff, check for security issues, suggest improvements"# Simulate a CI failure
bun src/cli.ts trigger-ci \
--repo /path/to/repo \
--workflow build --branch main --run-id 12345 \
--status completed --conclusion failure
# Simulate a PR event
bun src/cli.ts trigger-pr \
--repo /path/to/repo \
--pr-number 42 --title "Add auth" --base main --head feature/auth \
--action opened# List all watches
bun src/cli.ts watch-list
# Filter by kind or status
bun src/cli.ts watch-list --kind ci_failure --status active
# See run history for a watch
bun src/cli.ts watch-runs <watch-id>Watch runs and completed sessions are summarized into outcome fields:
ChangedVerifiedUncertainNext
When a session produces useful changes, publish them:
bun src/cli.ts publish-session <session-id> --base main
# With custom PR title and body
bun src/cli.ts publish-session <session-id> --base main \
--title "Fix auth test" --body "Resolved the flaky assertion in auth.test.ts"This commits any pending changes, pushes the session branch, and opens a GitHub PR. Requires GITHUB_TOKEN, GH_TOKEN, or a logged-in gh CLI.
All configuration is through environment variables. Defaults work out of the box for local development.
| Variable | Default | Description |
|---|---|---|
TELECODER_DATA_DIR |
~/.telecoder |
Where SQLite database and workspaces live |
TELECODER_LISTEN_HOST |
127.0.0.1 |
Server bind address |
TELECODER_LISTEN_PORT |
7080 |
Server port |
TELECODER_DEFAULT_AGENT |
codex |
Default coding agent |
TELECODER_POLICY_MODE |
observe |
Default trust policy (see below) |
TELECODER_ACPX_COMMAND |
acpx |
Path to acpx binary |
TELECODER_PROMPT_TIMEOUT_SECONDS |
300 |
Max execution time per session |
TELECODER_SESSION_HEARTBEAT_SECONDS |
5 |
Heartbeat interval for liveness |
TELECODER_SESSION_STALE_SECONDS |
30 |
Timeout before marking session abandoned |
GITHUB_TOKEN / GH_TOKEN |
-- | For PR publishing |
Override the command for a specific agent:
export TELECODER_AGENT_CLAUDE_COMMAND="claude --model sonnet"
export TELECODER_AGENT_OPENCODE_COMMAND="/usr/local/bin/opencode"Use with --agent claude or --agent opencode in CLI/API calls.
bun run configEvery session runs under a policy that controls what the agent is allowed to do:
| Mode | Permissions | Workspace Writes | Max Runtime | Use Case |
|---|---|---|---|---|
locked |
deny-all | blocked | 60s | Read-only investigation |
observe |
approve-reads | blocked | 180s | Analysis without changes (default) |
standard |
approve-all | contained | 300s | Let the agent make changes in its branch |
Set per-session:
bun src/cli.ts run --repo /path/to/repo --policy standard "fix the bug and commit"Or set globally:
export TELECODER_POLICY_MODE=standardFor always-on operation, deploy to a Linux VPS.
# Clone to a stable path
sudo mkdir -p /opt/telecoder
git clone <repo-url> /opt/telecoder/app
cd /opt/telecoder/app
# Run the install script
sudo scripts/install/install-ubuntu-vps.shThis installs Bun, Node 22, acpx, creates a systemd service, and starts TeleCoder.
# Edit the env file
sudo vim /etc/telecoder/telecoder.env
# Add your API keys and preferences:
# OPENAI_API_KEY=sk-...
# ANTHROPIC_API_KEY=sk-ant-...
# TELECODER_POLICY_MODE=observe
# TELECODER_DEFAULT_AGENT=codex
# Restart after changes
sudo systemctl restart telecoder.servicecurl http://127.0.0.1:7080/health
sudo systemctl status telecoder.service
sudo journalctl -u telecoder.service -n 50cd /opt/telecoder/app
git pull
sudo scripts/install/install-ubuntu-vps.sh --no-start
sudo systemctl restart telecoder.service# Run with auto-reload
bun run dev
# Run tests
bun run test
# Show CLI help
bun src/cli.ts helpsrc/
cli.ts CLI command dispatcher
server.ts HTTP/REST API (Bun.serve)
engine.ts Core task orchestration
store.ts SQLite persistence and migrations
types.ts TypeScript type definitions
config.ts Environment-based configuration
policy.ts Trust policy resolution
process.ts Shell command execution
workspace.ts Git workspace preparation
publish.ts PR publishing and GitHub integration
watch.ts Watch logic, matching, and prompting
outcome.ts Structured session outcome extraction
runtime/
acpx.ts ACP protocol integration
test/
*.test.ts Test suite (Bun test runner)
fixtures/ Test mocks and helpers
notes/
vision.md Product vision
vps-ubuntu.md VPS deployment guide
plans/ Sprint plans and reports
No code changes required. Set an environment variable:
export TELECODER_AGENT_MYAGENT_COMMAND="/path/to/my-agent"Then use it:
bun src/cli.ts run --repo . --agent myagent "do the thing"The agent must work with acpx. See the acpx docs for supported runtimes.
- Add your watch kind to
WatchKindinsrc/types.ts - Implement event detection (
isMyEvent) and matching (matchesMyWatch) insrc/watch.ts - Build a prompt constructor (
buildMyWatchPrompt) insrc/watch.ts - Add trigger logic to
src/engine.ts - Wire up the CLI command in
src/cli.tsand the HTTP endpoint insrc/server.ts
The existing CI and PR watches are good templates to follow.
Add a new policy mode by extending the POLICY_DEFAULTS map in src/policy.ts:
const POLICY_DEFAULTS: Record<TeleCoderPolicyMode, PolicyDefaults> = {
// ...existing modes...
mymode: {
permissionMode: "approve-reads",
workspaceWritePolicy: "contained",
maxRuntimeSeconds: 120,
},
};Then add "mymode" to the TeleCoderPolicyMode type in src/types.ts.
| Method | Path | Description |
|---|---|---|
POST |
/api/sessions |
Create a task ({repo, prompt, agent?, policy?}) |
GET |
/api/sessions |
List sessions (?status=&agent=&policy=&parent=&lineage=) |
GET |
/api/inbox |
List recent completed/failed sessions with structured outcomes |
GET |
/api/sessions/:id |
Get session details |
POST |
/api/sessions/:id/rerun |
Rerun a finished session |
GET |
/api/sessions/:id/events |
Get events (JSON or SSE with Accept: text/event-stream) |
GET |
/api/sessions/:id/lineage |
Get session family tree |
POST |
/api/sessions/:id/publish |
Create a PR ({base, title?, body?}) |
| Method | Path | Description |
|---|---|---|
POST |
/api/watches |
Create a watch ({kind, repo, instructions, ...}) |
GET |
/api/watches |
List watches (?kind=&status=&repo=) |
GET |
/api/watches/:id |
Get watch details |
GET |
/api/watches/:id/runs |
Get watch run history |
| Method | Path | Description |
|---|---|---|
POST |
/api/watch-events/ci |
Trigger CI watches ({repo, workflow, branch, runId, ...}) |
POST |
/api/watch-events/pr |
Trigger PR watches ({repo, prNumber, title, base, head, ...}) |
The current product direction is:
- durable background sessions
- warm reusable workspaces
- outcome-first review instead of transcript-first review
This repo now includes the first slice of that direction through structured session outcomes and the inbox API/CLI surface.
You (CLI / API)
|
v
TeleCoder Engine
|-- SQLite (sessions, events, watches)
|-- Policy resolution
|-- Workspace isolation (git clone + branch)
|
v
acpx exec
|
v
Coding Agent (Codex, Claude, OpenCode, ...)
Each task gets its own git workspace and branch (telecoder/<session-id>). The agent works in isolation. Results are captured, stored, and available through CLI or API. Nothing touches your main branch unless you explicitly publish.
See LICENSE for details.