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.
Scaffold the project
npx -y tiged@2.12.8 --mode=git gleanwork/glean-cookbook/recipes/multi-step-agent multi-step-agentSet 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)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)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:8080Register 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.
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.
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>")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)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.
- 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.