Skip to main content

A chatbot that only answers questions is half an agent. Attach a governed Tool and it can act — file a ticket, update a record — but only for people who are actually allowed to, with a graceful fallback for everyone else instead of a hard failure.

Agentplan → retrieve → act
Glean retrievalgrounded in the index
Custom toolgoverned write action
Agent builder access in your Glean instance
Permission to register a custom tool (or admin help)
A work email for tenant discovery and OAuth sign-in; an AGENTS-scoped API token is the fallback
uv and cloudflared installed locally
Your own email, to put on (and take off) the tool server's allow-list between the two runs
1

Scaffold the project

npx -y tiged@2.12.8 --mode=git gleanwork/glean-cookbook/recipes/multi-step-agent multi-step-agent
2

Set the tool server's allow-list

Set AUTHORIZED_EMAILS to your own email for the first run. You'll remove it for the second run to see the denied branch — changing the allow-list is how both governance paths get demonstrated, since the agent always runs as you. The server refuses to start on an empty allow-list, since that denies everyone and makes the permitted branch unobservable.

(cd multi-step-agent/tool-server && cp .env.example .env)
3

Run the tool server

Listens on port 8080. Keep this running in its own terminal — the agent calls it over HTTP once registered.

(cd multi-step-agent/tool-server && uv run server.py)
4

Expose the demo tool over HTTPS

Keep this running in a second terminal. Copy the printed https://<random>.trycloudflare.com origin into tool-server/openapi.yaml as servers[0].url, with no path. Confirm that <origin>/file_incident_ticket returns the expected 403 for a denied email. This public demo route accepts a spoofable identity header and only creates fake in-memory tickets; authenticate requests from Glean before connecting a real write action.

cloudflared tunnel --url http://localhost:8080
5

Register the tool

Manual, UI-only step. After replacing the OpenAPI placeholder with the tunnel origin, use Admin > Platform > Tools > Add and upload multi-step-agent/tool-server/openapi.yaml. The operation URL must be <origin>/file_incident_ticket.

6

Create the agent

Manual, UI-only step. In Agent Builder: paste the recipe's instructions, turn retrieval on, attach the tool you just registered. Copy the agent's ID for the next step.

7

Set credentials

Use the shipped login flow, then set GLEAN_AGENT_ID from the agent created above. Never implement authentication.

(cd multi-step-agent/invoke-agent && node scripts/glean-auth.mjs login --scopes agents --email "<work-email>")
8

Run it

Dependencies are declared inline (PEP 723) and locked, so uv installs them into an isolated environment on first run — no requirements.txt, venv, or activate step.

(cd multi-step-agent/invoke-agent && uv run main.py)
9

Verify

With your email on the allow-list, confirm the ticket actually gets filed. Then restart the tool server without it and confirm the agent produces a read-only fallback summary instead of a hard failure.

Take it further
  • Add a second governed tool (e.g., escalate to PagerDuty) and let the agent choose between them based on severity.
  • Switch to run_stream() and parse the SSE text yourself for a live-updating UI.
  • Combine with permissions-aware-retrieval's retrieval pattern if you want the agent's grounding to come from your own index instead of Glean's default retrieval.

Summarize our open incidents and file a tracking ticket

With your email on the tool server's allow-list: the agent summarizes and the demo tool call succeeds (200, a fake in-memory ticket id). With it removed: the tool returns 403 and the agent falls back to a read-only summary rather than failing the run. The allow-list demonstrates authorization logic; authenticate requests from Glean before connecting a real write action.

View source

Runs the recipe through the Glean cookbook plugin.

At a glance
SurfacesAgents, Tools
StatusProduction pattern
Time~2 hr
Required scopes
AGENTSTOOLS