Part 3 · Operate
Working through an agent
Three things worth wiring up, and what each one actually costs.
01What it is
Once an agent is connected it can read your brief, capture what it finds, draft into the approval queue, and react to events without being asked. This page is what to build with that.
02Why it exists
The reason to run this through an agent is not that it is faster to type. It is that the work already happens somewhere else. A founder reads a thread on their phone, has a call, gets a reply in a DM — and none of it reaches the system that is supposed to be keeping track, because reaching it means opening a tab and remembering why.
An agent that is already in the conversation closes that gap. The three examples below are ordered by how much they return for how little they cost, and the first one is worth building on day one.
03How to use it
- 01Copy the MCP address from Settings → Agent access.
- 02Paste it into your agent as an MCP server or custom connector. It sends you back here to choose what it may do.
- 03Start with read-only. Everything below works on it except approving.
- 04Wire the morning briefing. One call, one paragraph, no writes.
- 05Add capture once you trust it: threads and touchpoints, still no writes under your name.
- 06Add drafting last, and read what it queues for a week before considering approval.
- 07Subscribe to draft.ready and window.opened rather than polling for them.
04What good looks like
- You give it
- A morning briefing agent calls get_brief once at 7am with a read-only credential.
- You get
- One response with what is waiting on approval, how many follow-ups are overdue, how many companies are watched, and the accounts where something moved with the dated reason for each.
- Why
- get_brief exists so a briefing costs one round trip rather than four list calls, and a read-only credential cannot write anything even if the agent is confused.
Observed in a real run
05What weak looks like, and what it costs
- You give it
- An agent calls list_companies every thirty seconds to notice when something changes.
- You get
- Rate limited within the minute, and it still learns nothing sooner than the collector produces it.
- Why
- Polling cannot see anything the pipeline has not written yet, so it costs requests and buys latency. A webhook on window.opened arrives when the fact exists.
Derived from the rules in the code
06Read next
Agent tools
The capabilities reachable over MCP and the REST API. Each declares one scope, checked before the handler runs.
| Name | Value | What it does |
|---|---|---|
| list_ventures | no scope needed | The ventures this credential can reach, with their id, name and what each one sells. Call this first when you do not already have a venture id: almost every other tool takes one, and a credential covering more than one venture will refuse to guess. Cheap. |
| get_brief | read:brief | Everything waiting this morning, in one call: accounts where something moved and why, how many drafts need review, how many follow-ups are overdue, and what is being watched. This is the right first call for a daily briefing — it replaces four separate list calls and returns roughly a page of text. Cheap; it reads stored state and generates nothing. |
| list_approvals | read:approvals | Drafts a human has yet to allow: outreach messages and content, with the evidence behind each one. Use this to triage — read the grounding, decide, then call approve_outreach or approve_content per item. Nothing here has been sent or published. Cheap. |
| list_followups | read:followups | People owed a next touch, oldest first, with what was agreed and when they were last contacted. The second message is the one that wins and also the one that gets dropped, so this is usually the highest-value read in the product. Cheap. |
| list_companies | read:companies | Search watched companies by name, domain or window state. Returns the score, the window and the one-line reason, not the full timeline — call get_company for that. Use this to answer "which accounts are hot" or to find a company id. Cheap. |
| get_company | read:companies | The dated evidence behind a company: every change observed, in order, each with its source URL and confidence, plus the current window verdict. This is what to read before writing to anyone there — it is also the only place the reasons are dated, and a message that cites an undated reason reads as a guess. Moderate: returns up to 40 events. |
| list_people | read:people | People in the CRM by name, stage or what is owed them. Returns identity and stage, not their history — call get_person for that. Use it to find a person id before logging a touchpoint. Cheap. |
| get_person | read:people | Everything on one person: stage, every touch on every channel in order, and the intel gathered about them with source URLs and dates. Read this before drafting to them — the whole point of the second message is that it refers to the first, and this is where the first is recorded. Moderate. |
| list_content | read:content | Artifacts by channel and state: drafts in the gates, pieces waiting for approval, and what has been published. Returns titles and state by default; pass include_body to get the text, which is much larger. Cheap without bodies. |
| get_channel_performance | read:performance | What each channel produced against what it cost, by period, with the learned rank and whether it is below the floor. Use this to answer "where is effort paying off" — it is the allocation view, not a vanity count. Cheap. |
| get_learning | read:learning | Aggregate patterns: which message angles against which company states produced replies, with attempt counts and confidence. Counts only — no identities and no message bodies ever cross this boundary, by construction. Use it to ask what is working before drafting something new. Cheap. |
| list_mentions | read:mentions | Things people said in public that may be worth answering: comments on your posts, threads captured from communities, and anything forwarded in. Each carries its category, a strength score and the source URL. Use it to triage what deserves a reply. Cheap. |
| watch_company | write:watchlist | Add a company to the watchlist by domain. It starts being read on the normal cadence; changes show up in get_company and, when several land together, in get_brief. Use this when a company comes up in conversation and is worth tracking. Idempotent: calling it twice is safe and returns the existing row. Cheap. |
| add_person | write:people | Put a person into the CRM with a name, a title and optionally a LinkedIn URL. Creates the relationship record too, at stage `identified`. Use this when somebody comes up who is worth tracking; log what was said with log_touchpoint afterwards. Cheap. |
| log_touchpoint | write:touchpoints | Record that something happened with a person: a call, a reply, a DM, a meeting. This is what keeps the timeline honest when a conversation happens somewhere the product cannot see, and it is the single most useful thing an agent can write here. Updates the relationship’s last-touch date. Cheap. |
| capture_thread | write:captures | Hand the system a public thread, post or question somebody asked — a Reddit URL, a forum post, a Slack message the user pasted to you. It classifies the intent and, when it reads as somebody looking for what this venture sells, writes a reply draft into the approval queue. Use it whenever the user mentions a conversation worth answering. Moderate: this runs a classifier and may run a drafting model. |
| submit_idea | write:ideas | Queue an idea for content without writing anything yet. Use this when the user says something worth posting but does not want it drafted now — draft_content is the call that turns an idea into posts. Cheap. |
| request_read | write:watchlist | Ask for a watched company to be re-read before its next scheduled pass. Use it when something is known to have changed and waiting for the cadence would be too slow. Returns immediately; the read happens in the background and shows up in get_company. Cheap to call, and it costs a fetch rather than a model call. |
| draft_content | draft:content | Turn one idea into a native post for each channel this venture runs — the phrasing, length and shape differ per channel rather than one text being reposted. Everything produced runs the quality gate and lands in the approval queue at state `passed`; nothing is published. Expensive: this is several model calls, one per channel. Call it once per idea, not in a loop. |
| draft_reply | draft:outreach | Write a reply to a comment or a thread somebody posted — from list_mentions. Produces a public reply and, where the platform allows one, a private one, both through the full gate stack, both landing in the approval queue. There is deliberately no way to open a conversation with somebody who has not engaged first: this replies, it does not approach. Expensive: one model call. |
| draft_followup | draft:outreach | Write the follow-up to somebody already contacted, grounded in what was sent before and anything observed since. Requires a previous message on record; it will not open a conversation. Lands in the approval queue and sends nothing. Expensive: one model call. |
| approve_content | act:approve | Record a human-authorised approval for one artifact and move it to `approved`. This does not publish: publishing is a separate act, and content in `approved` sits until it is scheduled or posted. Refuses anything that has not passed the quality gate. One artifact per call, by id. Cheap. |
| approve_outreach | act:approve | Approve one drafted message and hand it to the send pipeline. **This sends.** It is the same thing the approve button in the app does, and it runs the identical pre-send gate — suppression list, excluded domains, per-domain caps, sending-identity health, autonomy stage and budget, all checked in one transaction. If any of those refuse, nothing goes out and the reason comes back. One draft per call, by id: read it before you approve it. Moderate. |
| mark_published | act:publish_mark | Record that an approved piece went out, with the URL it went out at. Use it when the user posts by hand — most channels are published by a person, and without this the system cannot measure what happened. It does not post anything; it records that somebody did. Cheap. |
| advance_relationship | act:relationship | Set where a person is in the pipeline: identified, researched, first_touch, engaged, conversation, proposal, won, lost, ghosted or nurture. Use it when the user tells you what happened — a stage that only ever moves forward automatically is a stage nobody trusts. Cheap. |
| snooze_followup | act:snooze | Move when a person is next owed a touch, or clear it entirely. Use it when the user says "not until next month" — an overdue list full of things that are not actually overdue is a list people stop reading. Cheap. |
| explain_read | explain:read | The reasoning behind a company read: which observed changes carried the conclusion, which were treated as noise, what the confidence is and what would raise it. Use it whenever a conclusion needs to be defended or checked — and use it before repeating a claim to anybody, because it names the dated sources. Cheap. |
Webhook events
What a signed webhook can carry. Each maps to the read scope that would let you fetch the same thing, so an endpoint cannot subscribe its way past a permission.
| Name | Value | What it does |
|---|---|---|
| read.produced | read:companies | A company was read and something changed. Carries the company, what moved and the new confidence. |
| window.opened | read:companies | Enough changed at one company inside a few weeks to open a buying window. This is the event most worth acting on. |
| draft.ready | read:approvals | Something was written and is waiting for a person. Carries the id and the channel, not the text. |
| approval.granted | read:approvals | A person allowed a draft. Fires whether the approval came from the app, an agent or the API. |
| message.sent | read:approvals | A message left the mailbox, or a post was published. |
| outcome.recorded | read:learning | A reply, a bounce, a meeting or a silence was classified. Negative outcomes fire too; they are the useful half. |
| mention.classified | read:mentions | A mention or capture was classified and cleared the strength floor. Carries the category and the source URL. |
| followup.overdue | read:followups | Somebody is owed a touch and the date has passed. |
| identity.paused | read:approvals | A sending identity stopped sending: a bounce rate, a complaint, or a token that expired. Sending is already halted when this fires. |
| hypothesis.killed | read:learning | A hypothesis was retired because it stopped earning its allocation. |
| channel.rank_changed | read:performance | A channel moved in the learned ranking. Only fires above the sample floor, because a rank below it means nothing. |