Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,19 @@ OTEL_CAPTURE_IO_MAX_CHARS=8000
# ── Agent layer (optional; PRD §5) ───────────────────────────────────────────
# A board agent above eight role agents. OFF by default — it is the one part of
# this repo written for it rather than extracted from production, so it does not
# stand in front of a first-time reader. It cannot write: role agents get a
# read-only adapter wrapper, and Pass 2c remains the only writer.
# stand in front of a first-time reader. Role agents never write: they get a
# read-only adapter wrapper. With BOARD_AGENT_WRITES off — the default — Pass 2c
# remains the only writer.
AGENTS_ENABLED=false
# Items handed to a role agent per run. Each is a model call.
AGENT_MAX_DELEGATIONS=8
# Let the BOARD agent perform the writes instead of Pass 2c. OFF by default.
# Off, no model is in the write path at all — no write tool exists to reach.
# On, the board agent writes the already-gated plan through a governed adapter,
# which is what PRD §5 means by "authority to write" and what production runs.
# Every write it originates is re-run through the same deterministic gates, so a
# write those gates refuse becomes a hold. Requires AGENTS_ENABLED.
BOARD_AGENT_WRITES=false

# ── Dispute arbiter (optional) ────────────────────────────────────────────────
# Resolve a Pass 2a-vs-blind-read write-level dispute against live tracker state
Expand Down
52 changes: 34 additions & 18 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ npm run demo -- --agents # replays the agent recording, offline
|---|---|---|
| How many | one per run | one per archetype |
| Decides | which items need a closer look | how an item reads to its owner |
| Tools | none — it orchestrates | `get_task`, `get_task_comments`, `search_tasks` |
| Can write | **no** | **no** |
| Tools | none by default; the read tools **plus writes** under `BOARD_AGENT_WRITES` | `get_task`, `get_task_comments`, `search_tasks` |
| Can write | **no** by default · **yes, through the gates** under `BOARD_AGENT_WRITES` | **no**, in every configuration |
| Built from | `boardAgent.ts` | the loop + its profile + its state |

A role agent is the existing tool loop given three things that already existed: its **profile**
Expand All @@ -29,7 +29,8 @@ memory, and `readOnlyTracker` as its tools. See [ROLES.md](ROLES.md).
## Where it sits

```
… 2a categorization → 2b contract check → [ AGENT LAYER ] → 2c execute → 2d audit
default … 2a → 2b → [ AGENT LAYER ] → 2c execute ───────────→ 2d audit
BOARD_AGENT_WRITES=1 … 2a → 2b → [ AGENT LAYER ] → board agent writes ──→ 2d audit
```

**After every gate, before the writer.** Both halves of that matter:
Expand All @@ -40,16 +41,23 @@ memory, and `readOnlyTracker` as its tools. See [ROLES.md](ROLES.md).

## Two guarantees, both structural

### 1. An agent cannot write
### 1. A role agent cannot write

Not because the prompt asks it not to — because `readOnlyTracker` wraps the adapter and refuses every
`apply()`, and no write tool is offered in the first place. Prompt text is a request; a wrapper is a
guarantee. A model that has been jailbroken, confused, or fed a malicious transcript still has no
code path to a mutation.
code path to a mutation. This holds in every configuration; there is no flag that gives a role agent
a write tool.

**Pass 2c remains the only writer, and it has no model in it.** The agent decides; deterministic code
executes. That is also what production does: its board agent *proposes*, and a script enforces the
protected-status guard, the duplicate check and read-only mode.
**By default, Pass 2c is the only writer and it has no model in it.** The agent decides;
deterministic code executes.

**`BOARD_AGENT_WRITES` changes who performs the write, and only for the board agent.** On, the board
agent is handed the already-gated plan plus write tools, and writes it through `governedTracker` —
which re-runs every deterministic gate over anything it originates, so a write the gates refuse
becomes a hold rather than a card. That is the shape PRD §5 describes and the shape production runs.
Off — the default — none of that code is in the process at all. The guarantee in each mode is stated
exactly in `SECURITY.md`, including how the second one is smaller than the first.

### 2. An agent cannot claim a write that did not happen

Expand Down Expand Up @@ -131,16 +139,24 @@ test, not by recording"** — and a reader who wants to see it fire should run t
### This is what "authority to write" means

