Skip to main content
E

agent-control-tower (dsh-tower)

evolution301/agent-control-tower/packages/dsh-tower

Control tower tools: dispatch a task to a role agent, read its report, verify and accept or send it back. Requires the separate agent-control-tower Python CLI (tower; it is not on PyPI — install it with pip install -e from the same repo) on the DSH process's PATH plus a tower init-ed workspace; without it every tool call fails with ENOENT.

Install

dsh plugin --profile web add github:evolution301/agent-control-tower

This plugin lives in the packages/dsh-tower subdirectory of the repository.

README

agent-control-tower

One agent to talk to. Many agents doing the work. Evidence before "done".

A local control tower for role-based AI agent teams — dispatch, report, verify, accept or rework. DSH-first, adapter-portable, no SaaS, no database, no daemon.

English 简体中文

Python License Tests Dependencies DeepSeek Harness

architecture

The problem

You can already get one agent to write code, one to write copy, one to review. What breaks is the handoff between them — in five predictable ways:

What actually happensWhat it costs you
Self-grading — the agent that did the work also reports "done, tests pass". It never ran them.You ship a bug and find it in production.
Context rot — a long session compacts its history, and the deliverable was "in the conversation".Re-doing work, contradicting earlier decisions, circular rounds.
Silent collisions — two agents edit the same file in the same round; both report success.Lost work, regressions nobody can explain.
No audit trail — which role changed what, on which instruction?You cannot review, reproduce, or hand the project over.
Ping-pong — "fix it" → "fixed" → "still broken". Nothing tracks rounds or forces escalation.Hours burned; you become the message bus.

agent-control-tower is the missing middle layer.

The loop

demo

Real output: tower demo → tower dispatch → tower accept → tower status / tower log

DispatchVerify & accept
dispatchaccept
Audit trail — just files
status
tower dispatch frontend "Rework the home layout. Deliver before/after screenshots at 1440 and 390."
tower inbox                      # what came back
tower accept T-0001              # files exist? checks carry results? verify_commands pass?
tower rework T-0001 --note "Checks list no results; re-run the suite"

Who it is for

Good fit

  • You already run 2–8 agent sessions (DSH, Claude Code, Codex, Cursor, Aider, plain API) and you are the one copy-pasting between them.
  • Indie devs / small teams building something multi-part: an app (backend + frontend + QA), a content site (research → copy → design → SEO), a data pipeline.
  • Agencies / freelancers who must show a client what was done and how it was verified — the ledger and reports are that answer.
  • Anyone burned by an agent saying "done" when it was not.
  • CI / automation that wants reproducible agent work: tower demo runs the loop offline in under a second.

Not a good fit

  • One-shot tasks ("rename this variable") — a single prompt is faster.
  • You want agents to talk to each other freely — this design is deliberately hub-and-spoke.
  • You want a hosted dashboard / multi-tenant service — this is a local CLI with adapters.

What you get

Without the towerWith the tower
You paste briefs between windowstower dispatch <role> "<brief>" — contract and write scope included
"Done ✅", no evidenceReports must list Files (must exist) and Checks (must carry a result); verify_commands run for real
Deliverables live in chatDeliverables in roles/<role>/, reports in <task>-REPORT.md
Nobody knows who changed whatAppend-only .tower/ledger.jsonl, timestamped
Rounds loop foreverround counter + max_rounds → the tower stops and tells you to escalate
Two agents overwrite each otherwrite_scopes per role, one directory per role
Work lost after compactionState on disk; tower rework T-0012 resumes with the note attached
Can't hand the project overgit log of reports and tasks is the handover doc
You re-read every report to see if a human is neededtower attention lists only ready / blocked / failed
A retry or a double-click creates duplicate worktower dispatch --key <id> returns the existing task

Install

git clone https://github.com/evolution301/agent-control-tower.git
cd agent-control-tower
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e .

Python 3.10+. One runtime dependency (PyYAML).

60-second demo (no model, no network, no API key)

tower demo
[1] dispatched T-0001 -> reported
[2] verify: PASS
[3] accepted T-0001
[4] ledger lines: 5

The mock adapter writes a placeholder artifact and a real report, so the entire dispatch → report → verify → accept loop is deterministic and CI-friendly.

Use it with DSH (primary)

The pattern was extracted from a real DeepSeek Harness deployment: one lead session steering several long-lived role sessions through a multi-week build, verifying every delivery.

# tower.toml
roles:
  frontend:
    description: Layout, components, styling, browser verification.
    adapter: dsh
    write_scopes: ["web/"]

adapters:
  dsh:
    base_url: "http://127.0.0.1:65163"
    sessions:
      frontend: "session-acc99990-..."

→ Full walkthrough: docs/dsh-quickstart.md — create role sessions, wire config, run the loop, plus the five failure modes the reference project hit and how the code prevents them.

Drive it from inside DSH: packages/dsh-tower

A companion bundle registers five tools on the harness tool registry, so the session never leaves the conversation:

dsh plugin --profile <name> add ./packages/dsh-tower

tower_dispatch · tower_inbox · tower_accept · tower_rework · tower_status → packages/dsh-tower

Panel: one glance, everything in flight

