Diagnose slow Cortex Agent requests from their traces¶
A caller’s-rights procedure that reads one agent request’s trace, computes a time breakdown by span, and returns an LLM explanation.
| Stage | Operate |
| Topics | AI Observability, Cortex Agents |
| Status | GA |
| Time to complete | 20min |
| Edition | Enterprise |
| Regions | Any region with Cortex Agents and AI_COMPLETE for the chosen model |
| Privileges | ACCOUNTADMIN for 01_setup.sql only; callers need MONITOR on the agent and SNOWFLAKE.CORTEX_USER |
| Related skills | agent-studio (bundled with Cortex Code) |
| Last verified | 2026-10-04 |
| Authors | Haebichan Jung |
| Code | View all files or download diagnose-agent-requests.zip, which includes this page as README.md. Teardown: sql/99_teardown.sql |
This recipe is a reference implementation: review the code and adapt it to your environment before production use.
CALL DIAGNOSE_REQUEST('DB.SCHEMA.AGENT', '<request_id>') returns where one agent request spent its time: a deterministic breakdown by span category (planning, SQL execution, search, response generation, other tools), the slowest spans, and a short LLM explanation with recommendations. It reads only the documented observability table function, runs with the caller’s privileges, and uses no dynamic SQL.
When to use this¶
- A user reports that an agent answer was slow and you want to know whether the time went to LLM planning, a tool, or SQL.
- You want a repeatable, scriptable diagnosis that you can call from a worksheet, a task, or another agent.
When not to use this:
- Aggregate latency and usage reports across many requests: use the
agent-observability-reportsub-skill of the bundledagent-studioskill, or Snowsight’s agent Observability pane. - Scoring answer quality: use agent evaluations.
- Cost questions: see ai-usage-cost-attribution.
How it works¶
- One turn is one trace. Each agent request writes a trace to
SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS. TheAgentV2RequestResponseInfospan carries the request id, total duration, status, and user question; sibling spans cover planning steps, tool calls, SQL execution, and response generation. - Top-level spans do not overlap. Spans whose parent is the root span run one after another, so their durations add up to the time accounted for. Nested spans (for example the SQL execution under the SQL tool) are listed but not added again.
- Caller’s rights. The procedure reads traces with the caller’s own privileges: MONITOR (or OWNERSHIP) on the agent plus
SNOWFLAKE.CORTEX_USER, the same as reading the table function directly. It cannot show a caller anything they could not already query. - No dynamic SQL. The agent name is split into three parts, each checked against an unquoted-identifier pattern; the request id must be a UUID; the model must be in an allowlist. All three reach SQL as bind variables.
- One LLM call per diagnosis. The compact trace goes to
AI_COMPLETEonce, with a JSON output schema. The model’s answer is returned, never executed.
Prerequisites¶
| Requirement | Notes |
|---|---|
| Cortex Agents | The sample agent uses models.orchestration: auto and a Cortex Analyst tool on a semantic view |
SNOWFLAKE.CORTEX_USER | Granted to the recipe role by 01_setup.sql |
| MONITOR on the agent | The recipe role owns the sample agent; grant MONITOR to diagnose your own agents |
claude-sonnet-4-6 for AI_COMPLETE | Default model. The model is not a SET variable, because a session variable cannot reach the procedure body. To use another, edit the MODEL default and the allowed_models list in the DECLARE block of 03_diagnose_request.sql and rerun that file |
Optional: READ UNREDACTED AI OBSERVABILITY EVENTS TABLE | Account-level privilege. Without it, question text and tool payloads are redacted; span names, durations, status, tokens, and model still come through |
Quickstart¶
sql/01_setup.sqlasACCOUNTADMIN: role (granted to you), warehouse, database.sql/02_agent.sql: 500 synthetic Northwind orders, a semantic view, andORDERS_AGENT. Skip it to diagnose your own agent.sql/03_diagnose_request.sql: createsAPP.DIAGNOSE_REQUEST.sql/04_run_and_diagnose.sql: asks the agent one question, waits until its trace is readable (it checks every 5 seconds for up to about 2 minutes), lists recent request ids, and diagnoses the latest one.sql/99_teardown.sqlwhen done.
Walkthrough¶
Find the request¶
DATA_AGENT_RUN returns the answer and token usage but not a request id, so read the id from the trace (or from REQUEST_ID in CORTEX_AGENT_USAGE_HISTORY, which can lag up to an hour):
Pass NULL (or omit the argument) to diagnose the latest request.
Validate, then bind¶
Input such as X.Y.Z'); DROP TABLE T; -- is rejected before any query runs. Quoted, case-sensitive agent names are rejected on purpose; extend the pattern if you use them.
Time breakdown¶
Status codes and error descriptions are read generically from any *.status.code and *.status.description attribute, because each tool type uses its own attribute prefix.
The LLM call¶
With response_format, AI_COMPLETE returns an OBJECT, so there is no string parsing and no retry loop.
Letting other roles diagnose¶
Grant the procedure plus the same privileges a direct trace read needs:
Verify¶
CALL DIAGNOSE_REQUEST('COOKBOOK_DIAGNOSE_AGENT_REQUESTS.APP.ORDERS_AGENT') returns an object with:
status: "success", thetrace_id, and asummarywithduration_msandstatus_code: "200". Thequestionkey appears only if the calling role has the optionalREAD UNREDACTED AI OBSERVABILITY EVENTS TABLEprivilege; without it the question is redacted and the key is left out.time_breakdown_mswith keys such asplanning,sql_execution,response_generation,tool_or_other. Their sum is a little belowsummary.duration_ms(the gap is time outside any top-level span). For the sample question, planning and response generation dominate and SQL is well under a second.slowest_spans(up to three) anddiagnosiswithsummary,root_cause,bottleneck_span, andrecommendations.
Bad input returns status: "error" with a message (AGENT_FQN parts must be unquoted identifiers, REQUEST_ID must be a UUID, MODEL not allowed); an unknown request id returns status: "not_found".
Gotchas¶
| Symptom | Cause | Fix |
|---|---|---|
not_found right after a request, or the latest diagnosis is of an older request | Traces land some seconds after the request finishes, or the id belongs to another agent | 04_run_and_diagnose.sql waits for the new trace before diagnosing; in your own scripts, wait or poll the same way, then retry. List ids with step 2 of that file |
Cortex Agent '<db>' does not exist or not authorized | The caller’s role lacks MONITOR (or OWNERSHIP) on the agent | GRANT MONITOR ON AGENT <db>.<schema>.<agent> TO ROLE <role> |
| Question and tool payloads show as redacted | Caller lacks READ UNREDACTED AI OBSERVABILITY EVENTS TABLE | The breakdown still works; grant the privilege only to roles that may read conversation content |
No active warehouse selected | Caller’s-rights procedures use the caller’s session warehouse | USE WAREHOUSE before the CALL |
MODEL not allowed | Model not in the procedure’s allowlist | Edit allowed_models in the DECLARE block of 03_diagnose_request.sql and rerun the file; do not pass model names through unchecked |
| Quoted or mixed-case agent name rejected | The identifier pattern accepts unquoted names only | Use the uppercase name, or extend the validation deliberately |
| Recommendations mention things the trace does not show | The LLM is summarizing, not measuring | Trust time_breakdown_ms and slowest_spans; treat diagnosis as a starting point |
DATA_AGENT_RUN output has no request id | The SQL interface returns content, status, and usage only | Read the id from the trace (step 2 of 04_run_and_diagnose.sql) or pass NULL for the latest |
Teardown¶
Run sql/99_teardown.sql as ACCOUNTADMIN. It drops the database (agent, semantic view, table, procedure), the warehouse, and the role. Trace events already written stay in SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS, which can’t be modified.
Adapt this recipe¶
Keep these, so a customized version keeps the recipe’s security model:
- Least privilege:
ACCOUNTADMINonly for the scripts listed under Privileges; everything else runs as the recipe’s own role. - No grants to
PUBLIC. - Caller’s rights: procedures run as
EXECUTE AS CALLER, so each user’s own grants apply. - Credentials: create keys, tokens, and secrets yourself, in a separate terminal, and read them from environment variables. Never put them in a script or give them to an agent.
- Dynamic SQL, if you add any, uses bind variables or an allowlist of identifiers, never text concatenated from user or model input.
Change these to fit your use case:
- Object names. Rename them in every script, including teardown, then check that teardown still removes everything.
- Roles, personas, and users: map them to your own, each granted no more than the recipe grants.
- Data: point the scripts at your tables instead of the sample data, then run Verify again.
- Models and warehouse sizes.
To have a coding agent adapt it:
Run it with a coding agent¶
Related skills¶
agent-studio(bundled with Cortex Code): itsagent-observability-reportsub-skill produces aggregate monitoring reports (latency percentiles, token economics, tool stats, feedback) from the same event table. Use it for fleet-level views; use this recipe for one request.