Developer Tools

Shipped2026

TEMPAD

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.md next 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:

  1. TEMPAD polls Linear for unassigned issues in active states.
  2. 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.
  3. A per-issue workspace directory is created or reused, after_create and before_run hooks run, and the configured IDE opens on that directory.
  4. 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:

  1. Workflow Loader. Parses WORKFLOW.md into {config, prompt_template}. Strict YAML front matter on top, Liquid-compatible prompt body below.
  2. Config Layer. Merges CLI flags, ~/.tempad/config.yaml, WORKFLOW.md front matter, environment variables, and defaults with well-defined precedence. Personal preferences win on the user side; team policy wins on the repo side.
  3. Issue Tracker Client. Linear GraphQL adapter today, implementing a stable Issue contract designed for future Jira, GitHub Issues, and Asana adapters.
  4. Orchestrator. The only component that mutates scheduling state. In daemon mode it owns poll ticks, dispatch, reconciliation, retry timers, and session metrics.
  5. Workspace Manager. Maps sanitized issue identifiers to filesystem paths under a configurable root, enforces path-containment invariants, and runs the four lifecycle hooks.
  6. Agent Launcher. bash -lc entry 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.md walks 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 -lc for all external commands. Every hook, IDE launch, and agent launch goes through bash -lc. This is a deliberate trust choice that matches WORKFLOW.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.md into 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_run hooks to enforce additional workspace constraints (for example, refusing to run if the branch is main).
  • 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.md is 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 -lc is 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.md into the workspace solves escaping, survives restarts, and doubles as a replay aid when runs fail.
  • Hot reload is worth the complexity. Editing WORKFLOW.md and 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.

developer-toolscoding-agentslinearorchestrationcligoopen-sourceclaude-codecodex
← Back to all experiments