Context Threads

Agents lose the plot across people, machines, and tools. You decide something with Claude on your laptop; your teammate's Codex on the build box never hears about it; tomorrow a fresh session re-litigates it. Context Threads are the shared memory that fixes that — a team-scoped, attributed feed of the decisions, constraints, and contracts that cross the seam between people and components, that any agent (or person) can read and add to.

The MCP server that carries them into an agent is named partyline-context-threads (recall / remember, and the /… slash commands below).

Not a chat, not a transcript. A thread holds durable facts — "we dropped guest checkout for v1", "the API returns created_at in ISO-8601" — not conversation. Small, high-signal, and safe to inject into any agent's context.

The shape

  • Thread — the working feed for one effort. Every thread belongs to a project: a planning session in a project creates its thread automatically on first use (titled after the project), and you can attach an existing thread to a project — or detach it — from the thread's page (thread owner only). Team-scoped and private by default; share it with your team when you're ready. Facts here churn: they get proposed, replaced, pruned.
  • Overview — a singular "what this is about" block (kind overview): 2–4 sentences framing the project/effort for a newcomer. It's pinned at the top of the thread and injected first into an agent's context (orientation before the details); /seed_from_history drafts it, and you can edit it on the web. Update it by superseding.
  • Fact (context block) — one attributed statement, of a kind: decision · constraint · contract · question · note. Every block records who wrote it (a person or agent:<name>), the engine, and when.
  • Project — the durable, curated context for a repo/component. You promote a settled fact from a thread into a project so it outlives the thread and seeds future work.

See it on the web

Threads live under their project: open a project's page and its threads are listed in the Context & planning panel. (The flat list at /threads still exists.) A thread renders as a version-control timeline — a single spine, oldest→newest, so you can read how the shared understanding evolved. Click any block to expand it:

  • A block that replaced an earlier one shows a diff — − old / + new — with the prior value tucked beneath it.
  • Each block shows its provenance: #id · kind · author · engine · time.

Per-block actions (you can edit/prune your own; the thread owner can touch anything):

ActionWhat it does
EditRecords a new revision that replaces the old wording — attributed to you, old text kept in history.
ReplaceSame mechanism, when a newer fact replaces an earlier one.
PruneSoft-delete — drops it from the live context but keeps it in history (nothing is ever hard-deleted).
RevertUndo — restores the prior value of a replace, or prunes a plain add.
PromoteSends the fact to a project's durable context.

Nothing is destructive: replaced, pruned, and superseded blocks stay in the timeline as history.

Use it from the CLI