The internal spec this repo was built from describes the Board agent as *"the orchestrator above the
role agents, holding board state and authority to write."* Read literally that sounds like a write
handle, and building it that way would put a model in the write path and cost the guarantee the
README leads with. (That spec is private and not shipped in this repo — the quote is given here in
full so the argument stands on its own without it.)

**Production does not work that way either.** Its board agent proposes, and one script enforces the
protected-status guard, the duplicate check and read-only mode. "Authority to write" there means *its
decisions result in writes* — not that it performs them. Pass 2c is this repo's equivalent of that
script. Proposing into the gates is the faithful port: the agent genuinely decides, and something
deterministic and auditable is still the only thing that writes.
role agents, holding board state and authority to write."* (That spec is private and not shipped
here — the quote is given in full so the argument stands without it.)

**Production means that literally.** Its board agent runs a create command, and a guard layer decides
whether the command lands: the protected-status guard, the duplicate check, read-only mode. The agent
performs the write; the guards govern it. An earlier version of this file claimed production's board
agent only *proposed* and never performed writes. That was wrong, and it mattered — it was used here
to argue that a read-only board agent was the faithful port when it was actually the divergent one.

`BOARD_AGENT_WRITES` is that shape, ported. On, the board agent writes through `governedTracker`,
which is this repo's equivalent of that guard layer: every write it originates is rebuilt into a
manifest item and re-run through the same gates the pipeline's own answer faced.

**The default is still off, and that is a deliberate smaller claim rather than the faithful one.**
Nothing here has governed a real board for months, the way the pipeline has. Defaulting a model into
the write path of a repo people clone and point at their own tracker is not a claim this repo has
earned. Off, no model reaches the tracker at all and the README's headline property is literal; on,
it is the production shape and `SECURITY.md` states precisely what narrows.

An earlier version of this layer could change one prose field. That was safe, and it was not
orchestration.
Expand Down
9 changes: 6 additions & 3 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ means.
| — | evidence prefetch | Fetch card history for the candidates 2a will need. Host-side, so 2a is a plain completion. |
| **2a** | categorization | `NEW_TASK` / `DUPLICATE` / `SUBTASK` / `UPDATE` / `RELATE`, against the live board. |
| **2b** | contract check | An independent **blind** re-derivation. Disagreement becomes a human hold. |
| **2c** | execute | The only writer. Deterministic — **no model in the write path.** |
| **2c** | execute | The writer. Deterministic — **no model in the write path.** `BOARD_AGENT_WRITES` substitutes the board agent here; see AGENTS.md. |
| **2d** | audit | Did the board end up how 2c said it would? |

Passes 0–1.7 read; 2a–2b decide; 2c writes; 2d verifies. A model never touches the write itself — 2c
takes a plan and applies it, which is why a wrong write requires a wrong *plan*, not a stray token.
Passes 0–1.7 read; 2a–2b decide; 2c writes; 2d verifies. By default a model never touches the write
itself — 2c takes a plan and applies it, which is why a wrong write requires a wrong *plan*, not a
stray token. Under `BOARD_AGENT_WRITES` the board agent performs the write instead, and the
equivalent statement is that a wrong write needs a wrong plan that *also survives every gate a second
time*.

## Pass 2b is blind, and that is the headline claim

Expand Down
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ to what runs internally, the tuned few-shot examples are replaced with generic o
[EXTRACTION.md](EXTRACTION.md)).

**Pipeline.** Eight passes, 0 through 2d: cleanup, inventory, critic, consolidator, categorization,
a **blind** contract check that never sees the categorization answer, the only writer, and a
a **blind** contract check that never sees the categorization answer, the writer, and a
post-write audit. Eight offline scenarios replay real recorded model responses through the real
prompts, parsers and gates — `npm run demo`.

Expand Down Expand Up @@ -89,6 +89,14 @@ live source to running the pipeline over it, planning by default, writing only w
It may propose a different category, list, assignee or description; every proposal is re-run through
the same gates, so a proposal the gates refuse becomes a hold, never a write.

**Board-agent write authority (`BOARD_AGENT_WRITES`), also off by default.** PRD §5 gives the board
agent "authority to write", and production means that literally — its board agent runs a create
command behind a guard layer. This flag is that shape: the board agent gets write tools behind
`governedTracker`, which rebuilds every write it originates into a manifest item and re-runs the full
deterministic gate set over it, so a write the gates refuse becomes a hold. Off, Pass 2c writes and no
model reaches the tracker at all. The prompt-injection guarantee differs between the two modes, and
`SECURITY.md` now states each one exactly rather than stating the stronger one twice.

