Harbor as of 2026-10-05: the coordination hub for the Open Sea's decomp team

Messages, threads, acknowledgements, claims, bands, tasks, review items and
stop flags for a team of AI agents; the chat page and board; host hooks and
MCP server; autopilot; tools/watch.py. Put under version control after the
weekend's changes so they have history and can be rolled back. Local only.
This commit is contained in:
KeelrLeek committed 2026-10-05 10:44:23 -05:00
commit dbfd1720c5
43 files changed
+12440

No files matched your search

+419
View File
@@ -0,0 +1,419 @@
"""Team work on top of messages: tasks, handoffs, pins, the human's availability, connections and restart prompts.
Mixed into Harbor (core.py); every method uses Harbor's connection, clock and write helper.
"""
import json
from .util import (PRIORITY_NAMES, HarborError, first_line, iso, parse_iso, parse_priority, parse_scope, parse_until,
scopes_overlap, utc)
TASK_STATES = ("open", "in_progress", "blocked", "review", "done", "cancelled")
OPEN_TASK_STATES = ("open", "in_progress", "blocked", "review")
TASK_MOVES = {
"open": {"in_progress", "cancelled"},
"in_progress": {"blocked", "open", "done", "cancelled", "review"},
"blocked": {"in_progress", "open", "cancelled"},
"review": {"in_progress", "done", "cancelled"},
"done": {"open"},
"cancelled": {"open"},
}
PRESENCE = ("here", "away", "dnd")
WORK_SCHEMA = """
CREATE TABLE IF NOT EXISTS tasks(
id INTEGER PRIMARY KEY, title TEXT NOT NULL, body TEXT, scope TEXT, lo INTEGER, hi INTEGER,
priority INTEGER NOT NULL DEFAULT 2, state TEXT NOT NULL, assignee TEXT, created_by TEXT NOT NULL,
item_id INTEGER, claim_id INTEGER, due_at TEXT, block_reason TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS tasks_state ON tasks(state, priority, id);
CREATE TABLE IF NOT EXISTS task_deps(task_id INTEGER NOT NULL, depends_on INTEGER NOT NULL, PRIMARY KEY(task_id, depends_on));
CREATE TABLE IF NOT EXISTS task_events(id INTEGER PRIMARY KEY, task_id INTEGER NOT NULL, agent TEXT NOT NULL,
from_state TEXT, to_state TEXT NOT NULL, note TEXT, at TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS handoffs(id INTEGER PRIMARY KEY, agent TEXT NOT NULL, body TEXT NOT NULL, created_at TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS handoffs_agent ON handoffs(agent, id);
CREATE TABLE IF NOT EXISTS pins(message_id INTEGER PRIMARY KEY, pinned_by TEXT NOT NULL, note TEXT, pinned_at TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS presence(name TEXT PRIMARY KEY, status TEXT NOT NULL, until TEXT, note TEXT, set_at TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS connections(agent TEXT NOT NULL, channel TEXT NOT NULL, host TEXT NOT NULL DEFAULT '',
last_at TEXT NOT NULL, count INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(agent, channel, host));
"""
class WorkMixin:
# ----------------------------------------------------------------- connections and activity
def _connect(self, c, agent, channel, host=""):
now = self.now()
c.execute("INSERT INTO connections(agent, channel, host, last_at, count) VALUES (?,?,?,?,1) "
"ON CONFLICT(agent, channel, host) DO UPDATE SET last_at=excluded.last_at, count=count+1",
(agent, channel, host or "", now))
c.execute("UPDATE agents SET last_seen=? WHERE name=?", (now, agent))
def connect(self, agent, channel, host=""):
"""Record that agent reached Harbor through channel (mcp, hook, cli, web) from host (codex, kimi, ...)."""
self._write(lambda c: self._connect(c, agent, channel, host))
def activity(self, agent, tool, host="", target=None, channel="hook"):
"""Agent used a tool: drives the 'working now' indicator, the pulse and the live feed. Hooks report every tool
call; agents whose app runs no hooks still show up through their Harbor calls (channel cli or mcp) and the
server's worktree scan (channel files)."""
now = self.now()
def do(c):
self._connect(c, agent, channel, host)
c.execute("UPDATE agents SET active_at=?, last_tool=? WHERE name=?", (now, str(tool)[:80] or None, agent))
self._pulse(c, agent, tools=1)
self._log_activity(c, agent, tool, target)
self._write(do)
def connections(self):
out = {}
for r in self.conn.execute("SELECT * FROM connections ORDER BY agent, last_at DESC"):
out.setdefault(r["agent"], []).append({**dict(r), "minutes": self.minutes_since(r["last_at"])})
return out
def working(self, agent_row, within=3):
minutes = self.minutes_since(agent_row.get("active_at"))
return minutes is not None and minutes < within
# ----------------------------------------------------------------- the human's availability
def set_presence(self, by, origin, status, until=None, note=None):
if by != self.human or origin != "web":
raise HarborError(f"only {self.human} sets their availability, from the chat page")
if status not in PRESENCE:
raise HarborError(f"status must be one of {', '.join(PRESENCE)}")
until_at = parse_until(until, utc(self.clock())) if until else None
note = " ".join((note or "").split())[:300] or None
now = self.now()
def do(c):
c.execute("INSERT INTO presence(name, status, until, note, set_at) VALUES (?,?,?,?,?) ON CONFLICT(name) DO "
"UPDATE SET status=excluded.status, until=excluded.until, note=excluded.note, set_at=excluded.set_at",
(self.human, status, until_at, note, now))
self._write(do)
return self.presence()
def presence(self):
row = self.conn.execute("SELECT * FROM presence WHERE name=?", (self.human,)).fetchone()
if not row:
return {"name": self.human, "status": "here", "until": None, "note": None, "set_at": None}
p = dict(row)
if p["until"] and parse_iso(p["until"]) <= utc(self.clock()):
p.update(status="here", expired=True)
return p
def presence_line(self):
p = self.presence()
if p["status"] == "here":
return None
what = {"away": "is away", "dnd": "does not want to be disturbed"}[p["status"]]
line = f"{self.human} {what}" + (f" until {p['until']}" if p["until"] else "")
return line + (f": {p['note']}" if p["note"] else "") + " - decide with a coordinator instead of waiting."
# ----------------------------------------------------------------- handoffs
def handoff(self, agent, body):
self._check_agent(agent, allow_human=False)
body = (body or "").replace("\r\n", "\n").strip()
if not body:
raise HarborError("empty handoff")
if len(body) > 20000:
raise HarborError("a handoff is at most 20,000 characters; attach a file to a message instead")
now = self.now()
def do(c):
hid = c.execute("INSERT INTO handoffs(agent, body, created_at) VALUES (?,?,?)", (agent, body, now)).lastrowid
self._touch(c, agent)
return hid
return {"id": self._write(do), "agent": agent, "created_at": now}
def handoffs(self, agent=None, limit=10):
if agent:
rows = self.conn.execute("SELECT * FROM handoffs WHERE agent=? ORDER BY id DESC LIMIT ?", (agent, int(limit)))
else:
rows = self.conn.execute("SELECT * FROM handoffs ORDER BY id DESC LIMIT ?", (int(limit),))
return [dict(r) for r in rows]
def last_handoff(self, agent):
rows = self.handoffs(agent, limit=1)
return rows[0] if rows else None
# ----------------------------------------------------------------- pins
def pin(self, by, message_id, note=None):
self._check_agent(by)
if not self.message(message_id):
raise HarborError(f"no message #{message_id}")
now = self.now()
def do(c):
c.execute("INSERT INTO pins(message_id, pinned_by, note, pinned_at) VALUES (?,?,?,?) ON CONFLICT(message_id) "
"DO UPDATE SET note=COALESCE(excluded.note, note)", (int(message_id), by, note, now))
self._write(do)
return self.pins()
def unpin(self, by, message_id):
self._check_agent(by)
self._write(lambda c: c.execute("DELETE FROM pins WHERE message_id=?", (int(message_id),)))
return self.pins()
def pins(self, limit=30):
rows = self.conn.execute("SELECT p.note pin_note, p.pinned_by, p.pinned_at, m.*, t.topic, t.subject FROM pins p "
"JOIN messages m ON m.id=p.message_id JOIN threads t ON t.id=m.thread_id "
"ORDER BY p.pinned_at DESC LIMIT ?", (int(limit),))
return [self._message_dict(r) for r in rows]
def decisions(self, limit=30):
rows = self.conn.execute(self.MESSAGE_SELECT + " WHERE m.kind='decision' ORDER BY m.id DESC LIMIT ?", (int(limit),))
return [self._message_dict(r) for r in rows]
# ----------------------------------------------------------------- tasks
def _task_dict(self, row):
t = dict(row)
t["priority_name"] = PRIORITY_NAMES.get(t["priority"], str(t["priority"]))
t["depends_on"] = [r[0] for r in self.conn.execute("SELECT depends_on FROM task_deps WHERE task_id=? ORDER BY 1",
(t["id"],))]
t["waiting_on"] = [r[0] for r in self.conn.execute(
"SELECT d.depends_on FROM task_deps d JOIN tasks x ON x.id=d.depends_on WHERE d.task_id=? AND "
"x.state NOT IN ('done', 'cancelled') ORDER BY 1", (t["id"],))]
return t
def task(self, task_id):
row = self.conn.execute("SELECT * FROM tasks WHERE id=?", (int(task_id),)).fetchone()
if not row:
raise HarborError(f"no task #{task_id}")
events = [dict(r) for r in self.conn.execute("SELECT * FROM task_events WHERE task_id=? ORDER BY id",
(int(task_id),))]
return self._task_dict(row), events
def tasks(self, states=OPEN_TASK_STATES, assignee=None, limit=200):
query = f"SELECT * FROM tasks WHERE state IN ({','.join('?' * len(states))})"
args = list(states)
if assignee:
query += " AND assignee=?"
args.append(assignee)
query += " ORDER BY CASE state WHEN 'in_progress' THEN 0 WHEN 'blocked' THEN 1 WHEN 'review' THEN 2 ELSE 3 END, "
query += "priority, COALESCE(due_at, '9999'), id LIMIT ?"
args.append(int(limit))
return [self._task_dict(r) for r in self.conn.execute(query, args)]
def add_task(self, by, title, body=None, scope=None, assignee=None, priority="normal", after=(), due=None,
origin="cli", notify=True):
self._check_agent(by, allow_human=(origin == "web"))
title = " ".join((title or "").split())
if not title:
raise HarborError("a task needs a title")
if assignee:
self._check_agent(assignee, allow_human=False)
if assignee != by and by not in self.coordinators:
raise HarborError("only a coordinator assigns tasks to someone else; leave it unassigned for `next`")
canonical, lo, hi = parse_scope(scope) if scope else (None, None, None)
prio = parse_priority(priority)
due_at = parse_until(due, utc(self.clock())) if due else None
after = [int(a) for a in (after or [])]
for dep in after:
self.task(dep)
now = self.now()
def do(c):
tid = c.execute("INSERT INTO tasks(title, body, scope, lo, hi, priority, state, assignee, created_by, due_at, "
"created_at, updated_at) VALUES (?,?,?,?,?,?,'open',?,?,?,?,?)",
(title, body, canonical, lo, hi, prio, assignee, by, due_at, now, now)).lastrowid
for dep in after:
c.execute("INSERT OR IGNORE INTO task_deps(task_id, depends_on) VALUES (?,?)", (tid, dep))
c.execute("INSERT INTO task_events(task_id, agent, from_state, to_state, note, at) VALUES (?,?,NULL,'open',?,?)",
(tid, by, body and first_line(body, 200), now))
self._touch(c, by)
return tid
tid = self._write(do)
if notify and assignee and assignee != by:
self.send(by, [assignee], f"Task #{tid} is yours: {title}" + (f"\n{body}" if body else ""),
thread=f"task:{tid}", subject=f"Task: {title}", kind="request",
importance="high" if prio <= 1 else "normal", trusted_refs={"task": tid}, origin="system")
return self.task(tid)[0]
def _move_task(self, c, task, agent, to_state, note=None, **fields):
now = self.now()
sets = ", ".join(["state=?", "updated_at=?"] + [f"{k}=?" for k in fields])
cur = c.execute(f"UPDATE tasks SET {sets} WHERE id=? AND state=?",
(to_state, now, *fields.values(), task["id"], task["state"]))
if cur.rowcount == 0:
raise HarborError(f"task #{task['id']} changed meanwhile; look at it again")
c.execute("INSERT INTO task_events(task_id, agent, from_state, to_state, note, at) VALUES (?,?,?,?,?,?)",
(task["id"], agent, task["state"], to_state, note, now))
def _eligible(self, task, agent):
if task["state"] != "open" or task["assignee"] not in (None, agent) or task["waiting_on"]:
return False
if task["scope"]:
now = self.now()
for a in self.conn.execute("SELECT * FROM claims WHERE released_at IS NULL AND (expires_at IS NULL OR "
"expires_at > ?) AND agent != ?", (now, agent)):
if a["kind"] in ("draft", "naming", "edit", "band") and scopes_overlap(
(task["scope"], task["lo"], task["hi"]), (a["scope"], a["lo"], a["hi"])):
if a["kind"] == "band" and task["assignee"] == agent:
continue # assigned to me explicitly inside someone's band
return False
return True
def take_task(self, agent, task_id, note=None):
self._check_agent(agent, allow_human=False)
task, _ = self.task(task_id)
if task["state"] != "open":
raise HarborError(f"task #{task_id} is {task['state']}" + (f" (assigned to {task['assignee']})"
if task["assignee"] else ""))
if task["assignee"] not in (None, agent):
raise HarborError(f"task #{task_id} is assigned to {task['assignee']}")
if task["waiting_on"]:
raise HarborError(f"task #{task_id} waits on " + ", ".join(f"#{d}" for d in task["waiting_on"]))
self.require_running(agent, "take tasks")
claim_id = None
if task["scope"]:
if task["assignee"] == agent and task["created_by"] in self.coordinators and task["created_by"] != agent and task["created_by"] != self.human:
# a coordinator assigned it explicitly (possibly inside someone else's band): record it as theirs
claim_id = self.claim(task["created_by"], task["scope"], kind="draft", note=f"task #{task_id}",
on_behalf_of=agent, override=True)["id"]
elif task["assignee"] == agent and task["created_by"] == self.human:
coordinator = next(iter(self.project.coordinators), None)
claim_id = (self.claim(coordinator, task["scope"], kind="draft", note=f"task #{task_id} (assigned by "
f"{self.human})", on_behalf_of=agent, override=True)["id"] if coordinator else
self.claim(agent, task["scope"], kind="draft", note=f"task #{task_id}")["id"])
else:
claim_id = self.claim(agent, task["scope"], kind="draft", note=f"task #{task_id}")["id"]
def do(c):
self._move_task(c, task, agent, "in_progress", note, assignee=agent, claim_id=claim_id, block_reason=None)
now = self.now()
c.execute("UPDATE agents SET doing=?, doing_at=?, last_seen=? WHERE name=?",
(f"task #{task_id}: {task['title']}"[:200], now, now, agent))
try:
self._write(do)
except HarborError:
if claim_id:
self._write(lambda c: c.execute("UPDATE claims SET released_at=? WHERE id=?", (self.now(), claim_id)))
raise
return self.task(task_id)[0]
def next_task(self, agent, take=True):
"""The best open task agent may take (priority, then due time, then age); takes it unless take=False."""
self._check_agent(agent, allow_human=False)
if self.stopped(agent):
return None
for task in self.tasks(states=("open",), limit=100000):
if self._eligible(task, agent):
if not take:
return task
try:
return self.take_task(agent, task["id"])
except HarborError:
continue
return None
def update_task(self, agent, task_id, to_state, note=None, origin="cli"):
self._check_agent(agent, allow_human=(origin == "web"))
if to_state not in TASK_STATES:
raise HarborError(f"task state must be one of {', '.join(TASK_STATES)}")
task, _ = self.task(task_id)
if to_state == "in_progress" and task["state"] == "open":
return self.take_task(agent, task_id, note)
if to_state == "review":
raise HarborError("a task goes to review by `submit --task`")
if to_state not in TASK_MOVES[task["state"]]:
raise HarborError(f"task #{task_id} is {task['state']}; it can move to "
+ ", ".join(sorted(TASK_MOVES[task["state"]])))
owner = (agent == task["assignee"] or agent in self.coordinators or
(agent == task["created_by"] and (not task["assignee"] or task["state"] in ("open", "done", "cancelled"))))
if not owner:
raise HarborError(f"task #{task_id} belongs to {task['assignee'] or task['created_by']}")
if to_state == "in_progress":
self.require_running(agent, "resume tasks")
if to_state == "blocked" and not note:
raise HarborError("say what blocks it (note)")
fields = {}
if to_state == "blocked":
fields["block_reason"] = note
releasing = to_state in ("open", "done", "cancelled") and task["claim_id"]
if releasing:
fields["claim_id"] = None
assigned = task["created_by"] != task["assignee"] and (task["created_by"] in self.coordinators
or task["created_by"] == self.human)
if to_state == "open" and task["state"] in ("in_progress", "blocked") and not assigned:
fields["assignee"] = None # handed back: anyone may take it with `next`
# a task a coordinator (or the human) gave to someone stays theirs when they hand it back for later: clearing it
# would let band routing hand it to the band owner (a load-balanced task inside another agent's band)
def do(c):
if releasing:
c.execute("UPDATE claims SET released_at=? WHERE id=? AND released_at IS NULL", (self.now(), task["claim_id"]))
self._move_task(c, task, agent, to_state, note, **fields)
if to_state in ("done", "cancelled", "open") and task["assignee"]:
c.execute("UPDATE agents SET doing=NULL WHERE name=? AND doing LIKE ?", (task["assignee"], f"task #{task_id}:%"))
self._touch(c, agent)
self._write(do)
people = {task["created_by"], *self.project.coordinators} - {agent}
if to_state == "blocked" and people:
self.send(agent, sorted(people), f"Task #{task_id} ({task['title']}) is blocked: {note}",
thread=f"task:{task_id}", subject=f"Task: {task['title']}", kind="status", importance="high",
trusted_refs={"task": int(task_id)}, origin="system")
elif to_state == "done" and task["created_by"] != agent:
self.send(agent, [task["created_by"]], f"Task #{task_id} ({task['title']}) is done" + (f": {note}" if note else "."),
thread=f"task:{task_id}", subject=f"Task: {task['title']}", kind="status",
trusted_refs={"task": int(task_id)}, origin="system")
return self.task(task_id)[0]
def _task_follows_item(self, item_id, item_state, agent):
"""Keep a task in step with its review item: landed -> done; sent back -> in progress."""
row = self.conn.execute("SELECT * FROM tasks WHERE item_id=?", (int(item_id),)).fetchone()
if not row:
return
task = self._task_dict(row)
target = {"landed": "done", "changes_requested": "in_progress", "withdrawn": "in_progress",
"rejected": "in_progress", "submitted": "review"}.get(item_state)
if not target or target == task["state"] or task["state"] not in ("review", "in_progress", "blocked") \
or target not in TASK_MOVES[task["state"]]:
return
def do(c):
self._move_task(c, task, agent, target, f"review item #{item_id} {item_state}")
if target == "done" and task["claim_id"]:
c.execute("UPDATE claims SET released_at=? WHERE id=? AND released_at IS NULL", (self.now(), task["claim_id"]))
c.execute("UPDATE agents SET doing=NULL WHERE name=? AND doing LIKE ?", (task["assignee"], f"task #{task['id']}:%"))
self._write(do)
# ----------------------------------------------------------------- restart prompt
def restart_prompt(self, agent, cmd=None):
"""A prompt to paste into a fresh session for agent: who it is, where it works, the rules, and the state."""
from .texts import launcher, protocol
self._check_agent(agent, allow_human=False)
cmd = cmd or launcher()
if not any(a["name"] == agent for a in self.project.agents):
raise HarborError(f"{agent} is not on this project's roster")
run = " ".join(cmd)
spec = next(a for a in self.project.agents if a["name"] == agent)
lines = [f"You are {agent} on the {self.project.title} team" + (f" ({spec['role']})." if spec["role"] else ".")]
if spec.get("workdir"):
lines.append(f"Work in {spec['workdir']}.")
if spec.get("instructions"):
lines.append(spec["instructions"].strip())
lines.append(f"Before anything else run `{run} --as {agent} brief` (or the Harbor MCP tool `brief`) and follow it.")
lines.append("")
lines.append(protocol(agent, self.project.name, cmd).strip())
lines.append("")
lines.append(f"State from Harbor at {self.now()}:")
stops = self.stopped(agent)
lines.append("- " + ("STOPPED (" + ", ".join(f["key"] for f in stops) + "): wait for the human to resume you; "
"do not start work." if stops else "Not stopped."))
mine = self.tasks(assignee=agent)
if mine:
lines.append("- Your tasks: " + "; ".join(f"#{t['id']} {t['state']} {t['title']}" for t in mine[:8]))
else:
nxt = self.next_task(agent, take=False)
lines.append("- No task in progress." + (f" Next available: #{nxt['id']} {nxt['title']} (take it with "
"`next`)." if nxt else ""))
claims = self.active_claims(agent=agent)
if claims:
lines.append("- Your claims: " + ", ".join(f"{c['kind']} {c['scope']}" for c in claims[:12]))
total, urgent = self.unread_count(agent)
lines.append(f"- Unread messages: {total}" + (f" ({urgent} urgent)" if urgent else ""))
h = self.last_handoff(agent)
if h:
lines.append(f"- Your last handoff ({h['created_at']}):")
lines += [" " + line for line in h["body"].split("\n")[:40]]
presence = self.presence_line()
if presence:
lines.append("- " + presence)
return "\n".join(lines)