$ ptln thread new "Checkout rework"      # → prints a thread id
ptln thread share <id> --team <slug>   # share with a specific team (default: private)
ptln thread remember <id> decision "dropping guest checkout for v1"
ptln thread recall <id>                # everything recorded on the thread
  • ptln thread — list your threads
  • ptln thread new "<title>" [--team <slug>] [--share]
  • ptln thread show <id> · ptln thread recall <id> — the thread + its facts
  • ptln thread remember <id> <kind> "<fact>" [--replaces <#>] — kinds: decision constraint contract question note
  • ptln thread share <id> --team <slug> / unshare <id> — a thread belongs to one team (the one you share it with); private threads (your personal space) are visible only to you
  • ptln thread promote <id> <#> <project> — promote a fact into a project
  • ptln thread accept <id> <#> / reject <id> <#> — confirm or drop an ambient-capture suggestion
  • ptln thread archive <id> — hide it from the list
  • ptln thread bind [<id>|--clear] — bind this repo to a thread (.partyline.json, check it in) so every session and run here auto-attaches
  • ptln scribe [--thread <id>] [--session <id>] — distill the newest session in this directory onto its thread (automatic capture, on demand)
  • ptln project new "<label>" · ptln project ls · ptln project show <id>

Wire it into an agent

Launch an agent already attached to a thread and it gets the shared context at startup plus tools to read and add to it:

$ ptln new claude --thread <id>
  • At launch, the thread's current facts are framed into the agent's context as background — "here's what the team already knows", explicitly not a to-do list.
  • During the session, the agent has two thread MCP tools: recall (pull the latest shared context) and remember (record a new fact). The same server also carries the org-scoped peer consult tools, the planning-tree tools (propose_work_item / plan_file_tree), and the run-diagnosis tools — all below. Gemini and Antigravity need a one-time ptln thread connect <engine>.
  • Zero-config for Claude and Codex. The partyline-context-threads server is wired into every Claude or Codex session partyline launches — no --thread, no bind, no setup step. The tools exist the moment the CLI runs, and the thread is resolved lazily, on the first tool call, from the repo's bind. So bind-then-use works in either order: bind a repo while a session is already open and its tools start working, with nothing to restart. (What --thread still adds is the launch primer — the thread's facts in the system prompt from turn one. And in a bound repo, a session launched --no-thread will still re-resolve the repo's thread on first tool use: the repo's declared memory wins over per-launch detachment.)
  • It captures as it works — the launch primer tells the agent to remember decisions/constraints/contracts the moment they happen (not chatter). It's the agent's own turn doing a tool call, so it costs your session a few tokens, nothing on partyline.
  • Seed an existing session — already deep in a conversation? Run the /seed_from_history slash command (an MCP prompt from partyline-context-threads, e.g. /mcp__partyline-context-threads__seed_from_history in Claude Code). The agent reviews the conversation it already has and backfills the durable facts into the thread — no re-typing, no transcript leaves your machine.
  • Import the latest — run /import_context to force the agent to recall the thread now and adopt it as background — for when facts changed, or you just attached mid-session. (Automatic at launch via the primer; this is the on-demand refresh. ctrl-\ c → v shows the context to you.)
  • Facts you record this way are attributed to you (you're piloting), or to agent:<name> for an autonomous party agent.

Ask a teammate's agent (peer consults)

Early access. A thread shares what the team already decided. A peer consult asks a teammate's agent something new: "does this API change break your callers?" Their agent answers on their checkout, read-only, and the answer comes back to yours labelled untrusted — so the humans stop being the clipboard.

The tools live on the same partyline-context-threads MCP server, but they're org-scoped, not thread-scoped — you can consult a teammate whether or not you share a thread:

  • list_peers — which machines you can ask, and which project labels each one answers about.
  • ask_peer(target, project_label, question) — ask one machine about one of its projects. It waits up to 45 seconds for a fast answer, then hands back a consult id rather than holding the tool call open. The peer sees only what you send, not your session.
  • check_consult(consult_id) — collect an answer that landed after that. Once, minutes later — not in a loop. Consults expire about 10 minutes after they're asked. Most consults are answered automatically (see below), usually inside a minute or two; a human only has to approve one when the peer is over budget for the day or has auto-answer switched off.

A question may be up to 32,000 characters (~8k tokens), because the text people actually send is an excerpt pasted out of an LLM discussion, not a sentence. That bound is enforced in one place server-side; the CLI checks it before sending and tells you what to trim, so you never learn you were over from a rejected request after composing the thing.

Both sides need ptln daemon running with the project registered (ptln daemon add-project <label> <dir>) — a question can only be addressed to a label a machine advertises. Inside ptln, ctrl-\ then p is the inbox: ask a peer, read the questions and replies waiting for you, and withdraw an ask of yours that's still out (open the row, then withdraw this question). The question field is multi-line and takes a paste whole — Enter inserts a newline, ctrl-d sends, ctrl-e opens it in $EDITOR and reads it back, esc abandons — with a live character count against the limit. On macOS the menu-bar tray shows queued questions with Approve/Decline; its notifications carry names and counts only, never question or answer text.

$ ptln daemon add-project api ~/dev/api   # advertise a project peers may ask about
ptln daemon consults api                # show this project's answer policy (auto by default)
ptln peer approve <consult-id>          # answer a queued question, read-only
ptln peer decline <consult-id>          # free the asker now instead of at the timeout
ptln peer cancel <consult-id>           # withdraw a question YOU asked (asker only)

Withdrawing a question you asked

ptln peer cancel <consult-id> — or withdraw this question in the ctrl-\ p inbox — takes back an ask you no longer need answered. Until this existed, esc on the wait only handed the ask to a background watcher, which is not a withdrawal: the question stayed answerable for the full 10-minute window and the peer's machine could still spend a read-only engine turn on it.

  • Only the asker can cancel. It's scoped by from_user server-side exactly as reading the handle is, and it goes to the control plane with your account token rather than to any daemon. A consult that isn't yours is indistinguishable from one that never existed. The target of a question can never cancel it — they have decline.
  • The peer's machine finds out. The withdrawal is pushed down that daemon's own event stream, and it drops the question from its pending set; approve-consult on it then fails with withdrawn rather than answering a question nobody is waiting for.
  • An answer turn already running is left to finish. Killing a read-only turn mid-flight buys nothing, so it completes and the control plane discards the answer — the status guard on the answer write sees a cancelled consult and refuses it.
  • Cancelling something already resolved is a no-op that reports the state it's really in. A cancel can never overwrite an answer that landed a second earlier.

ptln peer approve prints the question before it answers, and cannot do otherwise: the daemon requires a digest of the text that was displayed and refuses a mismatch. So there is no way to approve a question nobody read — which is also why the tray shows the question in its submenu.

Answering is automatic by default — what that means

A teammate in your org can cause your machine to run a read-only engine turn on your checkout, using your tokens, without asking you. That is the default (ptln daemon consults <label> auto), and these are its bounds:

  • The answer turn is read-only — it cannot write a file or run a command. That's the engine posture, not a convention.
  • The question is size-limited (32,000 characters), and only members of your org can ask.
  • It's capped per day — past the cap the question queues for your approval instead of being dropped.
  • Every consult leaves a durable row you can review afterwards — /consults in the app is that audit trail, both directions, for the last few weeks. It's deliberately read-only: answering a question needs the target machine's own checkout, so it's a local action (ptln peer approve, the ptln daemon run console, or ctrl-\ p) and a web button would have nothing to run.

To be precise about who approves what: a human is asked only when the day's budget is spent, or when auto-answer is off. Nobody sits and approves each question by hand.

The budget is a project setting

The default allowance is 24 auto-answers per project per day, plus 48 for the whole machine across every project it advertises.

The per-project number is a project settingauto-answer allowance in project settings on the web (projects.consult_auto_daily) — so one edit governs every machine in the project. It used to live only in each box's environment, which meant a launchd/systemd edit per machine.

  • Blank = the built-in default (24).
  • 0 = never auto-answer in this project — every consult waits for a human. That's a real setting, not "unset".
  • The value is validated to 0…200 on the way in, and the daemon re-validates and clamps it on the way out.

The cap is project-wide; the spend is counted per machine. Each daemon keeps its own daily ledger (~/.partyline/daemon/consult-budget.json), so three machines in a project at a cap of 24 can answer 72 questions between them. A single shared allowance would need server-side counting on the enforcement path, which is deliberately out of scope — so the number is said out loud rather than quietly shared.

PARTYLINE_CONSULT_AUTO_DAILY and PARTYLINE_CONSULT_AUTO_DAILY_TOTAL are now ceilings, not setters. Each does two jobs on the answering machine: it's the fallback used when the project has no setting of its own, and it's the ceiling the project's setting is clamped to. The clamp runs downward only — a box can tighten itself below what the project asks for, but nothing arriving over the network can loosen it. Above those, the daemon has compiled hard ceilings of 200 per project and 400 per machine that no setting can exceed. An unparseable value falls back to the default rather than uncapping anything.

Turn it off:

  • Whole machineptln daemon consults --all ask. This is the switch you want. It's a persisted local setting (~/.partyline/daemon/consults-global.mode, mode 0600) that the answering daemon re-reads on every question, so it takes effect immediately — including for the always-on service from ptln daemon install, with nothing to restart or reinstall. It outranks every per-project setting, including projects you register later, and nothing can override it: no per-project auto, no environment variable. ptln daemon consults --all reads it back; ptln settings shows the effective state. --all auto allows auto-answer again, and each project's own setting decides from there.

  • Per projectptln daemon consults <label> ask. Every question about that project then waits for you, in the ptln daemon run console, ptln peer approve|decline, the tray, or ctrl-\ p.

  • Per project, from the web — set the auto-answer allowance to 0. Unlike the two above, this one travels: every machine advertising the label picks it up.

PARTYLINE_CONSULT_AUTO_DAILY=0 still disables auto-answer as a ceiling of zero, but only where the answering process can see it: your shell for ptln daemon run, and an always-on service only if the variable was set when you ran ptln daemon install (install bakes it into the LaunchAgent/systemd unit when set, so changing it later means reinstalling). Prefer the persisted switch, or the project setting.

Policy is checked before budget, so no cap value — however large — can override a persisted ask.

Delivering an answer is not automatic, on purpose

When an answer lands for a session on your machine, the default is stage: the labelled block is pasted into that session's prompt unsubmitted, and it takes your Enter to become a turn. ptln daemon deliver <label> submit presses Enter for you. We'd leave that off.

The read-only guarantee protects the machine answering a question — it gives the machine that asked nothing. Your own session has whatever tools it was launched with, which for normal coding work means write and shell. With submit, a teammate's text becomes a prompt in that agent with no human reading it first. The "untrusted" label is a convention the model is asked to honour, not an enforcement boundary.

The cross-machine flow is new and has not been exercised end-to-end on two separate machines.

Over the A2A protocol

A partyline consult is an A2A task — one agent asks another agent a read-only question about a codebase and an answer comes back — so the control plane speaks the protocol directly, built to A2A v1.0.1. Any A2A client can consult a partyline project:

EndpointWhat it is
GET /.well-known/agent-card.jsonThe public gateway card. A pure function of the origin — it names no project, device, org, or user.
GET /api/v1/a2a/agent-card.json?tenant=…The per-project card. Authenticated, and gated by the same authorization check as a consult, because the existence of a project label is private.
POST /api/v1/a2aSendMessage and GetTask (the v1.0.1 method names, not v0.2/v0.3's message/send / tasks/get).

The spec wants one card per host, but a partyline agent is a (device, project) pair — so the pair rides in AgentInterface.tenant, the spec's own mechanism for many agents behind one endpoint.

A2A is not a bypass. It opens a consult through the same gate as the REST route: the same authorization, the same per-asker rate-limit bucket (so alternating between REST and A2A doesn't double your budget), the same 32,000-character bound, and the same per-project auto-answer policy and daily budget on the answering machine.

Diagnose a run without a browser (read_run)

The same partyline-context-threads server carries two read-only run tools, so an agent can answer "why did this run produce nothing?" without a human copy-pasting out of a logged-in browser:

  • read_run(run_id) — one run's current state: status, preset, engine/model, merge policy, timestamps, token spend, wall time, and every task's index, title, status, branch, and PR — plus the plan item it came from and its position in a chain. A run with no task rows and zero tokens never started, which is often the whole answer.
  • read_run_log(run_id, tail?) — the tail of that run's step output, the worker's own log lines. Default 200 lines, tail clamped at 1000, and the whole block byte-capped at 64 KB with an explicit truncation marker so the model knows when it isn't seeing everything. Secrets are redacted line by line.

Both take the run's UUID (from the run's URL) and need this machine signed in (ptln login). They issue GETs only — there is deliberately no generic "fetch a URL" tool, and the path is built from the validated id, so a model can't steer either tool at another endpoint. A run you aren't entitled to see is reported identically to one that doesn't exist, so run ids can't be probed.

A log body is untrusted, secret-bearing, third-party output. read_run_log fences and labels it as data for exactly that reason: a "helpful instruction" written into a build log by another agent must read as content, not as a directive.

Every agent starts briefed

The thread isn't just for interactive sessions — every agent partyline spawns starts with the project's context, injected before it begins:

        WORK                     REMEMBER                       BRIEF
  ─────────────────      ─────────────────────────      ─────────────────────────
  you · teammates        your project's thread          every agent starts
  · agents          ──▶  decisions · constraints  ──▶   already knowing them:
  (any machine,          · contracts                    planners · builders
   any AI)               (raw sessions stay on           · reviewers
                          your machine — only
                          facts are shared)
  • Interactive sessions (ptln new … --thread) get the launch primer described above.
  • Planning agents in a party are briefed with the project thread's facts before their first turn.
  • Build workers (crank) start each task with the facts relevant to that task — matched by which files and components the task touches — so a large thread contributes the right slice, not a dump.
  • The review agent gets the project's constraints and contracts as check-criteria: things the diff must not violate. Deliberately not the author's reasoning — the reviewer stays independent, but catches changes that contradict established decisions.

The briefing is plain text injected at launch, so it works with every engine partyline runs — including open-weight models — with no per-vendor wiring.

Automatic capture — the scribe

Recording facts by hand (or relying on the agent to remember) is Model A. The scribe is the automatic layer on top: partyline distills a session's conversation into durable facts for you.

  • ptln scribe — distill the current directory's newest session onto its thread, on demand. Each pass picks up where the last left off (a local watermark), so re-running only processes what's new.
  • On session end, thread-attached sessions are distilled automatically.
  • Private by design — the distilling runs on your machine, using your own engine. The raw conversation never leaves it; only the short, durable facts are written to the thread.
  • Scribe-written facts are attributed scribe:<engine> — visible, editable, and prunable like any other fact.
  • Reads Claude Code and Codex session stores today; more engines as their stores are supported.

Record a fact without leaving your session

Inside the session manager, ctrl-\ then c opens the Context menu: record a fact (kind picker + one line), view the live context, or switch/create a thread — all without dropping your agent or memorising commands. It writes straight to the thread as you, so it works in any session.

When the thread changes while you're away

If shared context changes while you're idle, the session manager surfaces a one-line banner at your next lull — "N updates to your team's shared context since you stepped away" — so you can ask your agent to recall. It's never injected mid-turn.

The safety guarantee

Agents only ever see live, current facts. A block that's a pending suggestion, has been replaced, or was pruned is hidden from recall and every launch briefing — so a retracted fact can never silently steer an agent. And every fact is attributed and correctable: anything wrong or stale, edit or prune it on the web and it disappears from every agent's view immediately. The web timeline shows the full history to people; agents get only what's current.

Ambient capture (optional)

A team can turn on an ambient scribe that distills a Party's conversation into proposed facts. These land as suggestions only — hidden from agents until a human hits Accept (ptln thread accept), so an imperfect distill pass is at worst noise in a review queue, never poisoned context. Configure it per team (use partyline's model, bring your own, or leave it off) on your team's settings page.

Next: Parties (agents) · Sessions · CLI reference.