Technical docs
Logs
Public reference generated from tech docs/logs.md.
Overview
Support and developer reference for execution-log surfaces backed by workflow runs.
Surfaces that use this system
- Org-wide logs page: `/logs` - AI teammate logs tab: `/staff/ai/[employee_id]?tab=logs` - AI teammate API tab: `/staff/ai/[employee_id]?tab=api`
All three surfaces reuse the same `AiTeammateLogsTable` + `useWorkflowRuns()` stack and are backed by org-wide workflow-run APIs.
Org-wide `/logs` access
- Frontend route: `/logs` - Frontend gate: `canAccessTool({ isOrgAdmin: true }, user)` - If access fails, the page renders `AccessDenied`.
Org-wide page behavior
- Header: `Execution Logs` - Description: `All skill executions across the organization` - Scope: workflow runs across all skills and AI teammates in the current organization - Data source: `useWorkflowRuns()` backed by `GET /api/organizations/{orgId}/workflows/runs/` - Default page size: `25` - Allowed page sizes: `10`, `25`, `50`, `100` - Org-wide `/logs` and teammate `Logs` tab both persist page size in localStorage under `alloy:logs-page-size`
Shared logs-table behavior
- Runs are ordered newest-first by `created_at`. - Refresh re-fetches the current page. - Rows can be deep-linked with `?run=<runId>`. - Pagination state is reflected as `?page=<n>`. - Filter and search state is synced into the URL and preserves unrelated params such as `run` and `tab`. - Expanding a row lazy-loads full run details from: - `GET /api/organizations/{orgId}/workflows/{workflowId}/runs/{runId}/` - Expanded details can show: - run metadata - runtime context - copied run links and IDs - conversation ID copy action - trigger URL copy action when `trigger_url` exists - flow/config snapshot - parsed steps - tool call args/results - voice transcripts and attachments when present - If an expanded run is no longer visible after filtering/pagination, the UI collapses it.
Search behavior
- UUID search is server-backed exact match. - Plain-text search is not sent to the backend. - Search flow today: - UUID input searches `conversation_id` first - if that returns zero results once, the UI falls back to `run_id` - pasting a URL containing `run=<uuid>` switches immediately into run-ID search - non-UUID text search is client-side against the currently loaded rows only - Current client-side text search matches: - skill name - input message - output message - error message - model - run ID - AI teammate name - conversation channel label/key
Filters
- Pagination and API filters are server-side. - UI chip filters are single-select because the server API accepts one value per field. - Org-wide `/logs` shows filter sections: - `Status` - `Channel` - `Model` - `Skill` - `AI Teammate` - `Date Range` - `Duration` - `Tokens` - AI teammate `Logs` tab reuses the same filters but fixes `ai_employee_id` from page context and hides the teammate column. - AI teammate `API` tab hides the channel filter and then client-filters the current page to `API`-labeled rows. - Date presets: - `Last hour` - `Last 24h` - `Last 7 days` - `Last 30 days` - Duration presets: - `< 1s` - `1–10s` - `10–60s` - `> 1 min` - Token presets: - `< 100` - `100–1K` - `1K–10K` - `> 10K`
Source labels
- UI channel labels are derived as: - `employee_chat` -> `Internal Chat` - `ally_chat` -> `Ally Chat` - `web_chat` -> `Web Chat` - `skill_chat` -> `Skill Chat` - no conversation -> `API` - When the backend already populated `conversation_channel` or top-level `channel`, the UI uses that value first. - Otherwise the UI derives: - `API` when there is no `conversation_id` - `Web Chat` when `runtimeContext.isAsync === true` - `Internal Chat` as the fallback for conversation-backed runs without a more specific channel
Backend/API behavior
- List endpoint: - `GET /api/organizations/{orgId}/workflows/runs/` - Detail endpoint: - `GET /api/organizations/{orgId}/workflows/{workflowId}/runs/{runId}/` - Both routes use `checkOrganizationAccess`. - List query validation currently supports: - pagination: `page`, `limit` (`limit` max `100`) - exact filters: `conversation_id`, `status`, `run_id`, `channel`, `ai_employee_id`, `workflow_id`, `model`, `trigger_id` - range filters: `started`, `ended`, `duration`, `tokens` - `exclude` limited to `flow`, `state`, or `flow,state` - The frontend list surfaces send `exclude=flow,state`. - Current backend behavior for any truthy `exclude`: - switches serialization to `WorkflowRun.listData()` - excludes only `flow` at the DB query layer - still returns a compressed `state` - Compressed list `state` includes: - `currentStepId` - top-level `error` - `runtimeContext.userId` - `runtimeContext.isAsync` - trimmed step summaries with reduced input/output payloads - Detail endpoint behavior: - `404` if the run does not exist - `404` if the run exists but does not belong to the requested workflow - loads up to `1000` step logs - backfills temporary conversation titles when the run has a conversation ID but no stored title
Current limitations that matter for support
- Plain-text search only searches the rows already loaded on the current page. - `started` filter maps to run `created_at`. - `ended` filter maps to `completed_at` only, not `failed_at` or `suspended_at`. - The `API` channel label is often derived client-side from the absence of `conversation_id`. - Backend `channel` is not reliably populated for direct API-triggered runs, so server-side `channel=api` filtering is not dependable even though the UI can label those rows as `API`.
Cross-reference
- The org-wide page is `/logs`. - Teammate logs and API request history reuse the same run data model. - General workflow-run internals are documented in `workflow-engine.md`.