Skip to main content

SprintBaton

SprintBaton

An asynchronous coding agent that runs on your todolist.

License: GPL v3 Python 3.12+ Status: alpha

SprintBaton turns version-by-version product improvement into a background process. You hand the agent the tasks you want done by labelling them on your todolist; the agent hands tasks back to you when it needs clarification. Work gets woven into your day at your own convenience instead of requiring you to sit in a live coding session.


Why

Coding agents have made building applications easy. But improving them - week after week, version after version - does not stay easy. If each application has n avenues of improvement and you maintain m applications, the upgrade workload grows roughly quadratically, as n × m, and the cost of personally attending a synchronous agent session for each one becomes prohibitive.

SprintBaton makes that loop asynchronous. The interface is a todolist you already use; the agent works in the background and only interrupts you with the clarifications that genuinely need a human.


How it works

  1. You onboard a Project - one todolist board plus the git repositories it drives. SprintBaton queues a one-time initialization run that reads each repository and writes the metadata that every later model reasons from.
  2. You brainstorm improvements while using your app and drop them into the Icebox column as todolist tasks.
  3. You hand the ones you want worked on to the agent by applying the sprintbaton-agent label.
  4. A polling job picks up labelled tasks and queues them. Tasks wait in the Icebox until the project's first initialization has finished.
  5. The Router classifies each task and routes it down the right path - answering itself, asking you, planning, or coding.
  6. When the agent needs you, it comments on the task and swaps its label to sprintbaton-human. You answer in the comments whenever you have time, then put the sprintbaton-agent label back.
  7. Finished work arrives as pull requests - one per repository the task touched - for your review, and merges into the dev integration branch; a scheduled release window later cuts the batch to staging for QA, then to live on your sign-off.

Each Project on your board maps to one or more git repositories (a frontend and a backend, say). Columns carry semantic state; labels carry whose turn it is; comments carry the human ↔ agent conversation.

flowchart LR
    Icebox[Icebox] --> Router{Router}
    Router -->|Simple| PC1[Passing Criteria]
    Router -->|Complex, or Simple flagged important| PC2[Passing Criteria]
    Router -->|Ambiguous / Abstract| TaskFinal[Task Finalization]
    PC1 --> InProgress[In Progress]
    PC2 --> PlanFinal[Plan Finalization]
    TaskFinal -->|clarified| PC3[Passing Criteria]
    PC3 --> TaskFinalized{Spec Classification}
    TaskFinalized -->|directly implementable| InProgress
    TaskFinalized -->|needs a plan| PlanFinal
    PlanFinal -->|planned| PlanFinalized{Plan Classification}
    PlanFinalized -->|L1 or L2| InProgress
    InProgress --> CodeReview[Code Review]
    CodeReview -->|changes requested| InProgress
    CodeReview -->|approved, PR opened| InReview[In Review]
    InReview -->|PR comments| InProgress
    InReview -->|release window| QA
    QA -->|batch sign-off| Shipped

Features

  • Todolist-native interface - no separate dashboard to babysit. Works through labels, columns, and comments on your existing board. Clarifying questions can carry a numbered list of options - reply with a number.
  • Pluggable todolist providers - adaptor pattern with a common interface that can be extended by todolist providers. The Kanban board columns can be mapped by hand, matched by name, or created for you after you confirm the plan.
  • Multi-repository projects - one board can drive changes across several repositories. A scoping step picks the repositories a task touches, each of which gets its own pull request, and the card follows the slowest one.
  • Pluggable agent frameworks - the coding backend is as swappable as the todolist provider. Every task action runs through a named harness, so you can point any role at the in-house tool loop, the Claude Agent SDK, the Claude Code CLI (claude), the OpenAI Agents SDK / Codex CLI (codex), the Google ADK / Gemini CLI (gemini), or OpenHands - mixing providers per role via AgentDefinitions. sprintbaton providers add <provider> --bind-roles seeds one agent per (runtime x model tier) and binds every task action to them in one command; SPRINTBATON_<ACTION>_AGENT overrides any single one.
  • Fallback chains and usage-limit awareness - each action can name an ordered chain of agents. When a provider hits a usage limit or a self-imposed token budget, the task walks to the next agent; only when the whole chain is exhausted does it pause in place until the limit resets.
  • Tiered model routing - a classifier decides whether each task needs higher-tier (L2) reasoning or can run on the default (L1) tier, controlling cost without sacrificing reliability on the tasks that matter. Tiers are provider-agnostic - L1/L2 map to the models of whichever provider you bound the roles to.
  • Pre-PR AI review loop - before any pull request is opened, a Review Agent judges the completed diff against the task and its passing criteria. Rejections bounce straight back to the Coding Model (same tier, no human visibility) for a bounded number of rounds; only an approved diff - or one that exhausted its bounce budget and escalated - ever reaches a human.
  • Conflict resolution - every branch is test-merged against dev before its PR opens; a conflict is handed to a narrowly scoped Conflict Resolution Agent instead of surfacing as a surprise on GitHub.
  • Asynchronous clarification loop - any role (except the Router) can pause on a question posted as a comment. If you answer within a few minutes it picks its own session back up; later answers restart the turn with the earlier questions and answers folded in, so nothing is lost either way.
  • Runtime escalation - execution is monitored; mis-routed or stalled tasks are corrected up a tiered ladder instead of failing silently.
  • Irreversibility guardrails - the agent never runs a destructive operation (migrations, data deletes, force-pushes, cluster changes) unattended; these hard-stop and wait for explicit human approval. Some harnesses do not allow these hooks and interventions - SprintBaton warns about this on every such run.
  • Repository metadata - an agentic initialization run explores each repository and extracts code insights and invariants so downstream models reason from facts about your codebase, not guesses. Metadata is published as immutable revisions, validated against a fixed structure, and retried automatically when the model API or git remote is briefly unreachable.
  • A shared notebook per task - every role with a checkout finds the task, spec, plan, passing criteria, a running notes.md from earlier roles and the Review Agent's findings under .sprintbaton/tasks/<task>/ in its clone. That folder is kept out of every commit.
  • Pull-request workflow - opens PRs, responds to review comments, and runs the dev → staging → production promotion on scheduled release windows with a QA gate. SprintBaton opens the promotion PRs; you merge them.
  • Local tool or hosted service - runs as a zero-infrastructure CLI on your machine, or multi-tenant in a cluster (MongoDB or PostgreSQL, Redis, and S3, Google Cloud Storage or Azure Blob Storage) with per-user encrypted credentials and a control-plane API. In hosted mode every command a model or a repository drives runs in a separate sandbox pod - per-run bubblewrap sandboxes with no network, no deployment secrets and no other tenant's files - so one tenant's code can never reach another's.
  • Observability - OpenTelemetry logs and metrics (and a trace pipeline), plus a record of every task action with its prompt version, model and token use, which powers the built-in token usage reports.

Model tiers

Tiers are provider-agnostic: the Router is the cheap classifier, L1 is the default coding/execution tier, and L2 is the high-reasoning planning/escalation tier. Which concrete model backs each tier depends on your provider (the one you ran providers add ... --bind-roles for) - for Anthropic, L1 is the Sonnet-class model and L2 the Opus-class one; escalation can step one rung above L2 to the dedicated escalation model at E4. The Router maps each task to an entry tier; the escalation subsystem can move it up at runtime.

Category Path Default tier
Simple Passing Criteria → coding; descriptors are sufficient L1
Ambiguous Spec model → user clarification → Passing Criteria → Spec Classification → coding or plan L1 or L2 (classifier-chosen)
Complex Passing Criteria → L2 plans → Plan Classification → coding L2 plan, L1 or L2 code (classifier-chosen)
Abstract Same pipeline as Ambiguous, with an Abstract-tuned finalization agent L2 execution floor, regardless of the classifiers