**No accuracy claimed.** Volume and hold rate are reported from 711 real items across 49 production
runs; precision and recall are not, because the only alternative to a hand-labelled ground truth that
doesn't exist is a model grading a model. See [LIMITATIONS.md](LIMITATIONS.md).
6 changes: 3 additions & 3 deletions EXTRACTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ this line is how you work out whether the other already has it.
| | Production | Here | Why |
|---|---|---|---|
| Passes 2a/2b | Tool-using agents that can fetch extra card history on demand | Plain completions; all evidence pre-fetched host-side. An optional agent layer sits *above* them, off by default | The default path costs duplicate recall on semantically-worded matches; the whole-board Jaccard backstop and the evidence-citation hold gate catch the fallout as human holds, not silent creates. |
| The agent loop itself | Delegated to a separate agent runtime — the pipeline is a *client* of it, over CLI and HTTP | **Written for this repo**, on the existing read-only tool loop | The only component here that is not an extraction. The production loop is a different product whose prompts read internal workspace files; porting it was neither possible nor in scope. Stated plainly in `LIMITATIONS.md`, and the reason `AGENTS_ENABLED` defaults to false. |
| Read-only enforcement | Structural, via a wrapper script that refuses write subcommands | Enforced at the adapter | Same guarantee, fewer moving parts. |
| The agent loop itself | Delegated to a separate agent runtime — the pipeline is a *client* of it, over CLI and HTTP | **Written for this repo**, on the existing tool loop (read-only by default; the board agent gets governed write tools under `BOARD_AGENT_WRITES`) | The only component here that is not an extraction. The production loop is a different product whose prompts read internal workspace files; porting it was neither possible nor in scope. Stated plainly in `LIMITATIONS.md`, and the reason `AGENTS_ENABLED` defaults to false. |
| Write enforcement | The board agent runs a write command; a wrapper script enforces the protected-status guard, the duplicate check and read-only mode as it does | Two adapter wrappers: `readOnlyTracker` refuses every write (role agents, always), `governedTracker` re-runs the full deterministic gate set over each write (the board agent, under `BOARD_AGENT_WRITES`) | Same shape, one layer closer to the thing it guards. An earlier version of this row said production's agent only *proposed*; that was wrong, and it was the sentence used to justify shipping a board agent that could not write at all. |
| Ingestion — transport | 8 webhooks, 14 cron routes, an Express app | A reference wiring, not a port: `npm run poll` (cron-able) and `npm run serve` (signature-verified GitHub/Slack webhooks, re-pulling rather than parsing the delivery payload) | Neither is the production infra — no TLS termination, process supervision, queue durability or horizontal scale. Built to prove `runPipeline(source, deps)` reaches from a real trigger, not to be deployed as-is. |
| Ingestion — reads and shapes | Slack, Gmail, GitHub, Drive and meeting transcripts | Read clients for GitHub, Gmail, Drive and Slack; normalizers for five payload shapes | Reading a service is not the same concern as scheduling the read, and conflating them cost this repo three sources — see below. |
| Which sources the pipeline *accepts* | A closed union — `kind: 'meeting' \| 'channel_sweep'` — on a source struct shaped for those two: `transcript`, `rawTranscript`, `participantLine`, `channelId`. GitHub, Gmail and Drive activity reaches the board through the separate agent runtime, not this pipeline | Five kinds behind one `IngestedSource`, all running the identical Pass 0→2d chain | **This repo generalized the contract; production did not.** Two meeting-only gates are the reason production's is closed — ASR speaker-confidence provenance feeding a Pass 2b legitimacy check, and visual grounding over video frames. Neither was extracted (see below), and with them gone nothing in the pass logic reads source kind except to pick a noun for a prompt. The generalization is real and code-verified, but it is **this repo's**, not a description of what production runs today. |
| Per-person agents | 12 live agent runtimes with their own state and tool access | 8 role *archetypes* — a profile, routing keywords and a state file each, drivable as read-only agents | Archetypes de-identify by construction: there is no real name to strip, because the concept is generic. They are load-bearing either way — the profile shapes the prompt even with agents off. |
| Per-role state | A `STATE.md` and journal per agent, rewritten on a schedule | One JSON file per archetype: what that role currently has open, plus human-maintained context | Same idea, scoped to what a pipeline can honestly maintain. Production's version is an agent's working memory; here it is a memo the pipeline writes after each run and reads back into the next one's prompt. No journal — nothing here would read one. |
| Read-only enforcement in agent passes | An environment variable read by a shell script | A wrapper around the adapter whose `apply()` refuses | Same intent, fewer moving parts, and the guarantee sits next to the thing it guards. |
| Read-only enforcement in agent passes | An environment variable read by a shell script | A wrapper around the adapter whose `apply()` refuses | Same intent, fewer moving parts, and the guarantee sits next to the thing it guards. Unchanged by `BOARD_AGENT_WRITES` — that flag reaches the board agent only; no role agent gets a write tool in any configuration. |
| Tracker client | A 2,034-line bash script shelling out from TypeScript | Typed HTTP adapters for ClickUp and Linear | Most of that script was `jq` shaping. Three pieces were real logic and were carried across; see below. |
| Retrieval | A live vector substrate | A declared `Retriever` interface, wired into 2a/2b; ships `nullRetriever` (default) and `localRetriever` (opt-in, flat-file Jaccard ranking) | Retrieval quality has never been measured for either implementation, so no claim about it would be falsifiable. The interface ships so the architecture visibly accommodates a knowledge layer; the live vector substrate does not, because nothing could be said about it honestly. |

