FurniOpsDocs

Proposals

Every change an AI agent wants to make, with the proof FurniOps ran on it. What a proposal holds, how it is proved and rated low or high risk, its statuses, accepting and rejecting it, and the webhooks that announce it.

A proposal (prp_…) is a change an AI agent wants to make, with the evidence for it. Review them under Catalogue › Proposals, or through the API.

{
  "object": "proposal",
  "id": "prp_034XqBaL2mRLTZPXyRbaUj",
  "catalogue": "cat_034XqBaL2mRLTZPXyRbaUa",
  "agent": "categories",
  "source": "src_034XqBaL2mRLTZPXyRbaUh",
  "status": "proposed",
  "risk": "low",
  "summary": "File 2 category paths covering 37 products",
  "payload": {
    "kind": "category_rules",
    "rules": [
      {
        "path": ["Upholstery", "Sectionals"],
        "category": "ctg_034XqBaL2mRLTZPXyRbaUk",
        "reason": "The path names sectional sofas; the sample titles are L-shaped sofas."
      },
      {
        "path": ["Upholstery", "Loveseats"],
        "category": "ctg_034XqBaL2mRLTZPXyRbaUm",
        "reason": "Two-seat sofas, by the path and the titles."
      }
    ]
  },
  "evidence": {
    "kind": "category_rules",
    "products": 1204,
    "filed_before": 1150,
    "filed_after": 1187,
    "snapshot": "2026-10-06T06:00:02Z"
  },
  "model": "gemini-3.8-flash",
  "cost_micros": 1840,
  "failure": null,
  "superseded_by": null,
  "decided_by": null,
  "created_at": "2026-10-06T06:04:19Z",
  "decided_at": null,
  "applied_at": null
}
PropertyMeaning
agentThe agent that proposed it.
sourceThe source it is about; null when it is about none (a new source from a probe).
statusWhere it stands (below).
risklow or high (below).
summaryWhat it proposes, in a line.
payloadThe change, by kind: category_rules, mapping, source or triage.
evidenceThe proof's result, by the same kind.
model, cost_microsThe model that proposed it, and what asking it cost in micro-USD.
failureWhy it failed its proof, or could not be applied.
superseded_byThe newer proposal about the same thing that replaced it.
decided_by, decided_at, applied_atWho decided it (a person, or the agent under auto_low_risk) and when.

New kinds come with new agents: show a kind you do not know by its summary.

The proof

Before anyone sees a proposal, FurniOps proves it against the source as it is now, in code. A change that makes things worse fails there and is never offered.

KindProved onIt fails when
category_rulesThe source's current products.A rule names a category the taxonomy lacks, names a path twice, or sits on a path that already has a rule; or it files no product that was not filed before.
mappingRows of the source's latest file, mapped with and without the columns.The file's headers changed since; a column is not one the mapping left unplaced (a person's "left out" included); the columns are not a valid mapping together; or they add field problems, empty a column imported now, or fill nothing.
sourceThe probe's report.There is a draft only when FurniOps can run the site's source now: a public feed of a platform it reads, and no wall.
triageThe run as recorded.A cited issue is not the run's own, or the run is no longer the source's latest.

The evidence shows the counts the proof made: products filed before and after, field problems before and after and the fields gained, the probe's platform and effort, or the issues explained.

Risk

The proof also rates the change. High-risk proposals always wait for a person, whatever the agent's autonomy.

KindLow riskHigh risk
category_rulesEvery product it files was unfiled.It moves products out of a category a person's rule gave them.
mappingIt adds columns to an already confirmed mapping, and none of them keys or groups products.It would confirm a mapping for the first time (which imports the source), or it maps a column that keys products (which regroups everything the source imports).
sourceNever.Always: a new source, and its licence basis is your call.
triageAlways.Never. A note is read, then dismissed; it is never applied.

Statuses

statusDashboard tabMeaning
proposedWaitingWaiting for a person.
appliedAppliedMade: accepted by a person, or applied at once as low risk under auto_low_risk.
rejectedRejectedA person said no (or dismissed a triage note).
failedFailedIts proof found it makes things worse, or applying it failed; see failure.
shadowShadowRecorded while the agent runs in shadow. Nobody is asked.
supersededSupersededA newer proposal about the same thing replaced it.

GET /v1/proposals lists those waiting for a person by default, newest first; filter by status, agent and source. Reading needs agent:read, which an API key can hold.

Decide

  • Accept and apply (POST /v1/proposals/{id}/accept): FurniOps proves the change again, then applies it through the same service a person uses (category rules, mappings, sources), as you. A proposal that no longer holds answers 409.
  • Reject (POST /v1/proposals/{id}/reject), with an optional reason for the agent's record. A triage note is dismissed this way.

Deciding needs agent:write, and accepting also needs the change's own permission (sources:write): owners and admins, signed in. Neither API keys nor OAuth apps can hold agent:write, so proposals are decided in the dashboard.

A rejection holds. An agent never proposes the same change again once a person rejected it. The same change is recognised however the agent words it: by the paths and categories, the columns, fields and settings, the source's fields, or the cited issues, never by its reasons.

Webhooks

Two events announce proposals to your webhook endpoints:

EventSent when
proposal.createdAn agent proposed a change that passed its proof and waits for a person.
proposal.appliedA proposal was applied: accepted by a person, or at once as low risk under auto_low_risk.

Each carries the full proposal. They belong to your organisation, not to a catalogue's records: only webhooks deliver them (not the change feed or the stream), and shadow proposals raise none.

On this page