Importance is orthogonal to the category (Task.important, set by the Router's importance flags) - an important task always executes at the L2 tier (E3) regardless of the classifiers' calls. Important Simple/Complex tasks always plan too (important Simple enters at E2 like Complex); important Ambiguous/Abstract tasks clarify first, then plan or not per Spec Classification.

Escalation triggers (thrashing, correctness stalls, discovered ambiguity, plan breakage, budget exhaustion, review failure) move a task up a cheapest-correction-first ladder (E0 retry → E1 clarify → E2 replan → E3 L2 execution → E4 escalation model → EH human handoff). Three things bypass the ladder: discovered importance (straight to planning), and irreversible operations or the global token cap (straight to you).


Task lifecycle (Kanban columns)

Icebox → Task Finalization → Passing Criteria → Task Finalized → Plan Finalization
  → Plan Finalized → In Progress → Code Review → In Review → QA → Shipped

Simple and Complex tasks skip Task Finalization/Task Finalized entirely - they reach Passing Criteria straight from the Icebox, right after Router classification - and Simple skips Plan Finalization/Plan Finalized too, going straight from Passing Criteria to In Progress (unless flagged important, in which case it takes the planning path like Complex).

  • Icebox - captured ideas. A labelled task waits here, unclassified, until the project's first metadata initialization has succeeded.
  • Task Finalization - awaiting your answers to clarifying questions. Shared by Ambiguous and Abstract tasks - the clarification loop is the same, but the finalization agent answering it differs by category (Abstract defaults to the L2 tier).
  • Passing Criteria - a machine-only checkpoint: given only the task's specification (the raw description for Simple/Complex, the finalized spec for Ambiguous/Abstract) and nothing else, an agent enumerates exhaustive passing/acceptance criteria before any planning or execution begins. These feed the Coding Model and the Review Model.
  • Task Finalized - the finalized spec is being classified: directly implementable (straight to In Progress) or still needs a plan (Plan Finalization).
  • Plan Finalization - a plan is being generated or revised.
  • Plan Finalized - the finalized plan is being classified: L1-implementable or L2-required, before entering In Progress.
  • In Progress - a quick scoping step picks the repositories the task touches, then the Coding Model works through them one at a time. Just before each PR opens, the branch is test-merged against dev and any conflict goes to the Conflict Resolution Agent.
  • Code Review - a machine-only gate: the Review Agent judges the completed diff against the task and its passing criteria before any PR exists. Rejections bounce straight back to In Progress (same tier, no human visibility) for a bounded number of rounds; only approved diffs - or ones that exhausted the bounce budget and escalated - ever reach In Review.
  • In Review - a PR per affected repository is open against dev; your comments bounce the commented repositories back to In Progress. Once every PR has merged, the task sits here until the next release window.
  • QA - the scheduled release window swept the task's batch from dev to staging for regression QA. Handing any task of the batch back to the agent (re-applying sprintbaton-agent) signs the whole release off.
  • Shipped - the okayed release promoted staging to production (live); every task in the batch moves here together.
  • Blocked (optional column) - the agent needs a human decision it cannot make, and says why in a comment. Without the column the card goes to Task Finalization with a sprintbaton-blocked label instead.

Editing and moving cards

The board is also how you steer work that is already under way. SprintBaton only observes your card on each poll; the worker decides what that means at the start of its next pass.

  • Edit the title or description - every edit is kept as a numbered revision, and every spec, plan and set of criteria remembers which revision it was built from. A cheap classifier decides how far back the edit reaches (nothing, the code, the plan, the criteria, the spec, or the whole task). Before any code exists the task just redoes from there and says so in a comment. Once code exists it asks you first: redo from that point, amend the code only, or keep going. Whitespace-only edits are ignored, and edits to work that is already merged are noted but not applied.
  • Move a card forward - it continues from that column. Anything the column strictly needs (a classification, criteria) is worked out quietly, without dragging the card back through the skipped columns. QA is the exception: it means the PRs are merged, and merging happens on GitHub, so the card is moved back with a note if they aren't.
  • Move a card out of QA - only Shipped is accepted. The code is already merged into a release, so to rework it, ship it and then move it back.
  • Move a card backward - that stage is redone, with the previous output and any comment you left as guidance. Branches and PRs are kept: new work goes on top of the existing branch and updates the open PR.
  • Move a card to a column SprintBaton doesn't know (or into Blocked) - the task is parked with everything intact. Move it back to resume.
  • Move a shipped card back - the same card starts a new round of work on a fresh branch from dev, with the previous round as context.
  • Move a card to Shipped - declares it done, outside any release.

Project initialization

The metadata run never appears on your board. It is an invisible task that sprintbaton serve picks up like any other, runs on the project owner's own agents and credentials, and publishes as a new revision only once the result passes validation:

flowchart LR
    Create[project create / init] --> Queued[Initialization run queued]
    Queued --> Poll[Polling job]
    Poll --> Repo[Per-repository pass]
    Repo -->|cut off| Repo
    Repo -->|invalid structure| Repo
    Repo -->|valid| Publish[Publish revision]
    Publish --> Project[Project index pass]
    Project --> Ready[Board tasks released]
    Repo -->|API or git unreachable| Retry[Retry with backoff]
    Retry --> Poll
    Repo -->|permanent failure| Failed[Blocked - see init --status]

Automatic runs happen on first onboarding and whenever a repository is added; re-run it yourself with sprintbaton init after significant changes. If the first run fails, board tasks keep waiting - fix the cause and re-run, open the gate with sprintbaton project metadata-gate "<title>" --open, or opt out of automatic runs (and the wait) with generateMetadata: false.


Install

SprintBaton ships as one Python package. The base install carries no model provider SDK and no storage client - you add the ones you use as extras.

pip install "sprintbaton[anthropic,claude-agent-sdk]"   # CLI/local tool with the
                                                        # Anthropic SDKs: SQLite,
                                                        # filesystem blobs,
                                                        # in-process queue, flock
pip install sprintbaton                                 # a claude / codex / gemini
                                                        # CLI-only setup - no
                                                        # provider SDK needed
pip install "sprintbaton[hosted,anthropic,claude-agent-sdk]"   # + every storage
                                                        # client and the
                                                        # serve-api API

Or install from a checkout, with the same extras:

git clone https://github.com/arunkarthik11/SprintBaton.git
cd SprintBaton
pip install -e .                                  # CLI-only setup
pip install -e ".[anthropic,claude-agent-sdk]"    # + the Anthropic SDKs
pip install -e ".[hosted,anthropic,claude-agent-sdk]"   # + every storage client
                                                  # and the serve-api API

Provider extras - one per in-process SDK. A setup that uses only the claude, codex or gemini CLIs (claude_code_cli, codex_cli, gemini_cli) needs none of them.

Extra Needed for
anthropic the single_shot and raw_tool_loop harnesses
claude-agent-sdk the claude_agent_sdk harness (bundles the claude executable)
openai, openai-agents openai_single_shot, openai_agent_sdk
google-genai, google-adk gemini_single_shot, gemini_agent_sdk
openhands open_hands

You rarely need to pick these by hand: in a CLI install, sprintbaton setup and providers add install the extras for the harnesses you choose (pass providers add --skip-install to opt out). In hosted mode nothing is installed at runtime - they print the extra to add to the image instead. Using a harness whose package is missing fails with an error naming the extra.

Storage and service extras - only for hosted mode:

Extra Contents
mongo / postgres MongoDB / PostgreSQL entity persistence
s3 / gcs / azure S3 (and S3-compatible) / Google Cloud Storage / Azure Blob Storage blobs
redis the hosted queue, lock and config cache
api sprintbaton serve-api (fastapi/uvicorn/bcrypt)
otlp the OTLP gRPC exporter
hosted all of the above - every storage client, no provider SDK

A plain CLI install runs against nothing but a SQLite file, the filesystem, and stdlib locking, all under ~/.sprintbaton/ by default (SPRINTBATON_LOCAL_STORAGE_ROOT).

Prerequisites

  • Python 3.12+ and git
  • Access to one model provider - Anthropic, OpenAI or Google - either as an API key or as a logged-in CLI (claude, codex or gemini). A CLI install needs no API key at all: when the provider's CLI is on your PATH, sprintbaton setup puts every role on it and it authenticates from your login - see CLI-based coding backends below
  • A supported todolist provider account and API token (Todoist today)
  • A GitHub token with repo access for the repositories you onboard

Copy .env.example to .env and fill in the values you need - SprintBaton reads config via pydantic-settings, so anything set in the environment or .env overrides the defaults.

cp .env.example .env

CLI-based coding backends (Claude Code, Codex, Gemini)

SprintBaton can drive the vendor coding CLIs directly as harnesses instead of making metered API calls. Each one runs the real claude / codex / gemini binary headlessly and authenticates purely from that CLI's own login on the machine running sprintbaton serve. These runs draw from your subscription's usage pool rather than metered API billing - a cost split subject to each vendor's own billing policy, not a SprintBaton guarantee.

Harness CLI binary Login command Provider What it can do today
claude_code_cli claude claude login anthropic every role,
codex_cli codex codex login openai every role;
gemini_cli gemini gemini auth login google every role;

A few things hold for all three:

  • Login-only. SprintBaton never forwards an API key to them, and it strips ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY / GOOGLE_API_KEY from their environment. A key you happen to have exported can never quietly decide which account a run is billed to.
  • Tool mode only. They run their tools directly on the host, so they can't be sandboxed, and a hosted deployment refuses them. codex and gemini also keep their login on disk (~/.codex/auth.json, ~/.gemini/), which a hosted worker couldn't use anyway. An agent that names one where it can't run is refused when you create it, not when a task reaches it.
  • Chains soften the edges. List an API or SDK agent after the CLI one (SPRINTBATON_REVIEW_AGENT=openai-cli-code,openai-sdk-code) and the resolver skips the entry it can't use.

For Codex, providers add openai with a cli runtime also adds SprintBaton's guard hook to your ~/.codex/hooks.json (your own entries are kept, and the file is backed up). Codex only runs hooks you have trusted, so open codex, go to /hooks and trust the SprintBaton entry once - until you do, its write-mode runs go unguarded and warn on every run.

In tool mode (SPRINTBATON_MODE=tool, the default), both runtime flags default to cli - provided that provider's binary is on PATH - so sprintbaton setup / providers add --bind-roles puts every role on that provider's CLI. No metered API key is needed for any role in that configuration. It does need a valid CLI login on the worker machine; without one, the first run fails with a clear message. If the binary isn't on PATH, the defaults fall back to the API and agent-SDK runtimes instead.

The two decisions are independent, so you can keep the five cheap router-tier roles on the metered API and everything else on a CLI login:

sprintbaton providers add anthropic --classify-harness api \
    --agentic-harness cli --bind-roles

Whether a call uses a subscription depends only on the harness and on the task owner's own credentials - never on who the owner is, and never on a subscription the deployment holds:

  1. a harness that can use a Claude subscription, and an owner who stored one (sprintbaton credentials create --provider anthropic_subscription --token <claude setup-token output>) - runs on that subscription;
  2. otherwise a harness that takes an API key, and an owner (or the deployment fallback) with one - metered;
  3. otherwise, in tool mode only, a CLI harness authenticates from the host's own login (claude login, codex login, gemini auth login).

In hosted mode the defaults put the agentic roles on the provider's agent SDK. For Anthropic that is the Claude Agent SDK, whose claude binary runs inside the sandbox pod. With a stored subscription the run holds only a placeholder token; the worker's egress broker swaps in the real one on the way to Anthropic, so the credential never enters the sandbox. CLAUDE_CODE_OAUTH_TOKEN is a tool-mode convenience only: a hosted worker never lends the operator's subscription to anyone.


Quick start

# 1. First-run configuration, interactively: credentials, model provider,
#    seeded agents, and the role bindings every task action resolves through.
sprintbaton setup

# 2. Onboard a project - the board↔repositories mapping. This also queues
#    the project's first metadata initialization run.
sprintbaton project create -f my-web-app.yaml

# 3. Start the worker: polling job + orchestrator. It picks up the
#    initialization run first (L2-tier, can take a while).
sprintbaton serve

# 4. In another terminal, check on the initialization run.
sprintbaton init --project "My Web App" --status

setup needs a terminal. Everything it does is reachable non-interactively, so CI and provisioning scripts never need it:

sprintbaton credentials create --provider github  --token ghp_...
sprintbaton credentials create --provider todoist --token tok_...
sprintbaton providers add anthropic --bind-roles      # seeds agents + binds every action
sprintbaton project create -f my-web-app.yaml
sprintbaton init --project "My Web App"
sprintbaton serve

providers add ... --bind-roles is required at least once. There is no built-in default wiring: every task action resolves through a binding to a persisted AgentDefinition, and sprintbaton serve prints a warning naming any action that has none.

Label a task sprintbaton-agent on your board and it's picked up on the next poll once initialization has finished.


Command reference

sprintbaton project create|list|delete|metadata-gate

Onboard and manage Project rows - one todolist board and its member repositories (columns, board, release cadence, todolist provider, per-repository branches, credential overrides). sprintbaton repo … is a back-compat alias for the same group.

sprintbaton project create -f my-web-app.yaml        # upsert by metadata.name
sprintbaton project list
sprintbaton project delete "My Web App"              # soft-delete
sprintbaton project metadata-gate "My Web App" --open    # let board tasks run
                                                         # before metadata exists
sprintbaton project metadata-gate "My Web App" --enforce

project create applies a k8s-style manifest:

apiVersion: sprintbaton/v1
kind: Project
metadata:
  name: "My Web App"
spec:
  boardId: "board_abc123"        # omit this and `columns` if createTodolistProject: true
  todolistProvider: todoist
  # createTodolistProject: true    # provisions a template board + sections instead
  releaseCadenceDays: 7            # dev → staging cutover window (weekly)
  columns:                         # provider column/section IDs → semantic states
    icebox: "col_1"                # any subset may be omitted on Todoist - see below
    taskFinalization: "col_2"      # shared by Ambiguous and Abstract tasks
    passingCriteria: "col_2a"      # exhaustive acceptance criteria (machine-only)
    taskFinalized: "col_2b"        # spec classification checkpoint (machine-only)
    planFinalization: "col_3"
    planFinalized: "col_3b"        # plan classification checkpoint (machine-only)
    inProgress: "col_4"
    codeReview: "col_4b"           # pre-PR Review Agent gate (machine-only)
    inReview: "col_5"
    qa: "col_6"
    shipped: "col_7"
    # blocked: "col_8"             # optional; halts fall back to a label without it
  credentials:
    todolist: cred_def456          # falls back to your default credential, then TODOLIST_API_TOKEN
  git:                             # optional commit identity for every member repo
    authorName: "SprintBaton"
    authorEmail: "sprintbaton@users.noreply.github.com"
  generateMetadata: true           # default; false = no automatic runs, tasks never wait for one
  metadataGuidance: |              # optional, ≤ 4000 chars, passed to the metadata agents
    Payments live in services/billing and are the critical surface.
  active: true                     # false pauses new-task intake (in-flight work continues)
  repositories:
    - name: "web-frontend"
      role: "frontend UI"
      remoteUrl: "git@github.com:your-org/web-frontend.git"
      githubRepo: "your-org/web-frontend"
      branches:
        dev: dev                   # integration trunk feature PRs merge into
        staging: staging           # release-window batch under regression QA
        production: main           # live
      credentials:
        github: cred_abc123        # falls back to your default credential, then GITHUB_TOKEN
    - name: "web-backend"
      role: "backend API"
      remoteUrl: "git@github.com:your-org/web-backend.git"
      githubRepo: "your-org/web-backend"

On Todoist, columns is optional: unmapped keys are matched against the board's existing sections by name, and the rest are proposed for creation - nothing is created until you confirm the plan at the prompt. Other providers require the full mapping. Linking a Todoist project also creates the sprintbaton-agent (salmon) and sprintbaton-human (green) routing labels.

A new project, or a newly added repository, queues a metadata initialization run automatically. Changing only metadataGuidance does not - the command prints a hint to re-run sprintbaton init.

sprintbaton init

Queues a metadata initialization run - an L2-tier agentic read of every member repository that produces the .sprintbaton/ insight files the Router, Spec, Planning, and Coding models read from, then the combined project index. sprintbaton serve executes it; init itself never calls a model or clones a repository. Run it again whenever the codebase has changed significantly.

sprintbaton init --project "My Web App"                  # regenerate every repository
sprintbaton init --project "My Web App" --missing-only   # only repositories with no metadata yet
sprintbaton init --project "My Web App" --wait           # block until the run finishes
sprintbaton init --project "My Web App" --status         # latest run, per-repo progress, gate state

init is purely per-project. Provider choice is not an init flag: it is a deployment-wide decision, expressed by sprintbaton setup or providers add <provider> --bind-roles.

Only one run per project can be unfinished at a time. A run that fails permanently shows its error and remedies in --status; brief outages are retried with backoff on their own.

Flags: --verbose/-v (stream reasoning traces), --quiet/-q (warnings only), --json (force structured JSON output).

sprintbaton serve

Starts the polling job, the release-window job, the usage-limit-wake job, the workspace sweeper, and the orchestrator worker loop. This is the long-running process - run it as a daemon, systemd unit, or container. On startup it re-enqueues tasks orphaned by a crash and prints any project whose first metadata initialization failed.

sprintbaton serve
sprintbaton serve --verbose       # live reasoning traces from the harnesses
sprintbaton serve --quiet         # warnings and errors only

Ctrl+C stops all four background jobs cleanly. In hosted mode serve refuses to start unless the sandbox service is configured and reachable (SPRINTBATON_SANDBOX_URL + SPRINTBATON_SANDBOX_TOKEN) - it never falls back to running tenant code in its own process - and it starts the egress/credential broker the sandbox's runs reach the network through.

At startup serve also checks that every harness reachable from a bound action (each fallback-chain entry and each execution tier) can import its packages. A missing one is reported with the action, the agent, the package and the extra that provides it. Tool mode only warns. Hosted mode refuses to start when an action has no usable agent left, since that means the image was built without a package its bindings need - add the extra to the PROVIDER_EXTRAS build argument. A chain that still has a usable entry is only a warning.

sprintbaton serve-api

Starts the hosted-mode CRUD API (fastapi/uvicorn) - a second front door onto the same storage sprintbaton serve acts on, meant to back a future web UI. Requires the api extra (included in [hosted]). CLI/local installs never need this.

sprintbaton serve-api
sprintbaton serve-api --host 0.0.0.0 --port 8080
Route Purpose
POST /accounts, POST /auth/login Sign up / log in; both return a bearer token
GET/POST /agents, DELETE /agents/{id} AgentDefinitions. 422 for an unknown harness or systemPrompt template, or a harness that can't run on this deployment; known harness caveats come back as warnings on the 201
GET/POST /credentials, DELETE /credentials/{id} Encrypted credentials (masked on read)
GET/POST /projects, DELETE /projects/{id} Projects and their member repositories; 409 returns a column plan to confirm
POST/GET /projects/{id}/metadata Queue an initialization run (409 with the unfinished run's taskId) / its status
PUT /projects/{id}/metadata/gate Open or enforce the metadata gate
GET/PATCH /config Per-user configuration overrides, including all 15 agent bindings
GET /reports/usage/timeseries|by-task|by-agent|by-state Token usage reports

Every route is scoped to the caller - another user's ids return 404.

sprintbaton credentials create|list|delete

Encrypted, named third-party tokens a project or repository can reference by id, or that become your default for a provider automatically.

sprintbaton credentials create --provider github --token ghp_...   # first one becomes default
sprintbaton credentials create --provider todoist --token tok_... --no-default
sprintbaton credentials list      # masked - plaintext is never shown
sprintbaton credentials delete <credential_id>

Known providers: github, todoist, linear, anthropic, anthropic_subscription, openai, gemini, open_hands_llm. anthropic_subscription is a Claude subscription token minted by claude setup-token; a harness that can use a subscription prefers it over an API key whenever you have stored one (store no subscription credential if you want metered spend).

sprintbaton (no command)

sprintbaton must be followed by a command. Run on its own it is an error (exit status 2) that says so and lists where to start:

$ sprintbaton
error: missing command - `sprintbaton` must be followed by a command
  likely cause: it was run on its own, with nothing after it
  to fix: run one of these:
    sprintbaton setup    set SprintBaton up (credentials, model provider, role bindings)
    sprintbaton --help   list every command and option

A command group on its own (sprintbaton providers, agents, project, credentials) is the same error, listing that group's commands.

Errors

Every error the CLI prints has the same three parts - what failed, the likely cause, and the command that fixes it:

$ sprintbaton init --project shop
error: unknown project: shop
  likely cause: the project was never onboarded, or the title is misspelled (titles are matched exactly)
  to fix: `sprintbaton project list` shows the onboarded titles; onboard a new one with `sprintbaton project create -f <manifest.yaml>`

That covers startup problems too: an unsupported setting, a database or queue that cannot be reached, and a missing optional package (which names the pip install 'sprintbaton[<extra>]' to run) are reported this way instead of as a traceback. Errors go to stderr and exit with status 1.

sprintbaton setup

The interactive front door for a first run: stores your GitHub and Todoist tokens, picks a model provider (probing PATH for claude, codex and gemini so it can tell you which CLIs you already have), chooses the two runtimes, seeds the agents, and binds every task action to them. It only asks for an API key when some role could actually use one, and on Anthropic's agent-SDK runtime it also offers to store a Claude subscription token.

sprintbaton setup

It opens with the SprintBaton logo typed out as a short animation, then a welcome and an outline of the steps. Set SPRINTBATON_NO_ANIMATION=true for a still logo; a terminal without 24-bit colour, narrower than 79 columns, or with NO_COLOR set gets plain text art instead. sprintbaton serve shows the same logo as a still frame in tool mode (never with --quiet, --json, or when output is not a terminal).

It is a thin wrapper - it calls exactly what credentials create and providers add --bind-roles call, and owns nothing of its own. It needs a terminal and refuses rather than prompting when stdin is a pipe; the scripted equivalent is in Quick start. Re-running is safe: every step it performs is create-if-absent. To change an existing provider's wiring, use providers update.

sprintbaton providers add|update|list|delete|set-limit

Register a model provider: installs the optional pip extra of each selected runtime's harness, warns if its CLI binary is missing, optionally captures a credential, seeds one AgentDefinition per (runtime x model tier), and with --bind-roles binds every task action - including the per-tier execution map - to them.

sprintbaton providers add anthropic --bind-roles              # the mode defaults:
                                                             # tool -> cli/cli,
                                                             # hosted -> api/agent_sdk
sprintbaton providers add openai --token sk-... --bind-roles
sprintbaton providers add google --classify-harness api --agentic-harness cli
sprintbaton providers add anthropic --base-url https://gateway.example.com   # an Anthropic-compatible gateway
sprintbaton providers list
sprintbaton providers set-limit openai --max-tokens 2000000 --window-seconds 3600
sprintbaton providers delete openai

Two independent decisions, along the seam the pipeline already has:

Flag Values Covers
--classify-harness default, api, cli, agent_sdk the five cheap router-tier roles (classification, spec/plan classification, repo scoping, revision classification)
--agentic-harness default, cli, agent_sdk the other nine

default resolves per mode: cli for both in a CLI install, api + agent_sdk in hosted mode - tool mode assumes you have a CLI login, hosted mode assumes you have an API key. In tool mode the cli default also needs the provider's binary on PATH; without it you get api + agent_sdk. Selecting cli explicitly in hosted mode warns, since the CLI harnesses can't run there. There is deliberately no api agentic runtime: SprintBaton has no agentic-API harness beyond Anthropic's legacy raw_tool_loop, which stays reachable through SPRINTBATON_CODING_HARNESS.

Each invocation seeds four rows - <provider>-<runtime>-router, -code, -plan and -escalate - at that provider's router / coding / planning / escalation models. Nothing is ever deleted, so a second invocation with a different runtime adds candidates alongside the first (openai-cli-code and openai-sdk-code can both exist, and either can be bound). When SPRINTBATON_CODING_HARNESS names a harness other than the chosen agentic runtime's, three extra <provider>-exec-* rows are seeded on it and the write-capable roles (execution, conflict resolution) bind to those instead - which is how the read-only advisory roles and the coding roles keep different harnesses.

--base-url (manifest spec.baseUrl, https:// only) points a provider at an endpoint other than its default - for example an Anthropic-compatible gateway in front of a non-Claude model. A claude_agent_sdk agent on such a provider works but carries an advisory (Anthropic does not support non-Claude models behind Claude Code); it is surfaced at agents create, POST /agents and on every run, never blocking.

providers add is create-if-absent throughout, including the provider row, so re-running it is a no-op. providers update is the deliberate mutation, and --bind-roles switches which job it does rather than adding a second one:

sprintbaton providers update openai                # re-seed row contents from
                                                   # current Settings; bindings untouched
sprintbaton providers update openai --bind-roles   # bind the roles to the existing
                                                   # rows; row contents untouched

One axis of change per invocation, so a behavior change is always attributable. Note the consequence: a model id moved in SPRINTBATON_OPENAI_CODING_MODEL (or any tier setting) reaches tasks on the next providers update, not on the next task - seeded rows are data, not a live view of your environment.

set-limit attaches a self-imposed rolling-window token budget - the ceiling the fallback router honors when walking an agent chain (add --replace to swap out the existing limits instead of adding one). providers list shows every provider with the install state of each of its harnesses.

A few more knobs on providers add: --token stores an API key as you register, -f provider.yaml takes the same settings from a manifest, --no-seed-agents registers the provider without creating any agents, and --skip-install leaves pip alone.

sprintbaton agents create|list|delete|harnesses

Manage AgentDefinitions directly - named (harness, provider, model) recipes, optionally with a system preamble. This is the deliberate override; the normal way to wire a whole provider is providers add --bind-roles above.

sprintbaton agents harnesses                       # list registered harness names
sprintbaton agents create --name sonnet-coder \
    --harness raw_tool_loop --provider anthropic --model claude-sonnet-5
sprintbaton agents create --name strict-sonnet \
    --harness claude_agent_sdk --model claude-sonnet-5 \
    --system-prompt strict-json                    # a template you add, see below
sprintbaton agents list
sprintbaton agents delete sonnet-coder

--system-prompt names a template that is prepended before whichever action template runs - model-specific standing instructions ("stricter JSON discipline", "prefer minimal diffs"), reusable across every action the agent is bound to. No preambles ship with SprintBaton: you add your own as a Markdown file next to the built-in templates in sprintbaton/prompts/templates/ (so strict-json above means strict-json.md). It is used exactly as written and never replaces the action's own template, so it cannot drift out of step with that action's output schema. An unknown template name is rejected here, not once per task at run time.

agents harnesses lists every harness you can name and marks the ones with known upstream caveats with ⚠; agents create prints those caveats when you pick one. --provider defaults to anthropic and must name a built-in provider (anthropic, openai, google, open_hands_llm) or one you registered with providers add.

Bind an agent to an action with SPRINTBATON_<ACTION>_AGENT (a single name, or a comma-separated fallback chain).

sprintbaton reports [timeseries|by-task|by-agent|by-state]

Token usage reports over the TaskActionEvent stream - metadata initialization runs included. No subcommand means by-task over the last 7 days.

sprintbaton reports                                    # by-task, last 7 days
sprintbaton reports by-task --repo "web-backend" --days 30
sprintbaton reports by-task --task task_abc123
sprintbaton reports by-agent --since 2026-07-01 --until 2026-07-19
sprintbaton reports by-state --days 14
sprintbaton reports timeseries --bucket week --days 90

Every subcommand accepts --days, --since/--until (ISO dates, override --days), and --repo (restrict to one repository by title); by-task also takes --task. --days defaults to SPRINTBATON_REPORT_DEFAULT_DAYS (7).


Global flags

init and serve both accept:

Flag Effect
--verbose / -v Floor logging at DEBUG and stream live reasoning traces from the harnesses
--quiet / -q Floor logging at WARNING - suppress the normal key-transition log lines
--json Force structured JSON log output even in an interactive tool-mode TTY

Without any of these, verbosity follows SPRINTBATON_LOG_VERBOSITY (normal by default) and tool-mode interactive runs get a colorized, human-readable console renderer instead of raw JSON lines.


Install modes

CLI / local (default) Hosted
Install pip install "sprintbaton[anthropic,claude-agent-sdk]" (or bare, for a CLI-only setup) the published image, or pip install "sprintbaton[hosted,anthropic,claude-agent-sdk]"
SPRINTBATON_MODE tool hosted
Entity storage SQLite (~/.sprintbaton/sprintbaton.db) MongoDB (default) or PostgreSQL
Blob storage filesystem (~/.sprintbaton/blobs/) S3 / any S3-compatible store (default), Google Cloud Storage, or Azure Blob Storage
Queue in-process Redis
Lock flock Redis
Users one, fixed (local) many, each with their own projects, agents, and credentials
Default runtimes your provider's CLI (claude / codex / gemini) when it's on PATH, otherwise its API + agent SDK API for the router roles, agent SDK for the rest (Anthropic's coding roles on raw_tool_loop, per SPRINTBATON_CODING_HARNESS)
Where tools and tests run your machine, in the task's clone the sandbox pod, one bubblewrap sandbox per run
serve-api optional (api extra) included in the image
External infra required none a database, Redis, an object store, the sandbox service

A hosted install is three workloads: the worker (sprintbaton serve - the orchestrator, git, model API calls and the egress/credential broker), the sandbox service (Dockerfile.sandbox, python -m sprintbaton.sandbox.server) and the optional API. The worker holds the deployment's secrets and runs only trusted code; every command a model or a repository drives - tool calls, test and build scripts, the claude binary itself - runs in the sandbox pod, which holds no secret but the worker<->sandbox token and shares no volume with the worker (files move through an explicit transfer; changes come back as a validated change set the worker applies to its own clone). Runs have no network except package registries and the model API, both through the worker's broker. The cluster must run a NetworkPolicy-enforcing CNI, and the sandbox nodes must either allow unprivileged user namespaces or grant the sandbox container CAP_SYS_ADMIN.

Hosted storage is chosen by configuration: SPRINTBATON_PERSISTENCE_BACKEND (mongo or postgres) and SPRINTBATON_BLOB_BACKEND (s3, gcs or azure), each with its own settings in the configuration reference below. Redis is the only hosted queue and lock. A deployment's blob backend is fixed once it holds data - stored artifact URLs name their vendor (s3://, gs://, azure://) and there is no migration tooling.

You don't have to build anything to run hosted mode - each release publishes both images (ghcr.io/arunkarthik11/sprintbaton and sprintbaton-sandbox, linux/amd64 + arm64, built with the same SDK) and two ready-made setups:

  • Docker Compose (deploy/compose/) - one host, batteries included: the three workloads plus Redis and, by default, MongoDB and MinIO (PostgreSQL is a compose profile away). Run ./init-env.sh, add your keys to .env, then docker compose up -d.
  • Helm (deploy/helm/sprintbaton/, also at oci://ghcr.io/arunkarthik11/charts/sprintbaton) - pick persistence.backend and blob.backend, then point them at your own stores (inline values or existing Secrets) or the bundled single-replica ones (values-bundled.yaml). values-aws.yaml, values-gcp.yaml and values-azure.yaml are cloud presets using workload identity for the object store.

Each directory's README covers the host/cluster requirements for the sandbox.

The published image contains every storage client (so any backend combination is a configuration change) and the Anthropic provider packages only. It is defined by two build arguments of the same Dockerfile: STORAGE_EXTRAS (default mongo,postgres,s3,gcs,azure,redis,api,otlp) and PROVIDER_EXTRAS (default anthropic,claude-agent-sdk). To use another provider's SDK, build your own worker and sandbox images together from docker-bake.hcl:

PROVIDER_EXTRAS=openai-agents,openai REGISTRY=registry.example.com/me TAG=custom \
  docker buildx bake --push

Both images must come from one build - the sandbox's claude binary has to match the worker's claude-agent-sdk, and a build without claude-agent-sdk omits it from the sandbox entirely. Custom builds aren't built or tested by the project's CI. sprintbaton serve checks at startup that every bound agent's harness can import its packages; in hosted mode it refuses to start (naming the extra to add to PROVIDER_EXTRAS) when an action has no usable agent left.

Deleting ~/.sprintbaton/ (and your WORKSPACE_ROOT, if you moved it out of there) is a clean reset of a local install. Per-concern backends can also be overridden individually (SPRINTBATON_PERSISTENCE_BACKEND, SPRINTBATON_BLOB_BACKEND, SPRINTBATON_QUEUE_BACKEND, SPRINTBATON_LOCK_BACKEND) regardless of the overall mode.


Development

python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest

[dev] pulls in [hosted] and both Anthropic extras, because the suite exercises every storage driver, the serve-api control-plane API, and the Anthropic harnesses. Tests that need real services (Mongo, PostgreSQL, Redis, MinIO/S3, GCS, Azure, a live Todoist account, or a real harness run) are skipped unless SPRINTBATON_CONFORMANCE_LIVE=1 is set - plus POSTGRES_DSN for PostgreSQL, GCS_BUCKET for GCS, and the AZURE_* settings for Azure. The cloud blob drivers also run against in-memory fakes of their client libraries on every test run.

The test suite lives in the development repository and isn't part of the public release, so a checkout of the public repo has no tests/ to run.


Configuration reference

Providers, models, and credentials

Variable Required Default Description
ANTHROPIC_API_KEY no* - Anthropic API key. *Needed only by metered harnesses when no per-user Anthropic Credential exists; it is the deployment-wide fallback (tier 3 of the credential chain). A claude login or a stored subscription covers the CLI and agent-SDK roles without it
CLAUDE_CODE_OAUTH_TOKEN no - Tool mode only: a Claude subscription token (claude setup-token) used when the owner has stored none of their own. Ignored in hosted mode - each hosted user stores their own anthropic_subscription credential
SPRINTBATON_LOCAL_LOGIN_SESSIONS no (mode-derived) May harnesses authenticate from a login that lives only on this machine's filesystem (claude login, codex login, gemini auth login)? Unset derives from SPRINTBATON_MODE; always false in hosted mode
OPENAI_API_KEY no - Deployment-wide OpenAI fallback key (per-user Credential/provider wins); used by openai_single_shot and openai_agent_sdk, never passed to codex_cli
GEMINI_API_KEY no - Deployment-wide Google fallback key (same three-tier resolution); used by gemini_single_shot and gemini_agent_sdk, never passed to gemini_cli
SPRINTBATON_DEFAULT_PROVIDER no anthropic Which provider sprintbaton setup offers first and a bare providers update targets (anthropic/openai/google). Not a resolution input - every action resolves through an explicit binding. Written by providers add --bind-roles
SPRINTBATON_OPUS_MODEL no claude-opus-4-8 Anthropic L2 (high-tier reasoning/planning) model
SPRINTBATON_SONNET_MODEL no claude-sonnet-5 Anthropic L1 (default coding/execution) model
SPRINTBATON_ROUTER_MODEL no claude-haiku-4-5 Anthropic cheap classifier for routing
SPRINTBATON_FABLE_MODEL no claude-fable-5 The E4 escalation tier's model (reached only by escalation from E3); also the Conflict Resolution Agent's default
SPRINTBATON_OPENAI_ROUTER_MODEL / _CODING_MODEL / _PLANNING_MODEL / _ESCALATION_MODEL no gpt-5-mini / gpt-5-codex / gpt-5.1 / gpt-5.1-pro Tier models providers add|update openai seeds its rows with (placeholders - verify the catalog before pinning)
SPRINTBATON_GEMINI_ROUTER_MODEL / _CODING_MODEL / _PLANNING_MODEL / _ESCALATION_MODEL no gemini-3-flash / gemini-3-pro / gemini-3-pro / gemini-3-pro Same, for google (placeholders)
SPRINTBATON_CODING_HARNESS no raw_tool_loop The harness the write-capable roles (execution, conflict resolution) are seeded onto when it differs from the chosen agent_sdk runtime's - via the extra <provider>-exec-* rows. Ignored for the cli runtime. Read at seed time
SPRINTBATON_OPENAI_CODING_HARNESS / SPRINTBATON_GEMINI_CODING_HARNESS no "" Same, per provider; empty → no extra rows, so the write-capable roles share the agentic runtime's harness
OPEN_HANDS_LLM_API_KEY / OPEN_HANDS_LLM_BASE_URL no - LLM credentials forwarded to the OpenHands harness runtime

Todolist and GitHub

Variable Required Default Description
TODOLIST_API_TOKEN no - Deployment-wide fallback provider token (a project-level or user-default Credential wins; the provider itself is per-project - Project.todolistProvider)
SPRINTBATON_AGENT_USER_ID no sprintbaton-agent The agent's own identity, used to tell its comments apart from yours (routing itself is label-based)
GITHUB_TOKEN no - Deployment-wide fallback GitHub token (same three-tier resolution); reaches git through GIT_ASKPASS, never the remote URL
SPRINTBATON_GIT_AUTHOR_NAME / SPRINTBATON_GIT_AUTHOR_EMAIL no "" Commit identity when neither the repository nor the project sets one; empty falls back to SprintBaton / sprintbaton@users.noreply.github.com
SPRINTBATON_GIT_SSH_KEY_PATH no "" Private key for SSH remotes; unset uses the host's SSH agent/config

Agent wiring - each names an AgentDefinition (or an ordered, comma-separated fallback chain). There is no built-in default behind these: empty means the action cannot resolve, and sprintbaton serve warns at startup naming every unbound action. sprintbaton providers add <provider> --bind-roles fills all 15 in at once, writing the per-user UserConfiguration twins of these fields (also editable via PATCH /config); an env var set here overrides that, per action.

Variable Required Default Description
SPRINTBATON_CLASSIFICATION_AGENT no "" Router / classification action
SPRINTBATON_FINALIZATION_AGENT / SPRINTBATON_ABSTRACT_FINALIZATION_AGENT no "" Ambiguous / Abstract spec-finalization agents
SPRINTBATON_PASSING_CRITERIA_AGENT no "" Passing-criteria checkpoint
SPRINTBATON_SPEC_CLASSIFICATION_AGENT / SPRINTBATON_PLAN_CLASSIFICATION_AGENT no "" The two implementability-classification checkpoints
SPRINTBATON_REPO_SCOPING_AGENT no "" Picks which member repositories a task touches (skipped for single-repository projects)
SPRINTBATON_REVISION_CLASSIFICATION_AGENT no "" Decides how far back an edit to a card's title or description sends the task (the rewind point)
SPRINTBATON_PLANNING_AGENT no "" Planning action
SPRINTBATON_REVIEW_AGENT no "" Pre-PR Review Agent (the AI review loop)
SPRINTBATON_EXECUTION_TIER_AGENTS no "" Per-escalation-tier execution agents: <tier>[,<tier>...]:<AgentDefinition name> groups, ;-separated (e.g. E0,E1,E2:sonnet-coder;E3:opus-coder;E4:fable-coder), each group value optionally a +-separated chain. Tiers left out fall through to SPRINTBATON_EXECUTION_AGENT. EH (human handoff) is rejected. --bind-roles writes the L1(E0-E2)/L2(E3)/escalation(E4) split here
SPRINTBATON_EXECUTION_AGENT no "" Uniform execution AgentDefinition - applied to any tier the tier map above doesn't cover, and what the plain execution action resolves through
SPRINTBATON_CONFLICT_RESOLUTION_AGENT no "" The Conflict Resolution Agent (--bind-roles binds it to the escalation-tier row - the coding harness on SPRINTBATON_FABLE_MODEL)
SPRINTBATON_METADATA_GENERATION_AGENT / SPRINTBATON_PROJECT_METADATA_GENERATION_AGENT no "" The per-repository and project-index initialization passes (--bind-roles binds them to the agentic planning-tier row). The harness must be able to confine writes to the metadata directory - claude_agent_sdk, claude_code_cli, codex_cli, gemini_cli, openai_agent_sdk, or gemini_agent_sdk - or resolution fails loudly

Metadata initialization

Variable Required Default Description
SPRINTBATON_METADATA_GENERATION_MAX_TURNS no 150 Agent turn cap per initialization run (a runaway/cost guard)
SPRINTBATON_METADATA_GENERATION_MAX_CONTINUATIONS no 3 Continuations per pass after a run is cut off by a turn or time limit
SPRINTBATON_METADATA_VALIDATION_MAX_ROUNDS no 2 Times a pass is sent back to fix structure violations before it fails
SPRINTBATON_INIT_RETRY_MAX_ATTEMPTS no 6 Consecutive transient failures (model API, git remote, blob store unreachable) before a run fails
SPRINTBATON_INIT_RETRY_BASE_SECONDS / SPRINTBATON_INIT_RETRY_MAX_BACKOFF_SECONDS no 60 / 1800 Retry backoff: doubles per attempt, capped, with ±10% jitter

Polling, releases, and escalation

Variable Required Default Description
POLL_INTERVAL_SECONDS no 60 Task polling cadence (also how often initialization runs are discovered)
RELEASE_CHECK_INTERVAL_SECONDS no 3600 How often the release-window job checks for a due dev → staging cutover (the cadence itself is per-project: Project.releaseCadenceDays)
ESCALATION_REGION_EDIT_LIMIT no 3 Thrashing trigger (N)
ESCALATION_FIX_ATTEMPTS no 2 Correctness-stall trigger per tier (M)
ESCALATION_NOPROGRESS_WINDOW no 4 No-progress step window (K)
ESCALATION_MAX_CLARIFY_ROUNDS no 3 Clarification loop cap
ESCALATION_GLOBAL_TOKEN_CAP no 2000000 Circuit breaker before forced human handoff
ESCALATION_TIER_TOKEN_BUDGET no 400000 Per-tier token budget
ESCALATION_TIER_WALL_CLOCK_SECONDS no 1800 Per-tier wall-clock budget
ESCALATION_AI_REVIEW_ROUNDS no 2 Coding ↔ Review-Agent bounces per tier before escalating
ESCALATION_HUMAN_REVIEW_ROUNDS no 3 Human PR-comment bounces per tier before escalating
ESCALATION_CONFLICT_RESOLUTION_ROUNDS no 2 PR-open-time merge-conflict resolution attempts before parking to Blocked

Conversations, usage limits, reporting, and provenance

Variable Required Default Description
CONVERSATION_RESUME_WINDOW_SECONDS no 300 How long a clarification-paused harness session stays resumable; later replies restart with prior context
SPRINTBATON_USAGE_LIMIT_AWARE no true Kill switch: when a harness reports a usage/rate limit, walk the fallback chain or pause the task until reset instead of failing the turn
SPRINTBATON_USAGE_LIMIT_URGENT_LABEL no urgent A task label that opts a task out of the pause (the call proceeds/fails on its own; grants no extra quota)
SPRINTBATON_USAGE_LIMIT_DEFAULT_BACKOFF_SECONDS no 1800 Fallback retry backoff when a usage-limit signal carries no reset time
SPRINTBATON_USAGE_LIMIT_WAKE_CHECK_INTERVAL_SECONDS no 300 UsageLimitWakeJob poll cadence - how often paused tasks are re-checked for resume
SPRINTBATON_REPORT_DEFAULT_DAYS no 7 Default lookback window for sprintbaton reports and /reports/usage/*
SPRINTBATON_PROVENANCE_ENABLED no true Classification-provenance kill switch: disables all context snapshotting and ClassificationRecord writes
SPRINTBATON_PROVENANCE_EXCLUDE_FROM_USER_REPO no true Keep .sprintbaton/ out of SprintBaton's commits to your own repo (the provenance store captures context regardless)

Storage, mode, and multi-tenancy

Variable Required Default Description
SPRINTBATON_MODE no tool Deployment mode: tool (SQLite + filesystem blobs + in-process queue + flock - zero external infra) | hosted (Mongo + S3/MinIO + Redis). Sets the default for every per-concern backend var below; any of them set explicitly overrides it individually
SPRINTBATON_PERSISTENCE_BACKEND no (mode-derived) Entity persistence: sqlite | mongo | postgres. Unset derives from SPRINTBATON_MODE (tool→sqlite, hosted→mongo). Storage only - it doesn't decide anything about auth or seeding. An unknown name fails startup
SPRINTBATON_BLOB_BACKEND no (mode-derived) Blob storage: filesystem | s3 | gcs | azure. Unset derives from SPRINTBATON_MODE (tool→filesystem, hosted→s3). Fixed once a deployment holds data: stored URLs name the vendor
SPRINTBATON_QUEUE_BACKEND no (mode-derived) Task queue: in_process | redis. Crash recovery is the startup reconciliation pass over the entity store, not queue durability
SPRINTBATON_LOCK_BACKEND no (mode-derived) Distributed lock: file (flock, cross-process) | redis
SPRINTBATON_LOCAL_STORAGE_ROOT no ~/.sprintbaton Tool-mode home directory - owns the .db file, blobs/, and locks/
SPRINTBATON_SQLITE_PATH no {root}/sprintbaton.db The SQLite backend's .db file (~ expanded); unset derives from SPRINTBATON_LOCAL_STORAGE_ROOT
SPRINTBATON_BLOB_ROOT no {root}/blobs The filesystem blob store's root; unset derives from SPRINTBATON_LOCAL_STORAGE_ROOT
SPRINTBATON_RECONCILE_STALE_AFTER_SECONDS no 3600 Crash recovery: how old an in-flight claim (Task.processingClaimedAt, initialization runs included) must be before startup reconciliation re-enqueues it and init --status reports it stale. Default = 2 × the per-tier wall-clock budget
SPRINTBATON_SANDBOX_BACKEND no (mode-derived) Where model- and repo-driven commands run: none (tool mode: the clone on this machine) | remote (hosted: the sandbox service). Hosted mode fails closed - none there makes serve refuse to start
SPRINTBATON_SANDBOX_URL hosted - The sandbox service address, e.g. http://sprintbaton-sandbox:8090
SPRINTBATON_SANDBOX_TOKEN hosted - Shared secret between the worker and the sandbox pod (the sandbox reads it from SPRINTBATON_SANDBOX_TOKEN_FILE)
SPRINTBATON_SANDBOX_BROKER_PORT no 8081 Port of the worker's egress/credential broker; the sandbox pod's runs reach the network only through it
SPRINTBATON_SANDBOX_EGRESS_ALLOWLIST no public package registries Comma-separated hosts a run may CONNECT to (*.suffix matches subdomains)
SPRINTBATON_SANDBOX_GIT_DEPTH no 50 Commits of history shipped into a session's disposable sandbox-side .git
SPRINTBATON_SANDBOX_MAX_CHANGESET_BYTES no 104857600 Upper bound on the changes collected back from one write-capable run
SPRINTBATON_SANDBOX_SESSION_TTL_SECONDS no 86400 Sandbox pod only: idle sessions are evicted after this
SPRINTBATON_SANDBOX_MAX_SESSIONS no 4 Sandbox pod only: concurrent sessions per pod
SPRINTBATON_USER_ID no local Tenant identity CLI commands operate as. The worker's polling/release loops span every tenant's active projects and ignore it
SPRINTBATON_CONFIG_CACHE_BACKEND no memory UserService settings-resolution cache: memory | redis
SPRINTBATON_CONFIG_CACHE_TTL_SECONDS no 60 TTL for the cache above; writes via the API invalidate immediately
SPRINTBATON_VAULT_BACKEND no local Credential envelope-encryption vault: local (master-key file) | hashicorp (HashiCorp Vault KV)
SPRINTBATON_MASTER_KEY_PATH no ~/.sprintbaton/master.key local vault only: where the auto-generated master key lives (chmod 600)
VAULT_ADDR / VAULT_TOKEN / VAULT_NAMESPACE / VAULT_SKIP_VERIFY no - / - / - / false hashicorp vault only: standard HashiCorp connection env vars
SPRINTBATON_VAULT_KEY_PATH / _KEY_FIELD / _KV_VERSION no secrets/sprintbaton/masterKey / value / 2 hashicorp vault only: the KV secret path, field, and engine version holding the master key
SPRINTBATON_SESSION_TTL_SECONDS no 604800 Bearer-token lifetime for the CRUD API
SPRINTBATON_API_HOST / SPRINTBATON_API_PORT no 127.0.0.1 / 8080 sprintbaton serve-api bind address
WORKSPACE_ROOT no ~/.sprintbaton/workspaces Root directory under which git clones are created (users/<userId>/projects/<projectId>/tasks/… per task, …/init/… for initialization runs) - the same shape as the blob keys. Unset derives from SPRINTBATON_LOCAL_STORAGE_ROOT, so a local install keeps everything under one directory
SPRINTBATON_MIRROR_ROOT no <WORKSPACE_ROOT>/.mirrors Where each repository's local bare mirror lives (one per user and project). Every clone is seeded from it and only the delta is fetched from the remote. Keep it on the same filesystem as WORKSPACE_ROOT so clones can hardlink its objects
SPRINTBATON_WORKSPACE_SWEEP_INTERVAL_SECONDS no 3600 How often the sweeper removes directories of shipped tasks and of deleted projects and repositories. 0 disables the periodic pass; the startup pass always runs

Several non-secret settings can also be overridden per user through PATCH /config: the Opus/Sonnet/router model names, the coding harness, all 15 agent bindings, provenance, the polling/release/resume cadences, the escalation tunables (except the conflict-resolution rounds), and the metadata initialization tunables (except the backoff cap).

Observability and logging

Variable Required Default Description
OTEL_EXPORTER_OTLP_ENDPOINT no - Telemetry export target (OTLP gRPC); unset keeps JSON stdout logs only, no exporter import
OTEL_SERVICE_NAME no sprintbaton Service name stamped on OTel logs/metrics/traces
OTEL_RESOURCE_ATTRIBUTES no - Extra OTel resource attributes (standard key=value,... form)
LOG_LEVEL no info Base log level; authoritative only at normal verbosity
SPRINTBATON_LOG_VERBOSITY no normal Log verbosity: silent (WARNING+ only) | normal (key transitions + existing INFO lines) | verbose (adds live reasoning traces). --verbose/-v and --quiet/-q on serve/init override it for one invocation; the console-vs-JSON output surface is the orthogonal axis derived from SPRINTBATON_MODE (+ TTY check, --json override)
SPRINTBATON_NO_ANIMATION no false true makes sprintbaton setup print its logo banner as a still frame instead of typing it out. The banner itself needs a terminal with 24-bit colour at least 79 columns wide; otherwise (or with NO_COLOR set) plain text art is shown, and nothing at all when output is not a terminal

Hosted infrastructure - only consulted in hosted mode (or when the matching per-concern backend is selected explicitly).

Variable Required Default Description
MONGO_BASE_URI no mongodb://localhost:27017 MongoDB connection URI (mongo persistence)
MONGO_DATABASE no sprintbaton MongoDB database name
POSTGRES_DSN postgres - PostgreSQL libpq DSN/URI (postgres persistence). TLS and other options ride it as libpq parameters, e.g. ?sslmode=verify-full
POSTGRES_SCHEMA no - Schema for SprintBaton's tables (created if missing); empty = the connection's search_path
POSTGRES_POOL_MAX_SIZE no 10 Connections per process in the PostgreSQL pool
REDIS_URI no redis://localhost:6379/0 Redis connection URI (queue/lock/cache). The queue, lock and config cache share one client per process
S3_ENDPOINT no - s3 blobs: empty = AWS's own endpoint for the region; set it for MinIO or any other S3-compatible store
S3_REGION no - s3 blobs: the bucket's region (needed on AWS outside us-east-1)
S3_BUCKET no sprintbaton s3 blobs: the bucket
S3_ACCESS_KEY / S3_SECRET_KEY no - s3 blobs: static keys, set together. Both empty = the default AWS credential chain (env, shared config, IRSA/web identity, instance role)
S3_CREATE_BUCKET no false s3 blobs: create a missing bucket at startup (with the region constraint AWS needs). Off, a missing bucket fails startup naming it
S3_ADDRESSING_STYLE no - s3 blobs: path | virtual | auto. MinIO and some gateways need path
GCS_BUCKET gcs - gcs blobs: the bucket (must exist). Authenticates with application-default credentials - workload identity on GKE, or GOOGLE_APPLICATION_CREDENTIALS
GCS_PROJECT no - gcs blobs: the project; empty = the credentials' default
AZURE_STORAGE_ACCOUNT azure - azure blobs: the storage account (may come from the connection string instead)
AZURE_CONTAINER azure - azure blobs: the container (must exist)
AZURE_STORAGE_CONNECTION_STRING no - azure blobs: authenticate with a connection string (non-identity installs, Azurite). Empty = DefaultAzureCredential (managed / workload identity, environment)
AZURE_ACCOUNT_URL no https://<account>.blob.core.windows.net azure blobs: override the endpoint (sovereign clouds, private endpoints). Never appears in stored azure:// URLs

Default model identifiers reflect the currently available models and can be overridden; verify the latest at the provider's docs before pinning. Each provider's model vars are read when its agents are seeded - the SPRINTBATON_OPENAI_* / SPRINTBATON_GEMINI_* ones by providers add openai / google, the Anthropic ones by providers add anthropic - so after changing one, run sprintbaton providers update <provider> to pass it on.


Observability

SprintBaton emits OpenTelemetry logs and metrics through the Observer (a trace pipeline is set up too, and MongoDB calls are traced when that backend is in use). The metrics are the ones that tell you whether routing is working:

  • tasks processed, by Router category,
  • escalations, by category, trigger, and the tiers moved from and to (the Router's calibration signal),
  • human handoffs and hard-trigger halts (irreversible operations should be near zero),
  • tokens used, by role and model.

Point OTEL_EXPORTER_OTLP_ENDPOINT at your collector to export to a remote dashboard; the OTLP exporter comes with the otlp extra. Every task action - initialization passes included - also records a TaskActionEvent with the tokens it consumed, how long it took, and the prompt version and model that produced it. That long-term record is what sprintbaton reports and /reports/usage/* aggregate.

Log identifiers and verbosity: every log line written while the worker handles a task is tagged with request_id/task_id/user_id/repo_id/action, so you can follow one task through the logs, plus a process_role field (worker/api/cli) telling the hosted deployables apart. Verbosity is three-level (silent/normal/verbose - SPRINTBATON_LOG_VERBOSITY, or --verbose/--quiet on sprintbaton serve/init): normal shows the key-transition taxonomy (task pickup, agent assignment, status/tier transitions, clarification pauses, PR/ship/block outcomes, and initialization queued/retried/continued/published/failed); verbose streams the harnesses' reasoning traces (thinking/text/tool calls/tool results) live via the dedicated sprintbaton.trace logger. Tool-mode interactive runs get a human-readable colorized console renderer; hosted mode (or --json, or a redirected stdout) always emits JSON. silent never disables OTel metrics - logging output only.

Contributing

Contributions are welcome. Please open an issue to discuss substantial changes before submitting a PR.

License

GNU General Public License v3.0 - see LICENSE.

Metadata

Release files for sprintbaton 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sprintbaton 0.2.3
File Size Uploaded Provenance
sprintbaton-0.2.3.tar.gz 492.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for sprintbaton 0.2.3
File Interpreter ABI Platform
sprintbaton-0.2.3-py3-none-any.whl Python 3 none any Details

Total release size: 1.0 MB

Release files / sprintbaton-0.2.3.tar.gz

Download URL sprintbaton-0.2.3.tar.gz
Size 492.6 kB
Tags Source
SHA-256 checksum
How to use checksums
53e7625fc79f2b97c27033923dd1069a45076842299d1fdf8a6e303b4d1adf84
BLAKE2b-256 checksum
How to use checksums
60497a75161e19bfef4c3950b560be2c8c6029d42a314e3cc0c26be8277172ea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.7

Release files / sprintbaton-0.2.3-py3-none-any.whl

Download URL sprintbaton-0.2.3-py3-none-any.whl
Size 547.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ab669b963d1ea7814808af1bfd3118d4bf63e1853da0b5aecc41ff67278b3c83
BLAKE2b-256 checksum
How to use checksums
60465790e7ae34ec928da57c7dacb6cf87a5f2e3cb04c83381588338e7394b33
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.7

Security for sprintbaton 0.2.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.2.3 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page