Skip to main content

A tool can change something outside Glean before anyone has a chance to review it. A durable agent run stops when the agent reaches a tool that needs confirmation, stores the exact call, and waits. Your application reads the call, shows it to a person, and sends their decision; the same run then continues. This recipe walks that flow with a small TypeScript CLI and an agent whose only tool sends you a Slack DM.

An approval takes four calls, and each one needs a value from an earlier response. Keep the run_id from the start, poll until the run needs a decision, and answer with the interaction_id the person reviewed. These are the bodies from one approved run, with example IDs and empty messages left out.

  • run_idreturned by step 1, used by steps 2, 3, 4
  • interaction_idreturned by step 2, used by step 3
agent_id
The published agent's ID: the 32-character ID after /agents/ in its Agent Builder URL.
decision
APPROVE or REJECT, chosen by the person who reviewed the tool call.
Step 1 of 4

Start a durable run

POST/api/agents/{agent_id}/runsglean.agents.createRun

Send the message for the agent. The response is the new run, still working.

Request body
{  "execution_mode": "DURABLE",  "stream": false,  "messages": [    {      "role": "USER",      "content": [        {          "type": "text",          "text": "Cookbook approval test 2026-09-29T21:51:07.008Z"        }      ]    }  ]}
Response body
{  "run": {    "run_id": "5d0c9e1f7a2b4c6e8f1a3b5c7d9e0f12",    "agent_id": "3a2139bdf60540248c270d77887053f0",    "state": "RUNNING",    "created_at": "2026-09-29T21:51:07.204Z",    "updated_at": "2026-09-29T21:51:07.204Z",    "pending_interactions": []  },  "request_id": "b7e41f0c2d9a4e6f8a1c3e5b7d9f0a24"}
  1. Request execution_modeDURABLE keeps the run on the server, so it can stop and wait for a person.
  2. Request streamfalse returns the run as one JSON body instead of a stream.
  3. Response run.run_idKeep this. Every later call names the run by it.
TypeScript CLIStarts a durable run and polls it
YouReview the exact tool call and approve or reject it
Glean agentResumes the same run and acts only if approved
Your Slack DMsThe one place the agent can write
Node.js 22.12+ and npm
A Glean instance with durable agent runs available
Permission to create agents in Agent Builder
The Slack Actions tool pack available in Agent Builder, and your Slack account connected in Glean, so the agent can send you a direct message
Permission to sign in with the agents scope
1

Copy the project

npx -y tiged@2.12.8 gleanwork/glean-cookbook/recipes/human-in-the-loop-agent human-in-the-loop-agent
2

Install and run the offline tests

Run every later command from this directory in the same shell. The tests run the real SDK against local HTTP handlers; they don't sign in or contact Glean or Slack.

cd human-in-the-loop-agent && npm ci
npm test
3

Create the agent and paste its instructions

In Glean, open Agents from the left navigation and click Create agent. Auto mode is selected by default, and Builder Assistant opens beside the agent; you don't need it (if it asks you to describe the agent first, paste these instructions into it). Click the name at the top of the builder and rename it Cookbook approval demo. Replace any text under Instructions with the text this command prints.

cat agent-instructions.txt
4

Add the Slack tool

In the Capabilities tab, under Tools, search for Slack Actions and add it. Under Write tools, keep only Send Slack message to user. Leave Allow agent to use write tools without approval unchecked: that checkbox is the approval boundary this recipe demonstrates. A spec file can't set this up for you, because it refers to Slack by a tool provider ID that is different on every Glean instance.

5

Set the trigger and publish

In the Triggers tab, set When should the agent run? to Manually run and What type of input does it need? to Chat message. Click Publish. Until you publish, your edits are only a draft, and API runs use the published agent.

6

Copy the agent ID

Click Share at the top right of the agent. Under Publishing options, the API section shows the Agent ID with a copy button; use it as <agent-id> below. You don't need Create token there: the next step signs you in instead. The ID is also the 32-character value after /agents/ in the page URL.

7

Sign in

Complete browser sign-in yourself. glean-auth finds your Glean backend from your email and keeps a refreshable session outside this project, so a run can wait for you. If OAuth isn't available, copy .env.example to .env and set GLEAN_API_TOKEN to a user-scoped token with the agents scope instead. Never paste a token into a chat or a command.

npm run login -- --email "<work-email>"
8

