Skip to content

Agent Review and Approvals

Your agents can ask you things, and you can answer with a click. Since v2.40.0 an agent working in your Glassy is a principal with a name, not an anonymous API caller. The Agent Review page is where the two of you meet: it holds the questions waiting on you, a feed of what your agents did, and the notifications they raised.


What the loop is for

An agent that can write to your workspace needs a way to be told no, or to ask before doing something consequential. Without it there are two bad options: the agent acts unilaterally, or it stops and reports into a log you never read.

The review loop is the third option. The agent asks a structured question, optionally pointing at the note or document it is asking about. You answer with a click and — when the answer is a "change this" style choice — a reason. The artifact carries the state, so the note itself shows whether it is under review, approved, or sent back for changes.

One status field carries the whole lifecycle:

Status Meaning
open Waiting on you
answered You picked a choice (and gave a comment if one was required)
withdrawn The agent no longer needs an answer

An agent cannot withdraw a question you have already answered — that is refused with NOT_OPEN, because your answer is the record and it stays the record.

Where you act

Open Agent Review. It has three sections in one surface, deliberately: the questions waiting on you, what the agents have been doing, and what they pinged you about — because a question with no activity context is hard to answer, and an activity feed with no way to respond is just noise.

  1. The queue — open questions, each with the agent that asked, the choices it offered, and a link to the linked note or document when there is one.
  2. Agent Activity — one feed over notes and documents your agents authored and edited, plus the questions they asked and the answers you gave. The summary line ("Hermes wrote 2 notes and asked 1 question in the last 7 days") is generated on the server and counts the whole window regardless of pagination, so the headline cannot disagree with the list beneath it.
  3. Notifications — what agents raised through the awareness lane.
  4. Recently answered — your own history, so you can see what you already decided.

State badges on the cards. A note an agent asked you about shows its state on the card itself — under review, approved, or changes requested — not only in the queue. That is the point of the design: you should be able to see the status of a thing while you are looking at the thing.

Answering, and why some answers need words

Choices are the agent's to offer (between 2 and 10 of them, yes/no by default). When an answer is a changes-style choice — one that sends work back — a comment is required, and the API refuses the answer with 422 COMMENT_REQUIRED if you leave it blank:

The "<choice>" answer requires a comment — say why, so the agent can act on it.

That is not friction for its own sake. An agent told "changes requested" with no reason can only guess or ask again, and a second round trip costs more of your attention than the comment did. Approvals and other positive choices do not require one.

Escalation: an unanswered question does not just sit there

If you are away, open questions age into the awareness lane:

  • at 24 hours and again at 72 hours, a warning notification is raised naming the question;
  • each threshold fires exactly once per question, so an ignored item does not nag you every minute;
  • escalation informs, it never answers — the question stays open until you answer it or the agent withdraws it.

The sweep runs on the dispatcher's minute tick and is appliance-only.

What your agent does

Three MCP tools, all listed in the MCP server tool table:

Tool Use
glassy_request_review Ask. Pass linked_type/linked_id (note or document) when the question is about a specific item, so you can open it from the queue
glassy_get_review Poll. Returns status plus your answer_choice, answer_comment, answered_by and answered_at
glassy_withdraw_review Retract an open question. Refused with NOT_OPEN once answered

Questions are capped at 2,000 characters and each choice at 120.

The awareness lane is the other half: glassy_notify lets an agent ping you directly (rate-limited to 10 per minute by design, and that window is shared across the appliance's worker processes, so 10/min means 10/min rather than 10 per process), and glassy_create_digest posts an end-of-session summary.

Saying what each choice means

Two optional fields on glassy_request_review exist so you are not guessing when you answer. Both are additive — omit them and the card renders exactly as it did before.

context — why the agent is asking. The situation, what it already tried, what is at stake. It has its own 2,000-character ceiling and renders under the question in its own space, so the reasoning no longer has to be folded into the same 2,000 characters as the question itself.

choice_notes — the consequence of each choice. A map of choice label to a one-line description of what picking it does:

{ "publish": "goes live on the public window immediately",
  "hold":    "I keep editing; nothing is shown to anyone" }

Each note renders inside its own button, which is deliberate: a sibling list would let a consequence be read against the wrong label, which is the exact failure the field exists to prevent. The map may be partial — label the choices where the outcome actually differs and leave the obvious ones alone. A consequence is capped at 200 characters on purpose: past that it has stopped being a consequence and become a second question.

A note keyed to a choice that was not offered is refused as INVALID_CHOICE_NOTES, listing what was sent against what is offered, rather than silently dropped — so a typo reaches the agent instead of vanishing between its tool call and your screen. glassy_get_review returns both fields alongside what was sent.

What happens after you answer

answered ends the request's own lifecycle, and there is deliberately no outcome write-back field or tool. Every tool call the agent makes is already recorded independently with its real outcome (ok / error / denied) and rendered beside the questions and answers as one stream, so a self-reported outcome would be a second account of the same loop that could disagree with the first. If an agent's next step matters to your decision, it should describe it in context — it will not be believed separately.

Between two machines

If you run more than one appliance — a desktop and a laptop, or a home box and a VPS — review requests sync. An agent can ask on one seat and you can answer on the other, which is the whole point: the question is not tied to the machine that happened to raise it. It rides the same Cloud Sync channel that carries notes, documents, bookmarks and voice.

Questions are deliberately not fed into the memory layer. A question is an instruction to you, not a fact about the world, so it does not become searchable corpus or graph edges.

Self-host only, and how to check

The identity and awareness surfaces around this loop are appliance-only; the hosted cloud build withholds them by design. Cloud serves 36 MCP tools to an appliance's 40 — the four withheld are glassy_notify and the three review tools (glassy_request_review, glassy_get_review, glassy_withdraw_review). The review tools are gated because the UI that answers them is appliance-only: an agent on cloud could otherwise file an approval request and poll a queue nobody can open. Cloud also answers POST /api/mcp-keys with 403 FEATURE_NOT_AVAILABLE, reporting agentIdentity.mode: "single-key" instead of "named-keys".

The withheld tools are named in the manifest rather than silently absent, so an agent can tell "this instance cannot do that" from "I guessed the name wrong":

curl -s https://your-instance/api/capabilities | jq '.agentIdentity, .notifications, .mcp.tools, .mcp.cloudGatedTools'

The manifest reports what is actually mounted, not merely declared — a surface that failed to attach at boot is reported as unmounted rather than advertised as available.

Named agent keys

For any of this to mean something you need to know which agent did it. An appliance can issue one MCP key per agent (Settings → Connections & data → AI tools (MCP)), each pinned to a name. Every key looks like gky_mcp_… — you cannot tell them apart by shape, only by the panel that issued them. The instance key sits at the top of that panel and named agent keys below it; both are scoped to that instance and either can be revoked there.

With named keys, authorship on notes and documents, the activity feed, agent memory and notifications all key off a verified principal rather than a name the client declared about itself in a handshake. Without them everything is attributed to one shared identity — the pre-v2.40.0 behaviour, and the reason "who wrote this?" used to be unanswerable.

A named key identifies an agent; it does not yet limit one. Every key carries the full capability set of the account that issued it, so issue keys only to agents you would trust with the workspace.

  • MCP Server — the full tool surface, including the document and review tools
  • Second Brain — the memory, search and knowledge graph your agents share with you
  • Agent Gateway — dispatching tasks to agents
  • Cloud Sync — how review requests and content travel between appliances