Two renderings of the same snapshot — the terminal (tower board) and a local read-only web panel (tower panel). Person-states first, healthy work takes no space.

panel

tower board            # print once
tower board --watch    # refresh in place (tmux/ssh friendly)
tower panel            # http://127.0.0.1:8787/  (read-only, auto-refresh 2s)

The panel shows needs you (ready / blocked / failed, each with its one question), then what is in flight, then what every role is doing right now, then the tail of the ledger. It reads the same files the CLI does — no database, no daemon, no writes.

Adapters

An adapter is the only component that knows how to make an agent act. Same contract for all four:

AdapterDrivesUse when
dsha live DeepSeek Harness session (session/prompt, queued)primary: long-lived roles with browser + shell tools
cliany agent binary that takes a prompt (claude -p, codex exec, Aider, your script)you already run agent CLIs
openaiany OpenAI-compatible /chat/completions with a small run_shell loophosted models, vLLM, Ollama
mocknothing (writes a placeholder artifact)tests, demos, CI

Adding one is ~40 lines → docs/adapters.md.

The report contract (and why the verifier exists)

## Status         → progress | waiting | ready | blocked | failed | dropped
## Summary        → 2–5 sentences
## Files          → - <path> — <what changed>          (must exist on disk)
## Checks         → - <command run> → <result seen>    (a bare command fails)
## Open issues    → anything unfinished, or "none"
## Attention      → only when status is blocked/failed: one concrete question

The status vocabulary is a closed set, borrowed from DeepSeek's dsh-superagent research prototype: the lead routes on a state, not on prose, and tower attention shows only the tasks that are actually waiting for a person. See docs/superagent-ideas.md for what we borrowed and what we deliberately left out.

tower accept mechanically rejects: a missing/empty section · a check line without a result · a listed file that does not exist · any failing entry in verify_commands.

verify_commands:
  - "pytest -q"
  - "npm run build"

That is the difference between "the agent said it works" and "the tower checked".

Commands

CommandPurpose
tower init <dir>scaffold config + role folders
tower dispatch <role> "<brief>" [--key …]create and run one task (idempotent with --key)
tower batch role1 role2 --brief "…"fan one brief out to several roles
tower inbox [--all] [--read]reports filed by roles (the full log)
tower attentiononly the tasks waiting on a person
tower board [--watch] / tower panelthe same picture, in a terminal or a local web page
tower accept <task>verify, then accept (auto-rejects to rework)
tower rework <task> --note "…"send back with reasons and re-dispatch
tower status [--json]task board
tower show <task> / tower loginspect one task / tail the ledger
tower demooffline end-to-end demo
tower doctorvalidate config, adapter options, keys, sessions

What's on disk

my-project/
├── tower.toml              # roles, adapters, verify commands
├── roles/
│   ├── backend/            # this role's only write area
│   │   ├── AGENT.md
│   │   └── T-0001-REPORT.md
│   ├── frontend/
│   └── qa/
└── .tower/
    ├── tasks/T-0001.json   # materialised task view
    ├── ledger.jsonl        # append-only event log (source of truth)
    └── inbox.jsonl         # reports + unread flag

How it compares

single agentagent framework (graph/DSL)agent-control-tower
Who routes the workthe one agent, in its headthe graph you wrotethe lead, explicitly, per task
Deliverablechat textnode outputsfiles + a four-section report
"Done" meansthe agent said sothe node finishedthe tower verified it
Audit trailscrollbackframework logsledger.jsonl you can git diff
Setuppromptgraph + runtime + infrapip install + one YAML
Runs offline / in CInosometimesyes (tower demo)

FAQ

Do I need DSH to use it? No. DSH is the primary adapter because that is where the pattern came from; cli, openai and mock follow the same contract.

Does it run the agents itself? No. It renders the prompt, calls your adapter, and checks what came back. The agent runtime stays yours.

Can agents talk to each other? No. Hub-and-spoke on purpose: the lead routes, so the task tree stays inspectable and two roles cannot silently converge on the same file.

What if a role keeps failing? round increments on every rework; when round > max_rounds the tower stops and tells you to escalate instead of looping.

Is the ledger safe to commit? Yes — append-only JSONL; committing it puts the audit trail in git log. Add .tower/ to .gitignore if you only want deliverables.

Where does it write? Only inside the tower workspace: roles/<role>/ for deliverables and .tower/ for state.

Docs

DocWhat's inside
DSH quickstartthe primary setup, end to end, plus five failure modes it prevents
Architecturecomponents, state machine, why files instead of a queue
Adaptersmock / cli / dsh / openai, and how to add your own
Borrowed from dsh-superagentthe five ideas we adopted, and what we left out
Contributingground rules, PR checklist, adapter guide

License

MIT — see LICENSE.

Acknowledgements

  • The pattern was extracted from a real long-running build on DeepSeek Harness, then generalised so it works without it.
  • The dsh-tower bundle follows the harness bundle conventions from the DeepSeek Harness docs: dsh.bundle + cordis.patch.yml + ctx.tools.register.
  • Terminal frames in this README are real command output, rendered by docs/assets/build_assets.py — no mock-ups.

Star History

Star History Chart

Related plugins