commitops
Docs · For agents · Sibling feeds

RepoOps and SalesAsCode push what they already know. CommitOps never pulls.

POST https://commitops.ai/api/v1/tools/<name> or the same tools at https://commitops.ai/api/mcp · contract v1, last changed 2026-10-18
What it isPush only

CommitOps works without either product. A feed only adds what the sibling already knows: RepoOps sends the measured agent spend and the roadmap items for a change, SalesAsCode sends deals and the commitments it found. Each sibling pushes as an agent principal, under the same rules as any other agent. The input and output schemas are in openapi.json, generated from the same code the server runs; where this page and the code disagree, the code wins.

ConnectOne key per feed

An owner of the CommitOps company connects the feed on /connect, or with the connect_sibling tool. That makes:

  • an agent principal named RepoOps or SalesAsCode, operated by that owner;
  • a source of its own (provider repoops or salesascode), so disconnecting the feed deletes what it sent, as for any source;
  • a key, shown once. The sibling stores it and sends it as Authorization: Bearer co_<org>_<prefix>_<secret>.

The key acts as that agent and nothing else. The RepoOps key carries propose and link_work; the SalesAsCode key carries propose. Neither can keep a candidate, and the database refuses an agent keeping an assertion it made itself.

ErrorsSame as every tool

Every call is POST https://commitops.ai/api/v1/tools/<name> with a JSON body, or the same tool over MCP. Errors come back as { "error": { "code", "message", "details" } } with a 4xx status. Retry a 5xx or a 429 with backoff; do not retry a 4xx unchanged.

invalid_input
400
plan_limit
402
forbidden
403
not_found
404
conflict
409
guard_failed
422
RepoOpsrecord_work_cost

Measured agent spend for one change

{
  "work": {
    "provider": "github",
    "key": "acme/app#42",
    "url": "https://github.com/acme/app/pull/42",
    "title": "Add SAML login",
    "state": "merged",
    "merged_at": "2026-10-15T12:00:00Z"
  },
  "cost_cents": 1234,
  "attribution": "branch",
  "roadmap_slugs": ["saml-sso"]
}
work.provider
github or gitlab.
work.key
owner/repo#number for GitHub, group/project!number for GitLab.
work.state
open, merged or closed (closed without merging). merged needs merged_at.
cost_cents
Integer cents: the metered spend of the agent sessions tied to this change. From a float in dollars, send Math.round(cost_usd * 100).
attribution
branch when the sessions ran on this change's own branch, file_overlap when they were matched by touched files.
confidence
Required with file_overlap. Only strong is stored; likely is refused with 400.
roadmap_slugs
Optional. Same as record_work_slugs.

What CommitOps does with it

  • Stores the figure on the change with cost_basis measured, who reported it, when, and the attribution. Reporting again replaces the figure.
  • A measured figure replaces a modeled estimate on the same change; the two are never added. A change that already carries a time-tracked cost is refused with 409, because measured agent spend and tracked human time measure different things.
  • No screen sorts by cost. Measured and modeled cost show on separate lines.
  • If CommitOps has not seen the change yet, it mirrors it from work under the feed’s source. When the GitHub or GitLab webhook later mirrors the same key, the forge’s copy wins for title, state and dates; the cost and slugs stay.

What RepoOps must not send

  • A figure from a model or an estimate. Measured means metered.
  • Spend it cannot tie to a change. Unattributed spend stays unattributed; never spread it across changes.
  • A likely file overlap match.
RepoOpsrecord_work_slugs

The roadmap items one change belongs to

{
  "work": {
    "provider": "github",
    "key": "acme/app#42",
    "url": "https://github.com/acme/app/pull/42",
    "title": "Add SAML login",
    "state": "merged",
    "merged_at": "2026-10-15T12:00:00Z"
  },
  "roadmap_slugs": ["saml-sso"]
}

A slug is lower case letters, digits and hyphens, starting with a letter or digit, at most 64 characters, at most 20 per change. Send a slug the company also sets on its promises with set_roadmap_slug.

Match pass 2 links any kept promise with the same slug to the change at confidence 0.90, the same as a roadmap:saml-sso label. The list replaces what RepoOps sent before for that change; send [] to withdraw. Slugs from the change’s own labels stay. The answer lists the promise refs linked.

SalesAsCoderecord_deal

One company and one deal, in the CRM shape

{
  "account": { "external_id": "acct_7", "name": "Britelane", "domains": ["britelane.com"] },
  "deal": {
    "external_id": "opp_7",
    "name": "Britelane, seats",
    "amount_cents": 1200000,
    "currency": "USD",
    "stage": "stage 2",
    "status": "open",
    "close_date": "2026-12-15",
    "renewal_at": null,
    "owner": "Dana Whitfield"
  }
}
account.external_id
SalesAsCode's own id for the company, stable across calls.
account.domains
Website domains. A URL is reduced to its host; personal mail domains are dropped.
deal.amount_cents
Integer cents, or null when unknown. Never a guess.
deal.currency
ISO 4217, or null when unknown. Null is stored as USD, the default every CRM connector uses.
deal.status
open, won or lost.

The company links to the account CommitOps already has with exactly that domain. A domain that matches two accounts links to none and the deal is not stored (account: "ambiguous"), because a wrong link is worse than a missing one. Otherwise the company becomes an account. The deal is stored with source crm, so it counts wherever a blocking promise waits on it, and totals count distinct deals. Sending the same deal again updates it.

SalesAsCodepropose_assertion

A commitment SalesAsCode found

The existing tool, unchanged. From the SalesAsCode agent it is filed under the feed’s own source.

{
  "quote": "we can have the audit log export live before your renewal",
  "message_text": "Priya: when does export land?\nDana: we can have the audit log export live before your renewal.",
  "account": "Britelane",
  "strength": "firm",
  "due_phrase": "before your renewal",
  "speaker": "Dana Whitfield",
  "said_at": "2026-10-14T16:20:00Z",
  "locator": { "recording_id": "gong:1234", "offset_ms": 61000 }
}
  • quote must be an exact substring of message_text, whitespace aside, or the call is refused with 422 guard_failed and nothing is written. Case and punctuation count.
  • message_text is the whole message or call turn the quote came from, not the excerpt alone. A message_text equal to the quote passes the guard and proves nothing, so send the full text whenever you have it.
  • said_at is when it was said, not when SalesAsCode read it.
  • locator points back: {message_id} for mail, {recording_id, offset_ms} for a call. Up to eight fields.
  • The promise is a candidate. A person keeps or discards it. The SalesAsCode key cannot keep.

Retrying the same quote with the same locator returns the first row with duplicate: true.

Never askedContent

CommitOps never asks a sibling for customer content beyond the quote and the text it came from, and never for code, diffs, prompts, session transcripts, notes, drafts or sequences. Nothing a sibling sends goes to a log, an analytics event or a model’s training. What the product does with customer words: security.

DisconnectOne transaction

An owner disconnects the feed on /connect. In one transaction: every deal it sent, every promise it proposed (kept or not) that has no quote from anywhere else, and every change it mirrored are deleted; the measured costs and slugs it put on changes GitHub or GitLab own are cleared, and a link pass 2 made on one of those slugs comes off. Its key is revoked. The agent principal stays, as every principal does, so the history of what it did keeps its author; an owner can retire it on /settings.

Back to the agent docs.