# Wiring Your Local Forge Into the Team Hub

This is the hand-off guide for getting your **local** Priority Forge onto the
latest version and connected to the **team hub** (shared memory + orchestration).
Paste this whole file into your Claude / Cursor / Droid / Codex session and walk
through it — the agent can run most of the steps for you.

> You'll receive a small JSON **token row** from the hub admin (Nick) out of
> band (DM / password-manager share — never email, never a public channel).
> Have it handy before Part 5.

---

## How to use this doc

Paste this whole file into your AI tool and say **"run the onboarding."** The
agent can execute the shell steps (pull, build, service install, MCP setup) and
the MCP calls. **Only two steps need you, the human:**

1. **Fully restart your AI tool** after MCP setup (Part 2) — it can't relaunch
   itself, and that restart is what loads the new Priority Forge tools.
2. **Paste your token row** at "Enable the hub" (Part 5) — the connection string
   and agent token are secrets the admin sends you out-of-band; the agent
   doesn't have them.

Everything else, the agent handles.

---

## Read this first — how the hub actually works

There is **no "everyone dumps their state, the admin merges it, then pushes it
back"** step. That model doesn't exist here, and expecting it will confuse you.
The hub is **forward-only**:

- **Memory plane** (shared activity feed): once you enable sync, *new* mutations
  you make — create / update / complete a task, switch context — emit to the hub
  automatically. Your **existing** tasks are **not** back-filled. History stays
  local.
- **Orchestration plane** (shared task inbox): nothing becomes team-visible
  until you **explicitly publish it** (`shared_task_publish`). There is no
  auto-promote and no admin approval in the loop.
- **No merge, no central reconciliation.** Each person's emissions are
  append-only and tagged with their identity. The hub is a stream others read,
  not a database the admin curates. You publish what you want shared; everyone
  polls to see it.

What this means for drift: there's very little to drift, because nobody is
trying to reconcile snapshots. The only coordination you owe your teammates is
**(a)** auditing what you share before you flip the switch (Part 4) and
**(b)** not double-publishing a task someone else already published (Part 6.4).

### What flows to the hub vs. what stays local

The hub carries the minimum needed to coordinate — `CLAUDE.md` → "Hub boundary"
is the authoritative rule. In short:

**Flows to the hub** (when sync is on and the task/project isn't private):
- Lightweight observations: task **IDs + titles**, project, status, who did
  what and when, dependency/blocker edges, and — on updates — *which fields*
  changed (names only, never the values).
- On `shared_task_publish`: the task **title + description + deadline**
  (description seeded from your notes; deadline advisory — the claimer's forge
  re-ranks it locally), frozen as the seed a teammate's forge projects from.

**Never leaves your machine:**
- Task content beyond the title/description/deadline seed: **priority values,
  effort, body, raw note-text edits**.
- **Learning signals**: task selections, skip decisions, priority changes, work
  durations, heuristic weights, priority scores — all PF-local ML inputs.

So a teammate sees *that* you're working on "Fix the auth bug" in `builds` and
what it blocks — not its priority, effort, notes body, or any of your ranking/ML
signals. And an ingested task is **re-ranked in the receiver's forge** against
their own queue; your priority never rides along.

---

## TL;DR — the happy path

```bash
# 1. Update + rebuild + restart (Linux example; macOS uses install-launchd)
git pull
npm install && (cd frontend && npm install)
npm run build
./setup.sh install-systemd          # macOS: ./setup.sh install-launchd
npm run verify

# 2. Refresh your AI tool's MCP connection
npm run setup:mcp                   # pick your client; then FULLY restart it
```

Then, inside your AI session:

```
3. Audit scope     → list_workspaces ; set_project_privacy / set_task_privacy on anything sensitive
4. Enable the hub  → paste the xmem_enable_sync({...}) snippet from your token row
5. Confirm         → hub_status   (expect ok:true, health.ok:true, no warnings)
6. Share day-1 work→ shared_task_publish per in-flight P0/P1 task
```

That's it. The rest of this doc explains each step and how to recover when
something looks off.

---

## Part 1 — Update your local forge

The setup script is idempotent: re-running it after a `git pull` updates your
services in place. You do **not** need to uninstall first.

```bash
cd <your-priority-forge-checkout>
git pull
npm install
(cd frontend && npm install)
npm run build
```

Then re-install the persistent services so they pick up the new build:

| Platform | Command |
|---|---|
| **Linux / WSL** | `./setup.sh install-systemd` |
| **macOS** | `./setup.sh install-launchd` |

> **WSL users:** your checkout must live in your WSL home (e.g.
> `~/priority-forge`), **not** under `/mnt/c/...`. A cross-mount path silently
> corrupts file-watching and storage.

> **Never** run `npm run dev` inside an editor's integrated terminal (VS Code,
> Cursor). The backend becomes a child of the editor shell and dies when the
> editor closes. Use the persistent service install above.

