Every meeting-notes tool produces fourteen action items from a thirty-minute conversation, and nobody reads a list of fourteen. Meeting to Tasks produces four, says what it deliberately left out, and — because it is the first agent we have built that writes into somebody else's workspace — asks before it files a single one.
What makes a task, and what is just something someone said
The rule the agent applies is narrow on purpose. A task has an owner, an outcome, and a way to know it is done. Everything else is context, and context belongs in the body of an issue rather than becoming its own issue.
“Discuss pricing” is a note. “Decide whether the Pro tier includes seats — Priya, before the board deck on the 14th” is a task. The second one can be finished; the first one can only be had again.
Two constraints keep it honest, and both exist because the failure they prevent is so tempting:
- Names and dates come from the notes, never from the model. If nobody was named, the owner is unassigned. An invented deadline is worse than a missing one, because it looks like a decision somebody made.
- Arguments that did not resolve are listed as unresolved. If two people said contradictory things, it quotes both rather than picking the one that makes a tidier list.
The seeded notes are a minefield, deliberately
The sample the agent opens on is written the way real notes are written: half-sentences, two people talking past each other, and six specific traps — one for each rule in the prompt.
- A contradiction nobody resolved — one person says the trial is 14 days, another says 30, and nobody in the room knew which was live.
- A decision reversed four lines later. An agent extracting linearly files it as a task; one that read to the end does not.
- An action item with no owner(“someone should look at onboarding emails”).
- Something already done and mentioned in passing.
- One stated date and several implied ones, to see whether it invents the rest.
- An admin aside (“expenses due Friday”) that reads exactly like a task and is not one.
A tidy transcript would let the agent look competent while proving nothing. This one makes the difference between reading and extracting immediately visible.
The document is the product; the tracker is optional
It writes tasks.md into your workspace before it touches any tracker, and that file is what you keep whether or not you ever connect one. Both Notion and Linear are optional connections, which is deliberate: an agent that refuses to start until you have linked a workspace cannot demonstrate itself to anyone who has not already committed.
It is also the artifact you argue with. Changing a list in a file is cheap; changing it after fourteen issues exist in a team's backlog is a cleanup job somebody else notices.
Every write stops and asks
This is the part that made the agent worth building, and it is the first time approvals have mattered in this product. Everything before this agent worked inside a sandbox you own — the worst an over-eager run could do was write a file you could delete. This one creates issues other people will read, in a tracker with no undo worth the name.
So the agent's own definition sets the policy rather than inheriting whatever the session happened to be set to:
policies: {
// This agent writes where other people read.
approvalMode: "manual",
},Reads run unattended — searching your tracker for an existing issue on the same subject is not something you want to click through. Writes park, and wait.
Building it found the gate did not cover this
The gate existed. It did not apply to any of the tools this agent actually writes with.
Approval is decided by looking a tool up in our platform registry to see whether it writes. A tool from a connected MCP server is not in that registry, so every one of them fell through to “no approval needed”:
needsApproval("writeFile", "manual"); // true
needsApproval("linear_create_issue", "manual"); // falseThe result was exactly backwards. A run in manual mode would stop and ask before writing a file inside its own sandbox, then create issues in a real team's tracker without a word.
The fix was already in the protocol. MCP servers send an annotations.readOnlyHint with each tool, and we were parsing it and throwing it away. Now we keep it, a write parks like any other gated tool, and a missing hint counts as a write — silence from a third party should read as “might write”, not as permission.
Then the same agent found the follow-up: its manual policy was still being ignored, because the client sends an approval mode on every request and the server could not tell “the user chose automatic” from “the control starts at automatic”. A correct definition, a correct resolver, and a default in the middle quietly winning — which is the shape of every bug in this series so far.
Try it with your worst notes
Not your cleanest ones. Paste in the half-typed mess from a call where two people disagreed and nobody wrote a conclusion, and read the Unresolved section first — that is where the actual value is.