Expand Down
7 changes: 5 additions & 2 deletions LIMITATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,11 @@ That is why `AGENTS_ENABLED` defaults to **false**. What turning it on can and c
- It **can** propose a description, a category, a list or an assignee, and raise an ownership doubt.
- Every proposal is re-run through `applyGates` — the same gates Pass 2b uses, not a copy — so one
the gates refuse becomes a human hold rather than a write.
- It **cannot** write anything, and **cannot un-hold**: agents only ever see items that already
passed the gates, so there is no path from an agent to an item a gate stopped.
- It **cannot un-hold**: agents only ever see items that already
passed the gates, so there is no path from an agent to an item a gate stopped. A **role** agent
cannot write in any configuration. The **board** agent cannot either, unless `BOARD_AGENT_WRITES`
is on — with it on, it performs the write, and every write it originates is re-gated first. See
AGENTS.md, and SECURITY.md for how the injection guarantee narrows in that mode.

Measured across both recordings and all eight scenarios, **no proposal has changed a final
category** — `agentReplay.test.ts` compares each item's final category against Pass 2a's and fails
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ source (transcript | channel | github | gmail | drive)
Pass 1.7 consolidator ─ merge, dedupe, anchor
Pass 2a categorization ─ NEW_TASK | DUPLICATE | SUBTASK | UPDATE, against the live board
Pass 2b contract check ─ a BLIND re-derivation; a genuinely different WRITE holds
Pass 2c execute ─ the only writer. Deterministic. No model in the write path.
Pass 2c execute ─ the writer. Deterministic. No model in the write path.
(BOARD_AGENT_WRITES hands this to the board agent instead.)
Pass 2d audit ─ did the board end up how 2c said it would?
```

Expand Down Expand Up @@ -213,8 +214,14 @@ and demo stays offline because they start from a recorded payload rather than a
**An optional agent layer** sits between the gates and the writer: a board agent that
delegates to eight role agents with **read-only** tools. It is off by default. It may **propose** a
different category, list, assignee or description — and every proposal is re-run through the same
gates, so one the gates refuse becomes a hold rather than a write. **The agent never writes, and
never un-holds.** See [AGENTS.md](AGENTS.md).
gates, so one the gates refuse becomes a hold rather than a write. **Role agents never write, and no
agent ever un-holds.**

A second flag, `BOARD_AGENT_WRITES`, hands the write itself to the board agent — the "authority to
write" PRD §5 describes, and the shape production runs. It is also off by default. On, the agent gets
write tools behind `governedTracker`, which re-runs every deterministic gate over anything it
originates; a write the gates refuse becomes a hold. Off, no model reaches the tracker at all. See
[AGENTS.md](AGENTS.md), and [SECURITY.md](SECURITY.md) for exactly which guarantee each mode buys.

**The rule that makes the tracker seam real:** the pipeline speaks canonical member names and list
keys; only an adapter ever sees a tracker id. Every gate, prompt, parser and the whole categorization
Expand Down
Loading
Loading