### 1.3 Verify

```bash
npm run verify
```

This checks ~14 things: MCP registration, agent rules, the systemd/launchd
services, the database, backend health on `:3456`, the MCP endpoint, the REST
API, and a **memory-hub probe** (`/xmem/status`). The hub probe is
non-failing — it reports your hub config but won't fail the run if the hub
isn't configured yet. Confirm the backend and services are green; the hub goes
green after Part 5.

Sanity check the backend directly:

```bash
curl http://localhost:3456/health
```

---

## Part 2 — Refresh your AI tool's MCP connection

The new hub tools (`hub_status`, `xmem_enable_sync`, `shared_task_publish`, …)
only show up after your AI tool reloads its MCP server list.

```bash
npm run setup:mcp
```

Pick your client when prompted. It writes the right files for each:

| Client | MCP config | Agent rules |
|---|---|---|
| **Claude Code** | `.mcp.json` (CWD) + `~/.claude.json` trust entry | `~/.claude/CLAUDE.md` |
| **Cursor** | `~/.cursor/mcp.json` | `~/.cursor/rules/priority-forge.mdc` |
| **Droid (Factory)** | `~/.factory/mcp.json` | `~/.factory/AGENTS.md` |
| **Codex** | `~/.codex/config.toml` | `~/.codex/AGENTS.md` |

The default transport is HTTP straight to `http://127.0.0.1:3456/mcp` — no
proxy needed on the same host.

> **Restart your AI tool completely.** Not "reload window" — fully quit and
> relaunch. MCP servers are loaded once at session start.

### 2.4 Confirm the tools are visible

Ask your agent to list its Priority Forge MCP tools, and confirm these appear:
`hub_status`, `hub_configure`, `xmem_enable_sync`, `shared_task_publish`,
`shared_task_list`, `shared_task_claim`. If they're missing, you didn't fully
restart — see Part 8.4.

---

## Part 3 — Get your hub credentials

The admin provisions the shared Postgres+pgvector hub once and generates a
**per-agent token** for each teammate. You'll get a row that looks like:

```json
{
  "agentId": "you@yourteam.com",
  "agentToken": "•••• (32-byte secret) ••••",
  "configSnippet": "xmem_enable_sync({ backend: 'pgvector', teamId: '…', agentId: 'you@…', connectionString: '<paste connection string here>', agentToken: '…' })"
}
```

…plus the team-level `connectionString` (a `postgres://…` URL) and the
`teamId`, distributed alongside.

Handling rules:

- **Out-of-band only.** Receive it via password-manager share, Signal, or in
  person. Never email it, never paste it in a shared channel, never commit it.
- **Store it in your password manager.** Tokens are **not recoverable** — only
  their SHA-256 hash is kept server-side. If you lose it, the admin has to
  rotate (Part 8.3).
- The `agentToken` is what authenticates every write/claim you make. The hub
  verifies it against the stored hash before accepting anything.

---

## Part 4 — Audit scope BEFORE you flip the switch

This is the step that prevents leaks. Do it **before** Part 5, because once
sync is on, new mutations start emitting immediately.

### 4.1 The hub spans ALL your workspaces

There is no "sync only workspace X" toggle. Once enabled, events from **every**
workspace you touch flow to the hub (subject to privacy flags below). List them:

```
list_workspaces
```

Review each. If you have a personal or client workspace whose mere activity
("task updated at 2am") you don't want teammates seeing, plan to mark its
projects/tasks private (next).

### 4.2 Default visibility is PUBLIC

Memory-plane events default to team-visible. Opt specific things out:

