Skip to content

Collaboration reference

How the-loop works with humans and other personas. The governing rule: every decision or opinion taken from a human MUST have a paper trail on the ticketing system or the PR.

Paper-trail rules

  • Planning questions/opinions/decisions → asked and recorded as comments in the ticketing system (GitHub issue / Jira), not resolved silently in files.
  • PR reviews → happen on the PR as comments and replies.
  • Self & critic reviews → also as PR/ticket comments.
  • Notifications/escalations when a human action is pending → published by the graph's notify hook on the event bus and delivered to a channel: the Slack bot under the CLI config's channels.slack, which posts every event its subscribe list names (issue-245, issue-309). No event → role filter lives in the harness config any more (issue-352); a role a node names rides along as detail and is not a delivery address — per-person routing is not built (issue-304). Notification only; the decision itself still lands as a comment.

Where questions go, and where artifacts are iterated (issue-134)

Two rules. The first is configurable, because only the operator knows where they are. The second is not, because it is a property of the artifact.

1. The interaction channel is declared, not guessed

A session driven by the CLI daemon is told where its answers come from, via routing.interaction.mode in the operator's cli-config.yaml (rendered into every event/spawn prompt as $interaction_directivereference/automation.md):

  • work-item (default) — do not assume a human is watching this terminal. Every question, clarification, decision or approval is asked as a comment on the work item or its PR, by running the-loop ask --work-item <ref> --question '…' (issue-208): the verb posts the comment, stamps the loop-prevention marker centrally (see the rule below — no agent memory involved), and records the wait as a session.awaiting_input event, which is what lets the control plane surface the question (attention, the dashboard's card) and answer it straight into the session (POST /api/v1/sessions/reply). The session then stops and waits: the reply reaches it as the next event. Only if the CLI is unavailable, post the comment with gh and mark it yourself. Never block on an interactive prompt, and never read silence as consent — if genuinely blocked, log the conflict, escalate once, and move to the next available work.
  • cli — a human is attached to this session's terminal, so ask there. This does not waive the paper trail: the outcome of every human decision still lands on the work item as a comment.

When no daemon is involved (a human ran /the-loop:work-on themselves), the human is by definition at the terminal — that is cli behaviour, with the same paper-trail obligation.

Every channel is a peer on one event bus, and the work item is the ledger (issue-245, issue-309, decision-103). When the operator's CLI config declares channels (today: a Slack bot — channels.slack), everything the loop says is an event: the ask, the graph's notifications (an approval request carries the work item's link and an excerpt of the artifact; work-item-complete fires at the end), and the comments the ledger's ingress sees (comment.agent — your own marked comments; comment.human — an authorized user's or collaborator's). A channel receives the ones its subscribe list names, renders them natively (Slack: Block Kit, a link button, Approve / Request changes buttons where a press can be received — and, under the control.command grant, an Execute button on the phase-selection checklist and a Start button on a kickoff's reply, each a press of the configured keyword whose outcome is written back onto the message, issue-337; a text longer than maxChars arrives as a structural digest — the ask first, choices numbered, code and traces as pointers, cut at a sentence, the link for the rest — never a mid-sentence cut, issue-338) into one thread per work item, rooted on the work item — opened the moment the work item starts (issue-317), root only, with every event a reply into it (issue-312) — and the ledger records every event that started elsewhere first — the ask's record is the question comment. What a message on a channel may become is the channel's publish grant: session input by default (work-item.reply: mirrored onto the work item as the-loop's own marked, defanged comment, then delivered into the waiting session); by grant, an answer to an open human gate (gate.feedback — read through the dispatcher's own graph coupling, and, when the pipeline cannot read the gate at all, deferred to the ledger under the same grant rather than mirrored where the gate never looks; issue-321) or a control keyword (control.command) — both recorded on the ledger as an unmarked comment under the operator's credential, carrying an envelope that names the person, so the ledger's own ingress classifies or executes it through the guards a typed comment goes through and the artifact's approvedBy names the person; or a new work item (work-item.create, a top-level message opening an issue in the repository its own first line names — a <repo>: prefix resolved against the declared set, kickoff.repo and the poll sources, refused rather than guessed when it resolves to none or several; issue-341). Who may speak on any channel is the one list routing.authorizedUsers, each entry a person with their id on every channel. What has no thread — a work item that has not started, a standing session, the instance itself — is reached by the /the-loop slash command (issue-334): its work-item verbs are control.command through the same ledger record, and status / restart / upgrade and the standing verbs call the core facade under the instance.command / standing.command grants. Whatever channel carried the conversation, the ticket carries the record — and a channel advances the loop through the ledger, never around it. The operator's map of every mode is docs/guide/slack.md.

2. RULE: a generated artifact is iterated on a durable, reviewable surface

Once an artifact of the chain exists — brainstorm.md, requirements.md/bugfix.md, design.md, testing-plan.md, tasks.md — iteration on it happens only through comments on a surface that outlives the session: the pull request that carries it, or the ticket. In both interaction modes. What is never permitted is iterating on it interactively, where the reasoning vanishes with the scrollback — that is the invariant, and it is not configurable (decision-051 §5, amended by decision-069).

Which of the two durable surfaces applies is this work item's own choice, made by its author at phase-selection (issue-183) — and it applies to the outer loop only:

The outer-loop-on-pull-request boxOuter loop's artifacts are iteratedTypical work item
unticked — the defaultas comments on the work item, Jira-stylework whose code lands in other repositories, so a PR in the origin repo would only ever hold a discussion
tickedas review comments on the PR carrying them, in the repository the ticket was created insingle-repo work, where that PR also carries the code
  • Commit and push the artifact either way, and link the checked-in file from the ticket; never paste its contents into a comment. The choice is where it is discussed, not whether it is checked in — every gate in the process graph reads files.
  • It is deliberately not a setting in harness-config.yaml or cli-config.yaml: it is a property of the work item, and it is frozen by the same signed reply that freezes the phase selection.
  • A contribution is not offered the box at all (issue-199): pdlc-contribution-loop joins a work item somebody else is already running, so there is no outer loop to place — its one contribution.md is iterated on that thread, and the code arrives as an ordinary pull request on the repository it targets. A review (issue-279) and an ad-hoc task (issue-225) run no phase-selection gate at all, so neither ever meets the question: the thread is their whole surface.
  • This is the reference, don't duplicate rule (SKILL.md) reaching its conclusion: the artifact is a checked-in file, and the file, the discussion and the approval belong in one place.
  • The inner loop has no surface to choose. A pull request's pdlc-pr-loop is iterated on that pull request — review comments, review threads, its own approval.
  • Planning questions asked before any artifact exists still follow rule 1.

See docs/decisions/decision-051.md for why the mode is a two-value enum, and docs/decisions/decision-069.md for why the artifact rule now names two surfaces rather than one.

RULE: mark every comment/reply as the-loop's own (loop prevention)

The webhook/poller trigger paths react to their own repo's activity, but the harness posts comments/reviews/replies under your own credentials (no separate bot token — decision-023's operating model), so by author alone the-loop's own reply is indistinguishable from something you typed. Left unmarked, it would re-enter the loop as new input on the next poll/webhook cycle, resuming the session that just wrote it — which may reply again, forever.

GitHub (and every ticketing system the-loop targets) attaches no queryable custom metadata to a comment or review — the body text is the only channel available. So:

  • Every comment, PR review, and reply the-loop posts (issue comments, PR conversation comments, PR review comments/replies, review submissions — anywhere this session writes a reply, not just review findings) MUST end with two things, appended after a blank line:
    1. The exact, invisible marker <!-- the-loop:agent-comment --> — an HTML comment, so it never clutters the rendered thread. This exact string is what the_loop.authz.is_self_authored matches on; do not paraphrase or omit it, and do not use it verbatim in a comment that is not the-loop's own.
    2. A short, visible attribution line so a human reading the thread also knows — reuse the round's [<harness>/<model>] prefix (reviewing.md) where one already applies; a plain 🤖 _the-loop, autonomous reply_ otherwise.
  • This applies everywhere GitHub-style credentials post on your behalf — issues, PRs, and (once supported) Jira or any other ticketing system — not only GitHub.
  • It applies to both producers, not just this session. The CLI daemon posts comments of its own (the interactive-session announcement, the_loop.announce; the reply route's delivery report, issue-208) with the same credentials, so they carry the marker too — via the_loop.authz.mark_self_authored, the producer-side counterpart of is_self_authored. Any new daemon-side comment MUST go through that helper; an unmarked one is re-ingested on the next cycle and pasted into the session it was about (issue-104).
  • Questions go through the-loop ask, which stamps for you (issue-208): when the question travels through the verb, the marker and attribution are appended centrally and idempotently — the by-hand rule above is the fallback for when the CLI is unavailable, not the primary path.
  • Only ever mark text the-loop composed. mark_self_authored asserts authorship and the trigger paths silently drop whatever carries the marker — never apply it to payload-derived text or another author's words.
  • Do not rely on this for anything else. It identifies authorship for loop prevention; it is not an authorization mechanism and does not replace the authorized-actor guard (security.md).

See docs/decisions/decision-031.md and cli/the_loop/authz.py for the CLI-side enforcement (both the webhook router and the poller drop a marker-carrying event before dispatch, regardless of who technically posted it) — the same module now holds the marking helper, so what the-loop writes and what it recognises cannot drift apart.

Conflicts & assumptions (keep unattended runs moving)

docs/decisions/ captures deliberate decisions; unattended runs also hit ambiguities and conflicts mid-flight (a missing field, contested ownership, an unexpected tool failure, an assumption the agent had to make). Those must neither block the loop nor vanish. Rule:

  • Resolvable with a reasonable default → assume and continue. Record the assumption in the append-only conflict log and keep going; do not stall the whole run on one low-stakes ambiguity.
  • Genuinely blocked → log, escalate once, move on. Record the conflict, escalate once via the paper trail (ticket/PR comment + a conflict-escalated notification), and proceed to the next available work rather than spinning.

The log is docs/decisions/conflicts.md (append-only). Each entry is one line: timestamp · phase · one-liner · status (assumed / escalated / resolved). It gives the human a precise, reviewable trail of every judgment call the agent made on their behalf.

Personas, roles and groups

  • The full list of available collaborators is defined up-front in the repo, in .the-loop/collaborators.yaml — the SINGLE source of truth for who works on this project and in which roles, validated against the plugin's collaborators.schema.json (issue-82, decision-035; the former config.personas/config.messaging keys are retired). CODEOWNERS-like: these are the stewards of the repository.
  • A collaborator may be an individual or a group (e.g. a GitHub team @org/team). A single user may hold multiple roles.
  • Supported roles: product-manager, architect, designer, engineer, qa, reviewer, approver.
  • A collaborator declares no delivery of their own (issue-304). Roles are what every filter and every phase gate targets; the notification itself goes to the one Slack bot configured under the CLI config's channels.slack. A collaborators.yaml still carrying the retired per-person notifications block is refused by the schema, with channels.slack and the-loop migrate-config named in the message.
  • Human identity is declared in exactly one place, hand-maintained and not in this file: routing.authorizedUsers — one entry per person, carrying their GitHub login (who may arm and command a work item) and, since issue-309, their Slack member id (whose thread reply, button press or /the-loop command is acted on). The retired channels.slack.authorizedUsers is refused at load.
  • A "work-item collaborator" is a different thing entirely (issue-307). This file's collaborators are the project's stewards and their roles, read by the plugin. A work-item collaborator is runtime state the daemon reads: an authorized user typing the-loop add-collaborator @login on one work item grants that login the right to be input on it — their comments reach that item's session — and nothing else. It is neither hand-maintained nor declared here, it is not a role, and it grants no authority: a work-item collaborator cannot command the-loop, arm a session, or answer a phase gate. See reference/automation.md.
  • The CLI daemon never reads this file (decision-032): it watches many repositories and belongs to none of them — see reference/automation.md.

RULE: identify collaborators up-front

Every work item MUST clearly identify the collaborators it needs at the start (in its spec front-matter collaborators). More collaborators can be added later as needed.

Not every task needs every persona

Match personas to the work:

Work typeTypical collaborators
Architecture-significant changearchitect (+ PM, engineer)
UI/UX changedesigner (+ engineer)
Product/requirements workproduct-manager (+ architect)
Simple bug fixengineer only
Content/copy fixreviewer (often no engineer)
Release/QA sign-offqa, approver

Pull required reviewers/approvers for each phase from collaborators.yaml by role.

For UI/UX work, the designer reviews the UI/UX design artifacts produced in the design phase (Figma links / self-contained HTML prototypes under docs/specs/<id>/design/) — iterating on the rendered output until the designer signs off, with every opinion recorded as a ticket comment (paper trail). See reference/design-artifacts.md.

User-interaction principles (reviewing AI-authored work)

The human often did not write the code and their familiarity with the codebase keeps dropping, so how the-loop communicates is a first-class concern. These rules are fixed — the userInteraction block left the harness config in issue-352.

  • Give enough context to decide. Whenever user input is requested (a planning question, a design opinion, a review), include enough context that the user can make the right judgement call without digging.
  • Condensed, prioritized PR summaries. Every PR the-loop raises tells the reviewer where to focus and in what order — reviewing a huge AI-authored PR top-to-bottom is not realistic. Lead with the highest-priority items to scrutinize. This briefing is produced from the-loop's internal ${CLAUDE_PLUGIN_ROOT}/skills/the-loop/templates/pr-briefing.md and posted/updated in the PR BEFORE human review is requested — a required item of the ready-to-ship gate, so it triggers on every PR (see workflow.md).
  • RULE: all diagrams are mermaid. PR summaries, design docs and educational snippets use mermaid diagrams to explain low-level details.
  • Document insights & decisions in the PR description. Capture the insights from taking the spec to implementation and every low-level decision the harness had to make, so the user sees the reasoning, not just the diff.
  • RULE: educate the user (mandatory, not optional). As the user's familiarity with the code drops, use every opportunity to teach them the low-level design decisions. This is intentional and required, not a nicety. Enforcement: it is not left to chance — the reviewer briefing is a required item of the ready-to-ship gate (workflow.md), so "request human review" cannot happen without the education step having fired.

Working with other tools (MCP / CLIs / plugins)

the-loop is allowed to freely interact with the MCP tools, skills and plugins available in the harness — Jira via MCP, GitHub via gh, plugins such as superpowers. Nothing declares them in the config: discover what the harness offers (its MCP servers, plugins, skills and CLIs) and check before assuming a capability is or isn't available.

Released under the MIT License.