Developer Tools
Shipped2026TEMPAD
Temporal Execution and Management Poll-Agent Dispatcher: an agent-agnostic local service that turns a Linear board into a live task queue for human developers and headless coding agents.
TEMPAD
Temporal Execution and Management Poll-Agent Dispatcher. An open-source, developer-local service that bridges issue trackers and developer workflows. TEMPAD continuously reads work from Linear, presents the available tasks in a terminal UI, and either opens your IDE on the claimed workspace (TUI mode) or runs coding agents headlessly in isolated per-issue directories (daemon mode).
TEMPAD began as an experiment in answering a simple question: can a team of developers and coding agents share the same task board without a central server, a dispatcher service, or a bespoke control plane? The shipped 1.0 is the answer. Everything it needs already lives in the tracker.
Overview
| Detail | Value |
|---|---|
| Category | Developer tooling, open source |
| Role | Creator, architect, primary maintainer |
| Venture | OneNeural |
| Language | Go 1.22+ |
| License | MIT |
| Tracker | Linear (GraphQL, adapter-ready for Jira/GitHub Issues) |
| Agents | Claude Code, Codex, Aider, OpenCode, or any prompt-in CLI |
| Timeline | March 2026 initial spec, shipped as 1.0 in April 2026 |
| Status | Shipped; 57 of 57 backlog tickets closed |
| Links | GitHub, brew install oneneural/tap/tempad |
Why I Built It
I spend most of my day moving between Linear, a terminal, and an IDE with a coding agent running inside it. The friction is always the same: scan the board, pick a ticket, assign it to myself, clone or reuse a worktree, bootstrap the environment, copy the ticket description into the agent's prompt, and only then start real work. Multiply that across a team (human and AI) and you get duplicate claims, stale branches, and agents running wherever someone last left them.
OpenAI's Symphony framed the orchestration layer cleanly, but it is opinionated about which agent and which tracker you use, and it leans on a central coordinator. I wanted an alternative that was:
- Agent-agnostic. My workflow uses Claude Code. A teammate uses Codex. CI uses Aider. The orchestrator should not care.
- Serverless by design. The tracker is already a shared coordination layer. Assignment is the distributed lock.
- Local-first. Each developer runs their own instance against their own credentials. No fleet management, no central deployment.
- In-repo policy. The prompt, hooks, and runtime defaults live in
WORKFLOW.mdnext to the code they shape.
TEMPAD is the minimum surface that covers all four.
What It Does
TUI Mode (Interactive)
The default mode. Run tempad and you get a live task board:
- TEMPAD polls Linear for unassigned issues in active states.
- The developer picks a task; TEMPAD assigns it to their Linear identity, re-fetches to verify the claim won the race, and falls back gracefully if someone else grabbed it first.
- A per-issue workspace directory is created or reused,
after_createandbefore_runhooks run, and the configured IDE opens on that directory. - Control returns to the board immediately. You can claim the next task in parallel; the agent inside your IDE owns the rest.
Daemon Mode (Headless)
Run tempad --daemon and TEMPAD becomes an autonomous worker for one developer's quota:
- Poll, dispatch, reconcile on a tick (default 30s).
- Claim eligible issues, spawn the configured agent subprocess inside the isolated workspace, deliver the rendered prompt through file / stdin / arg / env.
- Monitor the subprocess for completion, failure, stall, or a tracker state change that makes the run ineligible; clean up, retry with exponential backoff (capped), or release the claim when retries exhaust.
- Respect global and per-state concurrency limits so high-risk columns (for example
Needs Review) can be throttled independently.
Daemon mode is what makes TEMPAD more than a fancy task picker: it turns a single Mac mini or VM into a compliant 24/7 coding worker for one human's Linear quota.
Feature Matrix
| Capability | TUI Mode | Daemon Mode |
|---|---|---|
| Live Linear task board | Yes | Yes |
| Assignment-based claim + race detection | Yes | Yes |
| Per-issue isolated workspace | Yes | Yes |
| Workspace lifecycle hooks | Yes | Yes |
| IDE launch on claim | Yes | No |
| Headless agent subprocess | No | Yes |
| Concurrency limits (global + per-state) | No | Yes |
| Retry with exponential backoff | No | Yes |
| Stall detection | No | Yes |
| Tracker state reconciliation | Manual | Per tick |
Hot reload of WORKFLOW.md |
Yes | Yes |
| Optional HTTP dashboard | Optional | Optional |
Architecture
flowchart LR
subgraph Developer["Developer machine"]
CLI[tempad CLI]
Config["~/.tempad/config.yaml"]
Workflow["WORKFLOW.md<br/>(repo)"]
IDE["IDE<br/>(TUI mode)"]
Agent["Coding agent<br/>subprocess<br/>(daemon mode)"]
Workspace[("Per-issue<br/>workspace")]
end
subgraph Core["TEMPAD core"]
Loader[Workflow Loader]
CfgLayer[Config Layer]
Orchestrator[Orchestrator]
WM[Workspace Manager]
Launcher[Agent Launcher]
Logs[Structured logs]
end
subgraph External["External"]
Linear[Linear GraphQL]
end
CLI --> Loader
CLI --> CfgLayer
Workflow --> Loader
Config --> CfgLayer
Loader --> Orchestrator
CfgLayer --> Orchestrator
Orchestrator -->|claim and reconcile| Linear
Orchestrator --> WM --> Workspace
Orchestrator --> Launcher
Launcher -->|TUI| IDE
Launcher -->|daemon| Agent
Agent --> Workspace
Orchestrator --> Logs
TEMPAD is built as six focused components behind a thin CLI:
- Workflow Loader. Parses
WORKFLOW.mdinto{config, prompt_template}. Strict YAML front matter on top, Liquid-compatible prompt body below. - Config Layer. Merges CLI flags,
~/.tempad/config.yaml,WORKFLOW.mdfront matter, environment variables, and defaults with well-defined precedence. Personal preferences win on the user side; team policy wins on the repo side. - Issue Tracker Client. Linear GraphQL adapter today, implementing a stable
Issuecontract designed for future Jira, GitHub Issues, and Asana adapters. - Orchestrator. The only component that mutates scheduling state. In daemon mode it owns poll ticks, dispatch, reconciliation, retry timers, and session metrics.
- Workspace Manager. Maps sanitized issue identifiers to filesystem paths under a configurable root, enforces path-containment invariants, and runs the four lifecycle hooks.
- Agent Launcher.
bash -lcentry point for IDEs (TUI) and agents (daemon), with pluggable prompt delivery and a minimum contract of launch / wait / exit.
Observability sits on top of all six via structured logs (issue_id, issue_identifier, mode, attempt) and an optional HTTP server for read-only state, per-issue runtime, and manual refresh triggers.
Coordination Without a Central Server
The design decision I am proudest of: TEMPAD has no central coordinator. Assignment in Linear is the distributed lock. Two instances can race on the same issue; the loser re-fetches, sees the other assignee, and moves on. Fleet visibility is whatever Linear already shows you. Restart recovery is trivial because in-memory state is intentionally non-persistent: startup cleans up terminal workspaces, polling re-discovers work, retries re-build themselves.
Section 5 of the spec formalizes this as an optimistic concurrency pattern. Appendix C walks through the comparison with centralized architectures and why, for a developer tool, none of the added complexity is worth it.
Claim Mechanism
sequenceDiagram
participant Dev
participant TEMPAD
participant Linear
Dev->>TEMPAD: Pick task or tick fires
TEMPAD->>Linear: Fetch candidate issues
Linear-->>TEMPAD: Unassigned active issues
TEMPAD->>Linear: Assign issue to identity
TEMPAD->>Linear: Re-fetch issue to verify
alt Assignee matches identity
TEMPAD-->>Dev: Claim won and workspace ready
TEMPAD->>TEMPAD: Run hooks and launch
else Assignee is someone else
TEMPAD->>Linear: Unassign issue
TEMPAD-->>Dev: Claim lost and skip
end
Lifecycle: From Tick to Pull Request
1. Poll tick
- Reconcile running issues (stall check + tracker state refresh)
- Validate config
- Fetch candidate issues (unassigned, active states)
- Sort by priority, age, identifier
- Dispatch while concurrency slots remain
2. Dispatch
- Assign issue to tracker identity
- Verify claim (race detection)
- Sanitize identifier -> workspace key
- Create or reuse <workspace_root>/<key>/
- Run after_create (first time only) and before_run hooks
- Render prompt from WORKFLOW.md body + issue + attempt
- Deliver prompt via file | stdin | arg | env
- Launch agent subprocess in workspace
3. Monitor
- Stream stdout/stderr, parse optional JSON-line telemetry
- Enforce turn_timeout_ms and stall_timeout_ms
- Accumulate token and runtime totals
4. Exit
- Normal (exit 0) -> schedule 1s continuation check
- Failure (exit !=0) -> exponential backoff retry (cap: 5 min)
- Terminal state -> kill, clean workspace, release claim
- Retries exhausted -> unassign, log exhaustion, return to pool
Run Attempt State Machine (Daemon Mode)
stateDiagram-v2
[*] --> Unclaimed
Unclaimed --> Claimed: assign and verify
Claimed --> Running: launch agent
Running --> Succeeded: exit zero
Running --> Failed: exit non-zero
Running --> TimedOut: turn timeout
Running --> Stalled: stall timeout
Running --> Canceled: state change
Failed --> RetryQueued: backoff
TimedOut --> RetryQueued: backoff
Stalled --> RetryQueued: backoff
RetryQueued --> Running: timer fires
RetryQueued --> Released: retries exhausted
Succeeded --> Released: terminal state
Canceled --> Released
Released --> [*]
Design Decisions and Trade-offs
- Go over Rust and Elixir.
docs/STACK_COMPARISON_v1.mdwalks through a weighted matrix. Go won on single-binary distribution, operational simplicity, and the quality of the TUI and GraphQL ecosystems for this problem shape. Rust's correctness and Elixir's supervision were attractive but not worth the friction for a local-first tool that already has a tracker as its durable store. bash -lcfor all external commands. Every hook, IDE launch, and agent launch goes throughbash -lc. This is a deliberate trust choice that matchesWORKFLOW.md's role as trusted team configuration, and it keeps the executor dead simple. Hardening guidance is documented explicitly in Section 17.6 of the spec.- Assignment as the only tracker write. TEMPAD only assigns and unassigns. Every other ticket mutation (state transitions, comments, PR metadata) belongs to the coding agent via its own tools. This keeps the boundary between scheduler and agent legible.
- Liquid-compatible template with strict unknown-variable failure. Silent fallback is a subtle way to ship broken prompts into autonomous runs. TEMPAD fails loudly on the first unknown variable or filter so you catch mistakes in
tempad validate, not at 2 AM. - File-based prompt delivery as the default. Writing
PROMPT.mdinto the workspace works with every agent I have tried, leaves a durable artifact next to the work for debugging, and avoids shell-escaping arguments. - Non-persistent in-memory state. Retry timers, running entries, and totals all live in RAM. After a restart TEMPAD recovers by cleaning terminal workspaces and re-polling. Persistence was considered and rejected: adding a database changes the operational profile of a local dev tool much more than it saves you on recovery edge cases.
Configuration Example
A minimal WORKFLOW.md that drives daemon mode against Linear with Claude Code:
---
tracker:
kind: linear
api_key: $LINEAR_API_KEY
project_slug: my-project
polling:
interval_ms: 30000
workspace:
root: ~/workspaces/tempad
hooks:
after_create: |
git clone git@github.com:myorg/myrepo.git .
pnpm install
before_run: |
git fetch origin
git checkout -B tempad/{{ issue.identifier | downcase }} origin/main
agent:
command: 'claude --dangerously-skip-permissions'
prompt_delivery: file
max_concurrent: 3
max_concurrent_by_state:
'in progress': 2
'needs review': 1
max_turns: 20
max_retries: 10
turn_timeout_ms: 3600000
stall_timeout_ms: 300000
---
You are working on {{ issue.identifier }}: {{ issue.title }}.
{{ issue.description }}
{% if attempt %}
This is retry attempt {{ attempt }}. Continue from the workspace; do not start over.
{% endif %}
When done, move the issue to "Human Review".
Per-developer preferences live in ~/.tempad/config.yaml:
tracker:
identity: 'subodh@oneneural.ai'
api_key: '$LINEAR_API_KEY'
ide:
command: 'cursor'
args: '--new-window'
agent:
command: 'claude --dangerously-skip-permissions'
display:
theme: 'auto'
Install and Run
# Homebrew
brew install oneneural/tap/tempad
# Script
curl -sSL https://raw.githubusercontent.com/oneneural/tempad/main/scripts/install.sh | bash
# Go
go install github.com/oneneural/tempad/cmd/tempad@latest
Then:
tempad init # seed ~/.tempad/config.yaml
tempad validate # verify config before running
tempad --workflow WORKFLOW.md # TUI mode
tempad --daemon --workflow WORKFLOW.md # headless
tempad --daemon --port 8080 --workflow WORKFLOW.md # headless + HTTP dashboard
Safety and Operational Notes
TEMPAD is explicit about the fact that it is not a sandbox. It runs on your machine with your credentials, and any agent it launches inherits the parent environment (including LINEAR_API_KEY). The spec's hardening guidance is the canonical reference; at minimum:
- Keep agent approval settings conservative unless you understand the trust model.
- Use
before_runhooks to enforce additional workspace constraints (for example, refusing to run if the branch ismain). - Filter the Linear project, labels, or states that daemon mode is allowed to touch.
- Consider running daemon instances inside a container or under a restricted OS user when running against production projects.
Workspace path containment, identifier sanitization, and cwd enforcement at launch are hard invariants in code, not just documentation.
What I Learned
- Writing the spec before the Go code saved weeks.
docs/SPEC_v1.mdis deliberately language-agnostic. Every non-trivial disagreement during implementation was resolved by going back to the spec, and the spec is now the reference for a second implementation. - Optimistic concurrency is underrated. Once the claim and verify loop worked, every other "what if two instances hit it at once" concern dissolved. Most of the complexity people associate with distributed systems is avoidable if you already have a shared source of truth.
bash -lcis a feature, not a bug. It collapsed three layers of escaping into one, matched developer expectations, and made hooks feel like writing shell scripts instead of YAML.- Prompt as a workspace artifact beats prompt as an argument. Writing
PROMPT.mdinto the workspace solves escaping, survives restarts, and doubles as a replay aid when runs fail. - Hot reload is worth the complexity. Editing
WORKFLOW.mdand having the next tick pick it up (with a last-known-good fallback on invalid reloads) turned the prompt into a living document during tuning sessions.
Status and Roadmap
TEMPAD 1.0 shipped with all 57 backlog tickets closed. The tracker client is Linear-only today; the adapter interface is the stable contract for future trackers.
Near-term items I am considering:
- Jira and GitHub Issues adapters.
- Agent protocol extension for Codex app-server JSON-RPC (for true multi-turn sessions inside a single subprocess).
- Persistent retry queue behind a feature flag, for operators who want zero-loss restart recovery.
- Desktop notifications in TUI mode for high-priority new tasks and agent completions.
None of these are required for conformance, and the spec calls them out explicitly as recommended extensions rather than core behavior.