```
set_project_privacy({ project: "personal-ops", private: true })
set_project_privacy({ project: "job-hunt",     private: true })
set_task_privacy({ taskId: "<id>", private: true })
```

Common defaults: `personal-ops` and `job-hunt` are usually worth marking
private. A private project's tasks never emit; a private task never emits even
in a public project.

### 4.3 Config-file permissions

Your hub config lives at `~/.priority-forge/team-hub.json` and holds your
`agentToken`. The enable call writes it mode `0600` automatically. If you ever
hand-edit it, restore the perms:

```bash
chmod 600 ~/.priority-forge/team-hub.json
```

`npm run doctor` will warn if the perms are too open (it specifically checks
`team-hub.json` mode; `npm run verify` covers everything else).

---

## Part 5 — Enable the hub

Paste the `configSnippet` from your token row into your AI session and fill in
the connection string. It looks like this:

```
xmem_enable_sync({
  backend: 'pgvector',
  teamId: '<your teamId>',
  agentId: '<your-email>',
  connectionString: 'postgres://…   ← paste the one you were given',
  agentToken: '<your agent token>',
  syncIntervalMs: 60000
})
```

> Use the **exact snippet** from your row — it already contains your
> `agentToken` (a secret the agent can't reconstruct). Paste the literal call;
> don't retype it from memory.

**`backend` must be `pgvector`.** Do **not** use `xmem` — the xtrace.ai backend
is still stubbed (see `docs/PLAN_XMEM_ACTIVATION.md`); calling it today silently
no-ops and nothing persists.

This one call flips on everything:

- `memory.enabled = true` — your events flow to the shared memory plane.
- `pulse.emit = true` — auto-enabled for you; your local mutations emit forward.
- `orchestration.enabled = true` — auto-enabled for the `pgvector` backend; you
  can now publish and claim shared tasks.
- The hub **reader** starts polling the team feed every `syncIntervalMs`
  (default 60 s).

### 5.3 Confirm

```
hub_status
```

Expect:
- `ok: true`
- `memory: { enabled: true, backend: "pgvector", teamId, agentId }`
- `orchestration: { enabled: true, supportsOrchestration: true }`
- `health: { ok: true }`
- `warnings: []`

If `health.ok` is false or you see warnings, jump to Part 8.

---

## Part 6 — Bootstrap your in-flight work (day-1 curation)

Because the hub is forward-only, your teammates won't see anything you're
*currently* working on until you tell them. This is a quick, deliberate step —
not a bulk dump.

### 6.1 What auto-syncs from now on

Every new `create_task` / `update_task` / `complete_task` /
`log_context_switch` / project change emits to the memory plane automatically.
You don't manage this — just work normally.

### 6.2 What does NOT sync

Your pre-existing local tasks. There is **no backfill** — by design, so you
don't blast years of stale local tasks into the shared feed on day one.

### 6.3 Publish what's actively shared

Walk your current `in_progress` and high-priority (`P0`/`P1`) `not_started`
tasks. For each one a teammate needs visibility into:

```
shared_task_publish({ localTaskId: "<id>" })
```

Publishing **auto-claims the task to you**, so your in-flight work stays yours
but is now visible in the team inbox as "claimed by you." Skip anything stale,
personal, or not actively being worked.

### 6.4 Don't double-publish

There is **no content-level dedupe.** If you and a teammate both have "Fix the
auth bug" locally and you both publish, the hub ends up with **two** separate
shared tasks. Before publishing something you suspect is already tracked,
check the inbox and coordinate:

```
shared_task_list({ status: "claimable" })
shared_task_list({ status: "claimed" })
```

---

## Part 7 — Day-to-day after enabling

### Memory plane (passive)
Just work. Your mutations emit. To see what the team has been doing:

```
xmem_get_team_activity({ limit: 50 })
xmem_get_blocking_me            # things others are doing that block you
xmem_get_i_am_blocking          # your work that blocks others
```

### Orchestration plane (active)

```
shared_task_list({ status: "claimable" })      # the team inbox
shared_task_claim({ sharedTaskId: "<id>" })    # atomic — exactly one winner
shared_task_renew_lease({ sharedTaskId: "<id>" })  # before your lease expires
shared_task_release({ sharedTaskId: "<id>" })      # hand it back / off
```

Claiming **materializes a local task projection** (status `in_progress`, linked
by `sharedTaskId`) so your normal PF ranker and ML treat it like any other work.
Claims are an atomic compare-and-set: if two people claim at once, exactly one
wins and the other gets `already_claimed` with the current holder.

### Pull cadence
The reader polls every `syncIntervalMs` (default 60 s). There are **no
webhooks** — discovery is poll-based. If you're in a fast hand-off, you can
lower the interval by re-running the enable call with a smaller
`syncIntervalMs`.

### Your local queue is unchanged
`get_priorities` / `get_top_priority` still span **all your workspaces** and
rank with your personal weights. The hub doesn't change how your own queue
works.

---

## Part 8 — Troubleshooting

### 8.1 `hub_status` warnings
| Symptom | Meaning | Fix |
|---|---|---|
| `health.ok: false` | Backend unreachable or bad credentials | Re-check `connectionString` + `agentToken`; confirm the admin registered your agent |
| `memory on but pulse.emit=false` | Memory enabled but events not emitting | Re-run the `xmem_enable_sync` snippet (it sets `pulse.emit: true`) |
| `forceNoopEnv: true` | `XMEM_FORCE_NOOP=1` is set in your env | Unset it and restart the backend |
| `pgvectorCredsComplete: false` | Missing connection string or token | Paste the full snippet incl. `agentToken` |

### 8.2 `Permission denied` reading team-hub.json
```bash
chmod 600 ~/.priority-forge/team-hub.json
```

### 8.3 Lost your token
Tell the admin. Recovery isn't possible — the admin re-runs the bootstrap to
rotate your token, then re-sends your row. Re-paste the new snippet.

### 8.4 Your AI doesn't see the new MCP tools
You didn't fully restart it. Quit completely (not reload-window) and relaunch.
Confirm with `npm run verify` that MCP registration is green.

### 8.5 A local task didn't show up in the hub
Check, in order:
1. Is sync enabled? `hub_status` → `memory.enabled: true`, `pulse.emit: true`.
2. Is the task or its project marked private? (Part 4.2)
3. Is `backend` actually `pgvector`? On `xmem` (stubbed) or `noop`, events are
   silently discarded.
4. For *inbox* visibility (not just activity), did you `shared_task_publish` it?
   Memory-plane activity ≠ a claimable shared task.

### 8.6 You're running your AI inside a Docker container
The host backend is reachable from a container — see
[`DOCKER_ACCESS.md`](./DOCKER_ACCESS.md). Point your container's MCP config at
`http://host.docker.internal:3456/mcp` (Linux needs
`--add-host=host.docker.internal:host-gateway`).

---

## Appendix A — What the admin handles (so you don't have to)

- Ran `scripts/bootstrap-hub.ts` once to provision the Postgres+pgvector hub,
  register each agent's token hash, and emit the `team-tokens-*.json` rows.
- Holds that file at mode `0600` and distributes one row per teammate
  out-of-band.
- Owns hub uptime, schema migrations, and token rotation.
- Does **not** merge, approve, or curate your day-to-day emissions — there is no
  such step.

## Appendix B — The MCP tools you'll actually use

| Tool | Purpose |
|---|---|
| `hub_status` | Inspect your hub config, health, warnings |
| `xmem_enable_sync` / `xmem_disable_sync` | Turn the hub on / off (kill switch) |
| `shared_task_publish` | Promote a local task to the shared inbox (auto-claims to you) |
| `shared_task_list` | See the team inbox (`status: "claimable"`) |
| `shared_task_claim` | Atomically take a shared task; materializes a local projection |
| `shared_task_renew_lease` / `shared_task_release` | Keep or hand off a claim |
| `xmem_get_team_activity` | Read the shared activity feed |
| `xmem_get_blocking_me` / `xmem_get_i_am_blocking` | Dependency/blocker views |
| `set_task_privacy` / `set_project_privacy` | Opt specific work out of sync |
| `list_workspaces` | Review what's in scope before enabling |

`hub_configure` exists for fine-grained control of individual planes, but you
shouldn't need it — `xmem_enable_sync` is the one-call path for getting onto the
hub.
