OpenTree DashboardSign in

$ list_feedback --unresolved

Comments on the deliverable, back into the agent

Turn on the on-page toolbar and a reviewer can leave a comment anchored to the element they are objecting to — with no account. The agent reads those comments as structured input and marks them resolved when it has acted.

The loop, end to end

  1. The agent publishes the deliverable with agentation: true.
  2. You send the private link. The client opens it in a browser, with no login.
  3. They select a paragraph and leave a comment, a reaction, or a private note.
  4. The agent calls list_feedback on the next turn and gets the comments as data.
  5. It fixes the page with update_site — same link — and calls resolve_feedback.

Turn the toolbar on

set_agentation({ site_id: "…", agentation: true })

Or at publish time:

publish_html({ content: "…", agentation: true, reactions: true })

agentation and reactions are independent switches. A client review usually wants both; a status page shared with fifty people often wants reactions only.

What the reviewer gets

CommentsAnchored to an element, so this number is wrong points at the number.
ReactionsOne tap. Agreement and objection without composing a sentence.
Private notesVisible only to the person who wrote them — reading notes, not messages.
No accountNone of it requires a login, an email, or an install.

What the agent reads back

list_feedback({ site_id: "…", unresolved_only: true })

Or over HTTP:

curl https://read.botook.ai/sites/SITE_ID/feedback \
  -H "Authorization: Bearer otr_live_YOUR_KEY"

The shape of one item

FieldMeaning
idStable id — what you pass to resolve or delete.
kindcomment, reaction or note.
anchorThe element the reviewer had selected. This is the whole trick.
bodyWhat they wrote.
severityHow strongly it was flagged, when the reviewer said.
resolvedWhether anyone has acted on it.
created_atUnix seconds.

Why the anchor matters

The third chart looks off is a puzzle. The same sentence attached to the third chart is an instruction. The anchor is what makes the feedback usable as agent input rather than as something a human has to interpret first.

Resolving

resolve_feedback({ feedback_id: "…" })

Or from either REST shape:

curl -X POST https://read.botook.ai/feedback/FEEDBACK_ID/resolve \
  -H "Authorization: Bearer otr_live_YOUR_KEY"
curl -X DELETE https://read.botook.ai/feedback/FEEDBACK_ID \
  -H "Authorization: Bearer otr_live_YOUR_KEY"

Resolve keeps the record and marks it done. Delete removes it. Most loops only ever resolve.

Fix in place, do not resend

The revision goes behind the same token, so the client refreshes the link they already have:

update_site({ site_id: "…", content: "<!doctype html>…" })

Unresolved counts follow the page

unresolved_feedback and reactions_count hang off the site object, so a list of your pages doubles as a queue of what still needs attention.

curl https://read.botook.ai/sites -H "Authorization: Bearer otr_live_YOUR_KEY" \
  | jq '.sites[] | {title, unresolved_feedback}'

Where it lands for you

In the dashboard: one activity feed across every page — opens, reactions, comments — plus an optional daily digest email so a quiet review does not go unnoticed for a week.

Reviewer privacy

A comment is content the reviewer chose to send you. A private note is not: it stays with the person who wrote it. Nothing here records what anyone looked at beyond the aggregate counts on the engagement page.

Compared with the usual review round

RouteCost
Email with an attachmentComments arrive as prose about a file version nobody can name.
A shared documentThe client needs an account in a tool they do not use.
A screen-share callWorks, but only at a time you both have.
An anchored comment on the deliverableNo account, no version confusion, and it is already structured.

Turning it off

For a page going to a wide audience, leave the toolbar off. Nothing is injected and the document is served exactly as written:

set_agentation({ site_id: "…", agentation: false })

Does the toolbar change my page?

It adds a small script on the viewer response only. The stored document is untouched — what you get from /sites/:id/content is exactly what you published.

Reactions and comments are different records

They arrive from the same toolbar and they are stored apart, because they mean different things. A comment is a task: it has an anchor, a body, and a resolved state. A reaction is an opinion: it has a count and nothing to do. Merging them would put "someone liked the chart" into the same queue as "the chart is wrong".

CommentReaction
CarriesText plus the element it was left onOne emoji
Has a stateUnresolved until you resolve itNone — it is a tally
Switchset_agentationreactions, separately
Read withlist_feedbackGET /sites/:id/reactions

Telling two audiences apart

One page often goes to two sets of people — the client and the subcontractor, or design and legal. Mint a labelled share token for each and the comments arrive tagged with which link they came through, so you can act on one group without guessing:

curl -X POST https://read.botook.ai/share-tokens \
  -H "Authorization: Bearer otr_live_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{"site_id":"SITE_ID","label":"client-review"}'

Revoking one token ends that audience's access without disturbing the other, which is the polite way to close a review round early.

When the page is behind a gate

Feedback still works with a password or an email-domain gate in front of the page — the reader passes the gate first, then sees the toolbar. The email gate is the only route that gives you anything about who is talking: the page was opened by somebody who could receive mail at the domain you allowed. A password tells you only that they had the password.

The most common way this loop breaks is boring: the default expiry runs out on Friday while the client reads it on Monday. Match the expiry to the review, not to the publish, and extend it if the round drags:

set_expiry({ site_id: "…", expires_in_hours: 336 })

Expiry here is deletion, so an expired review page is gone rather than gone quiet — the feedback you already collected survives, because it belongs to the account, not to the link.

Feeding it back into the next run

The point of anchored, structured feedback is that the next turn can consume it without a human paraphrasing. In an unattended loop that is two commands — read the open items, and close them once the revision that answers them is published:

curl -s https://read.botook.ai/sites/SITE_ID/feedback?unresolved_only=true \
  -H "Authorization: Bearer otr_live_YOUR_KEY" \
  | jq -r '.feedback[] | "\(.anchor): \(.body)"'

Questions

Can clients see each other's comments?

Comments are for you and the agent. Private notes are visible only to their author.

Does the client need an account?

No, ever.

Can I export the feedback?

It is JSON from the API — pipe it wherever you like.

Can two clients review the same page?

Yes. Give each a share token so you can tell the audiences apart and cut one off alone.

Keep reading