# Priority Forge — Team Hub Workflows

The operational playbook for cross-forge work. This is the **how-to**; the
**why/how-it's-built** lives in
[`packages/priority-forge-hub/ARCHITECTURE.md`](../packages/priority-forge-hub/ARCHITECTURE.md),
and first-time setup lives in [`TEAM_ONBOARDING.md`](./TEAM_ONBOARDING.md).

> **Everything here is natural-language-drivable.** You don't memorize tool
> calls — you describe the intent to your agent ("publish this task to the
> team", "what's everyone working on", "bump that shared task to P0") and it
> issues the MCP calls. The explicit calls are shown so you know what's
> happening underneath.

## Mental model in 30 seconds

Two planes on one shared Postgres hub:

| Plane | Job | Nature |
|---|---|---|
| **Memory** (`team_memory_events`) | Shared *context* — "who's doing what, what blocks what" | Passive, append-only, auto-emitted |
| **Orchestration** (`shared_tasks`) | Shared *work* — claimable tasks with leases | Active, explicit, contended |

- A **SharedTask** carries only seeds — `title`, `description`, `deadline`, advisory `priority`. Everything rank/ML-affecting stays in each forge's local projection (linked by `sharedTaskId`).
- **Security:** the connection string is a secret (out-of-band only) + every write/claim is per-agent token-gated. (The network SG is currently open; the token gate is the real lock.)

---

## Workflow catalog

### 1. Connect a forge to the hub
**Purpose:** join the team hub so you see/contribute shared work + activity.
**Steps:** `xmem_enable_sync({ backend:'pgvector', teamId, agentId, connectionString, agentToken })` → `hub_status` (expect `health.ok:true`, `warnings:[]`). Turn off with `xmem_disable_sync` (config preserved; re-enable is one call).
**NL:** *"enable the priority forge hub from my saved config."*
**Gotchas:** credentials come from your token row out-of-band; fully relaunch your AI tool after `npm run setup:mcp` so the tools load; backend must be running on `:3456`.

### 2. Publish & claim (distribute / hand off work)
**Purpose:** make a task available for *someone else* to pick up, or move work between people.
**Steps:**
- Seed it: `shared_task_publish({ localTaskId })` (promote an existing local task — auto-claims to you) **or** `shared_task_create({ title, description?, deadline?, priority? })` (net-new shared task, no local task).
- Make it available: `shared_task_release({ sharedTaskId, outcome:'handed_back' })` → status `claimable`.
- Pick it up (the claimer): `shared_task_list({ status:'claimable' })` → `shared_task_claim({ sharedTaskId, priority?, effort?, project? })` → materializes a local projection (`in_progress`, linked by `sharedTaskId`).
- Finish: `complete_task({ id })` on the local projection — auto-releases the hub lease and marks the SharedTask `completed`.
**NL:** *"publish my 'fix auth' task to the team"* / *"claim the X shared task and work it."*
**Gotchas:** claim is **pull-based and contended** — see the [auto-claim race](#auto-claim--distribution-control). Priority/effort are claimer-chosen (or adopt the seed); the claimer's forge **re-ranks locally**.

### 3. Priority directive (steer priority *in place*, no reassignment)
**Purpose:** change the priority of a task a teammate already owns, **without** taking it from them or starting a claim race.
**Steps:**
- You issue: `shared_task_set_priority({ sharedTaskId, priority, note? })` → emits an advisory `priority_directive` (mutates nobody's queue, creates no claim).
- Owner sees: `shared_task_get_priority_directives()` → directives targeting tasks *that forge owns*, with suggested vs current priority + `satisfied` flag.
- Owner applies: `shared_task_apply_priority_directives()` → in-place `updateTask` priority change (logged as a normal change; no new local id).
**NL:** *"set the 'migrate DB' shared task to P0 with a note about the launch."* / (owner) *"apply any pending priority directives."*
**Gotchas:** needs an **owner** (a forge holding that SharedTask's projection) — it steers *already-distributed* work, not initial assignment. It's a strong signal, **not a forced override** (owner's forge applies it); idempotent.

### 4. Dedupe / overlap detection
**Purpose:** find duplicated/overlapping work across the team before re-prioritizing or reassigning.
**Steps:** `shared_task_search({ query })` (text over the live pool's title+description) + `xmem_search_team_memory({ query })` (semantic over the activity feed) + `shared_task_list({ status })` (full enumeration).
**NL:** *"is anyone else working on auth?"* / *"find duplicate shared tasks about the parser."*
**Gotchas:** `shared_task_search` is the current pool; `xmem_search_team_memory` is historical activity — use both. No content dedupe on publish, so duplicates *can* exist; this is how you spot them.

### 5. Cancel / retire (clean up duplicates)
**Purpose:** remove a claimable duplicate/erroneous SharedTask.
**Steps:** `shared_task_cancel({ sharedTaskId })`.
**NL:** *"cancel the duplicate 'set up CI' shared task."*
**Gotchas:** **originator-only**, and **claimable-only** — a *claimed* task must be released by its holder (cancel won't yank it).

### 6. Team awareness (current focus + activity)
**Purpose:** see what the team is doing without touching any work.
**Steps:** `xmem_get_current_focus()` (who's on what right now) · `xmem_get_team_activity({ limit, sinceTs?, agentIds? })` (the event feed).
**NL:** *"what's everyone working on?"* / *"show me the last 20 team events."*
**Gotchas:** read-only; reflects emitted observations (title/project/status, not full task content).

### 7. Blocker / dependency visibility
**Purpose:** see cross-forge blocking relationships.
**Steps:** `xmem_get_blocking_me()` (others' work that blocks you) · `xmem_get_i_am_blocking()` (your work blocking others) · `xmem_force_rederive_blockers()` (recompute edges if stale).
**NL:** *"what's blocking me?"* / *"whose work am I holding up?"*

### 8. Audit & history
**Purpose:** reconstruct what happened to a piece of shared work.
**Steps:** `shared_task_get({ sharedTaskId })` (full record + currentClaim) · `shared_task_history({ sharedTaskId })` (raw claim audit) · `shared_task_timeline({ sharedTaskId })` (narrative "5m ago …") · `shared_task_get_my_claims()` (leases you currently hold).
**NL:** *"who's touched the 'refactor' shared task?"* / *"what am I currently holding?"*

### 9. Lease lifecycle
**Purpose:** understand the claim → renew → release/expire mechanics.
- On claim, a **lease** is set (`leaseUntil = now + ttlMs`, default 30 min).
- While the holder's forge is alive + the task is `in_progress`, PF **auto-renews** via a heartbeat (~every 15 min) — so the lease effectively never expires during active work. **The TTL is not a deadline to finish.**
- On **expiry without renew** (holder crashed/offline/abandoned): the task becomes **re-claimable by anyone** (lazy — happens on the next claim attempt; no background sweeper). The original holder can no longer renew; they can still release *if* nobody re-claimed.
- Manual control: `shared_task_renew_lease({ sharedTaskId, ttlMs? })` · per-claim `ttlMs` · `DEFAULT_TTL_MS` in `src/orchestration/shared-tasks.ts`.
**Tuning:** longer TTL = more tolerance for transient holder downtime before steal, slower recovery from real failure; shorter = the reverse. The lease is a **dead-man's switch**, not a work timer.

### 10. Admin: provision / rotate agents
**Purpose:** add a teammate, or fix/rotate a token.
**Steps:** `scripts/bootstrap-hub.ts` — `--add-only --agents <email>` (register a new agent + mint token + emit a token file) · `--rotate-only --agents <email>` (rotate an existing agent's token) · or `xmem_register_agent({ agentId, token })` (register/rotate one token). Distribute each agent's row out-of-band; never commit `team-tokens-*.json` (gitignored).
**Gotchas:** registration writes the SHA-256 hash to `agent_identities`; the agent's `team-hub.json` must hold the matching plaintext token or its **writes** fail with `unauthorized agent` (reads are unauthenticated).

---

## Cross-cutting behaviors & gotchas

### Auto-claim & distribution control
Teammates' agents are **proactive** — a Claude that *sees* a claimable task may claim+complete it on its own (more so in a bypass session, where nothing makes it pause). So "publish to the open pool" can become **"whichever agent notices it first grabs it,"** not "the lead assigns it." To keep distribution under your control:
1. **Use priority directives** (Workflow 3) for work that should *stay with its owner* — no pool, no race.
2. **Tell teammates' agents not to auto-claim** — claim only when you explicitly direct them, so the pool is a controlled inbox.
Use the open pool's auto-claim deliberately (great for hands-free load-balancing), not by accident.

### Projection seeds (what travels vs what stays)
Only `title`/`description`/`deadline`/advisory-`priority` cross to a claimer. Effort, notes body, work durations, weights, priority *scores*, and priority *change history* stay forge-local. The claimer's forge **re-ranks** with your priority as the base label and its own heuristics on top — your steer + their local context.

### ML continuity across reassignment
Every ML record carries `sharedTaskId`, so a task's training history survives the release→reclaim local-id churn (a new local id per claim is stitched back via the stable hub key). You don't manage this — it just keeps the learning signal whole.

### Security posture
Network port is open (any IP can reach `:5432`); the real gates are the **secret connection string** (read access) + the **per-agent token** (write/claim access). Keep the conn string out-of-band; if it leaks, rotate the DB password. SSL is enforced.

---

## Tool reference

**Orchestration (`shared_task_*`):** `create` · `publish` · `list` · `claim` · `release` · `renew_lease` · `cancel` · `get` · `get_my_claims` · `history` · `timeline` · `search` · `set_priority` · `get_priority_directives` · `apply_priority_directives`
**Memory (`xmem_*`):** `enable_sync` · `disable_sync` · `get_status` · `get_team_activity` · `get_current_focus` · `search_team_memory` · `get_blocking_me` · `get_i_am_blocking` · `force_rederive_blockers` · `register_agent` · `reader_debug`
**Config (`hub_*`):** `configure` (fine-grained planes) · `status` (health + warnings)
**Admin script:** `scripts/bootstrap-hub.ts` (`--add-only` / `--rotate-only`)

---

## Future: abstract these behind Claude skills

These workflows are the natural seams for packaged skills (like the apply-pilot / web-interact skills), each codifying the steps + guardrails so a teammate just says `/hub-…`:

| Proposed skill | Wraps | Codifies |
|---|---|---|
| `hub-onboard` | Workflow 1 | pull/build/setup:mcp, paste token row, `enable_sync`, `hub_status` verify; the two human-only steps (relaunch, token paste) |
| `hub-status` | Workflows 6, 8 | health + current focus + my claims + recent activity in one glance |
| `hub-distribute` | Workflow 2 | publish/create + (optional) hand-back, **with the distribution-control guardrails** so it doesn't get strip-mined by auto-claim |
| `hub-claim` | Workflow 2 (worker side) | list → claim → work → complete, with lease heartbeat awareness |
| `hub-reprioritize` | Workflow 3 | issue directives (lead) + apply pending directives (owner) |
| `hub-dedupe` | Workflows 4, 5 | overlap report across pool + activity, then cancel/merge duplicates |
| `hub-admin` | Workflow 10 | provision/rotate agents, emit + (securely) hand out token rows |

Each skill should encode the **gotchas** above (auto-claim race, lease semantics, originator-only cancel, directive-needs-owner) so the guardrails travel with the convenience.