Run the agent and decide

The command starts one durable run, waits for the agent to pause, and shows the tool, what it does, and its exact arguments. Type a to approve that one call (the DM arrives), r to reject it (nothing is sent), c to cancel the run, or press Enter to leave it waiting. If the run finishes without asking, the Slack tool isn't added, Allow agent to use write tools without approval is checked, or the agent wasn't published.

npm start -- --agent-id "<agent-id>" --email "<work-email>" --show-json
9

Follow the API calls

--show-json prints each API request and response body under the SDK call that sent it, so you can follow run_id and interaction_id from one call to the next. Leave it off to see only the review. The JSON includes the tool arguments, so keep it out of shared logs.

10

Pick a waiting run back up

Closing the CLI or reaching the 120-second wait doesn't cancel the run; the CLI prints the full resume command, with the agent and run IDs. After a decision, a resumed run can take a few minutes to finish; if the wait runs out, resume again. Without a terminal to ask in, resume prints a decision command bound to the pending interaction ID; run it only after a person has reviewed that call.

11

Check approve, reject, and cancel

Approve one run and confirm exactly one DM arrives. Run the command again and reject; confirm no DM arrives and the agent says the message was not sent. Run it a third time, press Enter, then run the resume command it printed and cancel the run (c); it ends cancelled with nothing pending. Only Slack can show whether a message was sent.

npm start -- --agent-id "<agent-id>" --email "<work-email>" --show-json
12

Clean up

Delete or archive the Cookbook approval demo agent in Agent Builder when you're done. Nothing here deletes runs, messages, or agents.

Leave Allow agent to use write tools without approval unchecked for the tool you want reviewed. Asking for consent in the agent instructions is not an approval gate. If a run finishes without pausing, treat it as a configuration error.

Send the interaction ID the person reviewed, never a newly polled one. One decision covers one call: if the agent pauses again, show the new call. A decision cannot edit arguments; reject and start again instead.

Nothing in this recipe approves automatically. Without a terminal to ask in, the CLI prints a decision command bound to the pending interaction ID; a coding assistant must still ask the user before running it. Only count input given after the call is on screen: the CLI throws away anything typed while it waited, so a keypress made earlier cannot approve a call nobody has seen.

Retrying a start after an unknown outcome creates a second run, so the client never retries on its own. After a network error, read the run before doing anything else. Keep credentials and printed tool arguments out of shared logs.

The CLI reviews a single tool approval and refuses a run waiting on more than one. Multi-call approval batches are outside this recipe.

Agent spec files refer to built-in tools such as Slack by a tool provider ID that differs on every Glean instance, and an unresolved tool is dropped without an error. Until that's portable, creating the agent in Agent Builder is the reliable path.

A run survives your connection closing, not a server worker crashing. An active turn times out after 30 minutes; runs waiting for approval don't expire through that timeout.

Cancellation stops future work. A tool call that already ran stays done, and closing the CLI never cancels a run.

Take it further
  • Replace the terminal prompt with an approval screen in your application. Keep the reviewed interaction ID and arguments attached to the decision.
  • Swap in another write tool once you have confirmed it requires confirmation. Show large numeric arguments from the raw response, since JSON.parse rounds integers above 2^53.

Start a run and approve the Slack DM it asks to send.

The run pauses in REQUIRES_INPUT with one TOOL_APPROVAL for Send Slack message to user, showing the message text, and nothing arrives in Slack yet. After approval the same run reaches SUCCEEDED and exactly one DM arrives.

Start a run and reject the Slack DM it asks to send.

The run pauses before sending. After rejection the same run reaches SUCCEEDED, the agent replies that the message was not sent, and no DM arrives.

Start a run, leave it waiting, then resume it and cancel.

Leaving the prompt sends nothing and keeps the run in REQUIRES_INPUT. Resuming shows the same pending call; cancelling ends the run CANCELLED with no pending interactions and no DM.

View source

Runs the recipe through the Glean cookbook plugin.

Auth

Run the authenticate step on this page. It discovers your tenant from work email and signs you in with OAuth, using the shipped login command. If OAuth is unavailable, create a scoped Glean-issued token in Token Management (agents).

At a glance
CapabilitiesAgents, Tools
SurfacesPlatform API, Agents, Tools, API clients
StatusQuickstart
Time~5 min
Required scopes
agents