Skip to main content

Frontend-Backend API Contract

The authoritative contract between the River Agents frontend (Next.js 15, React 19, TypeScript, Zustand) and the backend API -- covering the API client configuration, all TypeScript type definitions, page-by-page call specifications for all 12 pages, 7 Zustand store designs, WebSocket event types, error handling, optimistic update patterns, and cache invalidation rules.

Quick Navigation

API Client Configuration

All frontend HTTP traffic routes through TLO Gateway (:8001). The frontend never calls the backend, data orchestration, or any internal service directly.

Base URL

Base URL = NEXT_PUBLIC_API_BASE_URL  (default: http://localhost:8001)
Full URL = {Base URL}/api/v1{endpoint}

Required Headers

HeaderValueApplied To
AuthorizationBearer {access_token}All authenticated endpoints
Content-Typeapplication/jsonAll requests (auto-removed for FormData)
Acceptapplication/jsonAll requests

Token Lifecycle

  1. On login, signup, or OTP verify, the backend returns { access_token, refresh_token, expires_in }.
  2. Tokens are stored via StorageService (localStorage).
  3. On 401 response, the client automatically attempts refresh via POST /auth/refresh-token.
  4. If refresh fails (invalid or expired refresh token), all auth storage is cleared and the user is redirected to /signin.
  5. On successful refresh, the original request is retried once with the new token.

Standard Response Envelope

Every API response uses this structure:

{
"success": true,
"status": 200,
"message": "Agents retrieved successfully",
"data": {},
"error": null,
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-05-10T08:04:22Z"
}
}

Pagination Convention

Paginated endpoints accept these query parameters:

ParameterTypeDefaultDescription
pageinteger1Page number (1-indexed)
page_sizeinteger20Items per page (max 100)
sort_bystringcreated_atSort field
sort_orderasc or descdescSort direction
searchstring--Full-text search across name, description

Paginated responses include:

{
"items": [],
"pagination": {
"total": 142,
"page": 1,
"page_size": 20,
"total_pages": 8,
"has_next": true,
"has_previous": false
}
}

TypeScript Type Definitions

Enumerations

// Agent lifecycle status
type AgentStatus =
| 'draft'
| 'configured'
| 'validated'
| 'deployed'
| 'active'
| 'running'
| 'awaiting_approval'
| 'paused'
| 'archived';

// Business function category
type AgentCategory =
| 'customer_support'
| 'sales_lead_qualification'
| 'finance_reconciliation'
| 'risk_compliance'
| 'data_analyst'
| 'operations_monitoring'
| 'executive_decision'
| 'custom_enterprise';

// Autonomy level
type ActionLevel =
| 'read_respond' // Read data and respond only
| 'recommend' // Suggest actions but not execute
| 'act_with_approval' // Execute write actions after human approval
| 'fully_automated'; // Execute all allowed actions autonomously

// How an execution is triggered
type TriggerType =
| 'manual'
| 'scheduled'
| 'event'
| 'workflow'
| 'api'
| 'threshold';

// Execution run state
type ExecutionStatus =
| 'queued'
| 'running'
| 'awaiting_approval'
| 'success'
| 'failed'
| 'cancelled'
| 'timeout'
| 'max_turns_exceeded'
| 'budget_exceeded';

// Approval resolution state
type ApprovalStatus =
| 'pending'
| 'approved'
| 'rejected'
| 'edited_approved'
| 'expired';

// Agentic loop step classification
type StepType =
| 'reasoning'
| 'tool_call'
| 'observation'
| 'interaction'
| 'approval_gate'
| 'error_recovery';

// Tool dispatch category
type ToolCategory = 'reasoning' | 'execution' | 'interaction';

// Agent version deployment state
type VersionStatus = 'draft' | 'active' | 'archived';

// Template availability state
type TemplateStatus = 'draft' | 'published' | 'deprecated';

// Governance enforcement mode
type EnforcementMode = 'block' | 'require_approval' | 'warn' | 'log_only';

// Governance policy scope
type PolicyScope = 'organization' | 'workspace';

// Notification delivery channel
type NotificationChannel = 'email' | 'webhook' | 'slack' | 'in_app';

// Audit event categories
type AuditEventType =
| 'agent_created' | 'agent_updated' | 'agent_deployed'
| 'agent_paused' | 'agent_resumed' | 'agent_archived' | 'agent_deleted'
| 'execution_started' | 'execution_completed' | 'execution_failed' | 'execution_cancelled'
| 'approval_requested' | 'approval_granted' | 'approval_rejected' | 'approval_expired'
| 'trigger_created' | 'trigger_updated' | 'trigger_deleted'
| 'policy_created' | 'policy_updated' | 'policy_violated'
| 'settings_updated' | 'api_key_created' | 'api_key_revoked'
| 'data_source_bound' | 'data_source_unbound'
| 'tool_enabled' | 'tool_disabled';

// Alert severity
type AlertSeverity = 'info' | 'warning' | 'critical';

// System health
type HealthStatus = 'healthy' | 'degraded' | 'critical' | 'unknown';

Core Entity Interfaces

// ---- Agent ----

interface Agent {
id: number;
organization_id: number;
workspace_id: number;
created_by: number;
owner_user_id: number;
name: string;
description: string;
category: AgentCategory;
status: AgentStatus;
action_level: ActionLevel;
governance_level: 'standard' | 'strict' | 'custom';
icon_name: string; // Lucide icon name
icon_color: string; // Tailwind color class
icon_bg: string; // Tailwind background class
tags: string[];
goal: string;
instructions: string;
system_prompt: string | null;
model_config: ModelConfig;
notification_config: NotificationConfig;
current_version_id: number | null;
template_id: number | null;

// Computed / joined fields returned by GET /agents and GET /agents/{id}
owner_name: string;
owner_avatar: string | null;
health_status: HealthStatus;
health_score: number; // 0-100
total_runs: number;
successful_runs: number;
failed_runs: number;
avg_duration_seconds: number;
last_run_at: string | null; // ISO 8601
last_run_status: ExecutionStatus | null;
deployed_at: string | null;

created_at: string; // ISO 8601
updated_at: string;
deleted_at: string | null;
}

// ---- AgentVersion ----

interface ModelConfig {
default_tier?: 'fast' | 'balanced' | 'reasoning' | 'coding';
reasoning_tier?: 'fast' | 'balanced' | 'reasoning' | 'coding';
provider_preference?: 'anthropic' | 'openai' | 'gemini' | 'deepseek' | 'any';
temperature?: number;
max_tokens?: number;
max_turns?: number;
token_budget?: number;
timeout_seconds?: number;
}

interface ToolConfigOverride {
timeout_seconds?: number;
retry_count?: number;
disabled?: boolean;
}

interface ApprovalRules {
require_approval_for: string[]; // Tool names requiring approval
auto_approve_conditions: { tool: string; condition: string }[];
approver_roles: string[];
escalation_timeout_minutes: number;
}

interface AgentVersion {
id: number;
agent_id: number;
version_number: number;
status: VersionStatus;
instruction_set: string;
model_config: ModelConfig;
tool_id_overrides: Record<string, ToolConfigOverride>;
approval_rules: ApprovalRules | null;
is_active: boolean;
change_summary: string | null;
created_by: number;
created_by_name: string;
created_at: string;
tools?: ToolBinding[];
data_sources?: DataSourceBinding[];
}

// ---- AgentTrigger ----

interface ScheduledTriggerConfig {
cron: string;
timezone: string;
input_template?: Record<string, unknown>;
}

interface EventTriggerConfig {
event_source: 'webhook' | 'database' | 'pubsub';
event_type: string;
filter_expression?: string;
input_mapping?: Record<string, string>;
}

interface ThresholdTriggerConfig {
metric: string;
operator: 'gt' | 'lt' | 'gte' | 'lte' | 'eq';
value: number;
window_seconds: number;
cooldown_seconds: number;
}

interface AgentTrigger {
id: number;
agent_id: number;
trigger_type: TriggerType;
config: ScheduledTriggerConfig | EventTriggerConfig | ThresholdTriggerConfig | Record<string, unknown>;
is_active: boolean;
last_triggered_at: string | null;
next_trigger_at: string | null;
created_at: string;
updated_at: string;
}

// ---- Execution ----

interface Execution {
id: number;
organization_id: number;
workspace_id: number;
agent_id: number;
agent_name: string; // Joined
agent_version_id: number | null;
version_number: number | null; // Joined
trigger_type: TriggerType;
trigger_id: number | null;
triggered_by_user_id: number | null;
triggered_by_name: string | null; // Joined
input_prompt: string | null;
status: ExecutionStatus;
priority: 'low' | 'medium' | 'high' | 'critical';
started_at: string | null;
completed_at: string | null;
duration_seconds: number | null;
turn_count: number;
tool_calls_count: number;
total_tokens: number;
cost_estimate: number | null;
error_message: string | null;
error_code: string | null;
temporal_workflow_id: string | null;
created_at: string;
updated_at: string;
}

// ---- ExecutionStep ----

interface ExecutionStep {
id: number;
execution_id: number;
step_number: number;
step_type: StepType;
tool_name: string | null;
tool_category: ToolCategory | null;
input_data: Record<string, unknown>;
output_data: Record<string, unknown>;
model_used: string | null;
model_provider: string | null;
tokens_input: number | null;
tokens_output: number | null;
duration_ms: number | null;
status: 'success' | 'error' | 'timeout' | 'skipped' | 'blocked';
governance_decision: 'PROCEED' | 'APPROVAL_REQUIRED' | 'BLOCKED' | 'SUGGEST_ONLY' | null;
error_message: string | null;
created_at: string;
}

// ---- ApprovalRequest ----

interface ApprovalRequest {
id: number;
organization_id: number;
workspace_id: number;
execution_id: number;
agent_id: number;
agent_name: string; // Joined
execution_step_id: number | null;
action_description: string;
action_tool: string | null;
action_payload: Record<string, unknown>;
reasoning_summary: string | null;
risk_context: string | null;
confidence_score: number | null;
status: ApprovalStatus;
requested_at: string;
expires_at: string | null;
responded_at: string | null;
responded_by_user_id: number | null;
responded_by_name: string | null; // Joined
response_note: string | null;
created_at: string;
updated_at: string;
}

// ---- Template ----

interface Template {
id: number;
organization_id: number | null; // null for system templates
name: string;
description: string;
category: AgentCategory;
icon_name: string;
icon_color: string;
icon_bg: string;
config: {
name_suggestion: string;
description: string;
category: AgentCategory;
action_level: ActionLevel;
instructions: string;
model_config?: ModelConfig;
tools?: string[];
data_source_ids?: number[];
default_triggers?: AgentTrigger[];
governance_policy_ids?: number[];
};
integrations: string[];
governance: Record<string, unknown>;
is_system: boolean;
is_active: boolean;
usage_count: number;
created_by: number | null;
created_at: string;
updated_at: string;
}

// ---- GovernancePolicy ----

interface PolicyCondition {
when: string; // CEL expression
description?: string;
}

interface PolicyEnforcement {
action: 'block' | 'gate' | 'log' | 'alert';
message?: string;
approvers?: string[];
alert_channels?: NotificationChannel[];
expires_hours?: number;
}

interface GovernancePolicy {
id: number;
agent_id: number;
policy_type: string;
name: string;
description: string | null;
condition: PolicyCondition;
enforcement: PolicyEnforcement;
is_active: boolean;
created_at: string;
updated_at: string;
}

// ---- AgentMetrics ----

interface AgentMetricsPeriod {
period_start: string;
period_end: string;
total_runs: number;
successful_runs: number;
failed_runs: number;
avg_duration_seconds: number;
p95_duration_seconds: number;
total_tokens_used: number;
total_cost_usd: number;
approval_requests: number;
approval_rate: number;
health_score: number;
error_rate: number;
throughput_per_hour: number;
daily: AgentMetricsDailyPoint[];
}

interface AgentMetricsDailyPoint {
date: string;
total_runs: number;
successful_runs: number;
failed_runs: number;
avg_duration_seconds: number;
total_tokens_used: number;
total_cost_usd: number;
}

// ---- AuditLogEntry ----

interface AuditLogEntry {
id: number;
organization_id: number;
workspace_id: number;
event_type: AuditEventType;
actor_user_id: number | null;
actor_type: 'user' | 'agent' | 'system' | 'scheduler';
resource_type: string;
resource_id: number | null;
agent_id: number | null;
agent_name: string | null; // Joined
execution_id: number | null;
details: Record<string, unknown>;
previous_state: Record<string, unknown> | null;
new_state: Record<string, unknown> | null;
ip_address: string | null;
user_agent: string | null;
created_at: string;
}

// ---- WorkspaceSettings ----

interface NotificationConfig {
channels: { type: NotificationChannel; target: string; enabled: boolean }[];
on_success: boolean;
on_failure: boolean;
on_approval_needed: boolean;
on_policy_violation: boolean;
}

interface WorkspaceSettings {
general: {
default_action_level: ActionLevel;
default_model_tier: 'fast' | 'balanced' | 'reasoning' | 'coding';
max_agents_per_workspace: number;
auto_archive_after_days: number | null;
default_notification_channels: NotificationChannel[];
};
compute: {
max_concurrent_executions: number;
max_turns_per_execution: number;
execution_timeout_seconds: number;
max_tokens_per_execution: number;
max_retries: number;
retry_delay_seconds: number;
compute_priority: 'low' | 'medium' | 'high' | 'critical';
};
governance: {
require_approval_above_level: ActionLevel;
strict_governance_mode: boolean;
budget_limit_monthly_usd: number | null;
budget_alert_threshold_percent: number;
require_certification_for_deploy: boolean;
policy_violation_action: 'block' | 'warn' | 'log';
};
access: {
default_agent_visibility: 'workspace' | 'organization';
allowed_categories: AgentCategory[];
role_permissions: Record<string, string[]>;
};
notifications: {
notifications_enabled: boolean;
notify_on_execution_failure: boolean;
notify_on_approval_required: boolean;
notify_on_policy_violation: boolean;
slack_webhook_url: string | null;
teams_webhook_url: string | null;
};
api: {
api_access_enabled: boolean;
api_rate_limit_per_minute: number;
api_keys: ApiKey[];
webhooks: Webhook[];
};
}

// ---- ApiKey ----

interface ApiKey {
id: number;
name: string;
key_prefix: string; // e.g., "ra_k_abc"
permissions: Record<string, unknown>;
is_active: boolean;
expires_at: string | null;
last_used_at: string | null;
usage_count: number;
created_at: string;
}

// ---- Webhook ----

interface Webhook {
id: number;
name: string;
url: string;
events: string[];
headers: Record<string, string>;
is_active: boolean;
last_triggered_at: string | null;
last_status_code: number | null;
failure_count: number;
created_at: string;
updated_at: string;
}

// ---- Tool / DataSource Bindings ----

interface ToolBinding {
id: number;
agent_id: number;
tool_name: string;
tool_category: ToolCategory;
is_enabled: boolean;
config: Record<string, unknown>;
}

interface DataSourceBinding {
id: number;
agent_id: number;
data_source_id: number;
data_source_name: string; // Joined
data_source_type: string; // Joined
access_level: 'read' | 'write' | 'read_write';
connection_status: 'connected' | 'disconnected' | 'error';
last_sync_at: string | null;
}

// ---- Aggregate / Stats Types ----

interface AgentStats {
total_agents: number;
active_agents: number;
running_agents: number;
paused_agents: number;
failed_agents: number;
pending_approvals: number;
agents_by_category: Record<AgentCategory, number>;
agents_by_status: Record<AgentStatus, number>;
}

interface RunStats {
total_runs: number;
running: number;
success: number;
failed: number;
awaiting_approval: number;
avg_duration_seconds: number;
success_rate: number; // 0.0-1.0
}

interface MonitoringStats {
total_active_agents: number;
total_runs_24h: number;
overall_success_rate: number;
avg_latency_ms: number;
total_tokens_24h: number;
active_alerts: number;
queue_depth: number;
system_health: HealthStatus;
}

interface ThroughputDataPoint {
timestamp: string;
requests: number;
errors: number;
latency_ms: number;
}

interface Alert {
id: number;
severity: AlertSeverity;
agent_id: number | null;
agent_name: string | null;
title: string;
message: string;
metric_name: string | null;
metric_value: number | null;
threshold_value: number | null;
acknowledged: boolean;
acknowledged_by: string | null;
created_at: string;
resolved_at: string | null;
}

interface AgentHealth {
agent_id: number;
agent_name: string;
status: AgentStatus;
health_status: HealthStatus;
health_score: number;
success_rate: number;
avg_latency_ms: number;
last_run_at: string | null;
}

interface AuditStats {
total_events: number;
events_by_type: Record<AuditEventType, number>;
unique_agents: number;
unique_users: number;
time_range_start: string;
time_range_end: string;
}

interface GeneratedAgentResult {
agent_config: {
name: string;
description: string;
category: AgentCategory;
action_level: ActionLevel;
instruction_set: string;
tools: string[];
data_source_types: string[];
trigger_config: Record<string, unknown>;
notification_config: NotificationConfig;
governance_level: string;
tags: string[];
};
reasoning: string;
confidence: number; // 0.0-1.0
warnings: string[];
}

// ---- Generic Response Wrappers ----

interface PaginatedResponse<T> {
success: boolean;
status: number;
message: string;
data: {
items: T[];
pagination: {
total: number;
page: number;
page_size: number;
total_pages: number;
has_next: boolean;
has_previous: boolean;
};
};
error: string | null;
meta: { request_id: string; timestamp: string };
}

interface SingleResponse<T> {
success: boolean;
status: number;
message: string;
data: T;
error: string | null;
meta: { request_id: string; timestamp: string };
}

interface ErrorResponse {
success: false;
status: number;
message: string;
data: null;
error: string;
meta: { request_id: string; timestamp: string };
}

interface PaginationMeta {
total: number;
page: number;
page_size: number;
total_pages: number;
has_next: boolean;
has_previous: boolean;
}

Page-by-Page API Specifications

Agent Library (/agents)

On mount -- both calls fire in parallel:

#MethodEndpointQuery ParamsResponsePurpose
1GET/api/v1/agentspage=1&page_size=20PaginatedResponse<Agent>Initial agent list
2GET/api/v1/agents/stats--SingleResponse<AgentStats>Header metric cards

User actions:

ActionTriggerMethodEndpointParams / BodyResponse
SearchDebounced 300ms keystrokeGET/api/v1/agents?search={query}&page=1PaginatedResponse<Agent>
Filter by statusDropdown selectionGET/api/v1/agents?status={status}&page=1PaginatedResponse<Agent>
Filter by categoryDropdown selectionGET/api/v1/agents?category={cat}&page=1PaginatedResponse<Agent>
SortColumn header clickGET/api/v1/agents?sort_by={field}&sort_order={asc/desc}PaginatedResponse<Agent>
PaginatePage button clickGET/api/v1/agents?page={n}&page_size=20PaginatedResponse<Agent>
Edit agentDropdown "Edit"PUT/api/v1/agents/{id}{ name, description, action_level }SingleResponse<Agent>
Delete agentDropdown "Delete" + confirmDELETE/api/v1/agents/{id}?force=true (optional)SingleResponse<{ message: string }>

Create Entry (/agents/create)

No API calls on mount. Static page presenting two creation paths. Navigation only.

ActionResult
Click "Create with AI"Navigate to /agents/create/ai
Click "Guided Setup"Navigate to /agents/create/guided

AI Create (/agents/create/ai)

On mount: No API calls. Renders the input form.

User actions:

ActionMethodEndpointBodyResponse
Submit promptPOST/api/v1/agents/generate{ prompt: string, quick_starter_id?: string }SingleResponse<GeneratedAgentResult>
Accept generated configPOST/api/v1/agentsFull AgentCreateRequest from generated configSingleResponse<Agent>
Load generation historyGET/api/v1/agents/generation-history--PaginatedResponse<GenerationHistoryItem>

Generation animation: While awaiting the POST /api/v1/agents/generate response, the frontend drives a 4-stage animated UI:

StageLabel
1Analyzing requirements
2Designing configuration
3Selecting tools and data sources
4Finalizing

On success, store the generated config in agentStore.draftConfig and allow user to review before posting to POST /api/v1/agents.


Guided Wizard (/agents/create/guided)

Accepts ?template={id} query param. Pre-populates from template config when present.

On mount:

ConditionMethodEndpointResponsePurpose
?template={id} presentGET/api/v1/agents/templates/{id}SingleResponse<Template>Pre-fill all wizard fields
AlwaysGET/api/v1/agents/templatesPaginatedResponse<Template>Optional template selector in Step 1

Per-step API calls:

StepNameAPI Calls
Step 1IdentityNone -- local state only
Step 2Define GoalNone -- local state only
Step 3Data and SystemsGET /api/v1/data-sources -- PaginatedResponse<DataSource>
Step 4Actions and ToolsNone -- local state only
Step 5Review and CreatePOST /api/v1/agents on submit

POST /api/v1/agents request body:

interface AgentCreateRequest {
name: string;
description: string;
category: AgentCategory;
domain: string | null;
action_level: ActionLevel;
governance_level: 'standard' | 'strict' | 'custom';
goal: string;
icon_name: string;
icon_color: string;
icon_bg: string;
tags: string[];
template_id: number | null;
version: {
instruction_set: string;
model_config: ModelConfig;
tools: string[];
data_sources: { data_source_id: number; access_level: string }[];
trigger_config: Record<string, unknown> | null;
approval_rules: ApprovalRules | null;
};
notification_config: NotificationConfig;
}

Response: SingleResponse<Agent>


Agent Detail (/agents/{id})

The most complex page. 7 tabs: Overview, Activity, Data Access, Governance, Monitoring, Audit and Logs, Settings.

On mount -- all three calls fire in parallel:

#MethodEndpointResponsePurpose
1GET/api/v1/agents/{id}SingleResponse<Agent>Full agent detail
2GET/api/v1/agents/{id}/executions?page=1&page_size=10PaginatedResponse<Execution>Recent runs for Overview tab
3GET/api/v1/agents/{id}/metrics?period=7dSingleResponse<AgentMetricsPeriod>Chart data for KPI strip

Hero actions (available on all tabs):

ActionMethodEndpointBodyNotes
DeployPOST/api/v1/agents/{id}/deploy--Optimistic: status -> 'deployed'
PausePOST/api/v1/agents/{id}/pause--Optimistic: status -> 'paused'
ResumePOST/api/v1/agents/{id}/resume--Optimistic: status -> 'active'
Run NowPOST/api/v1/agents/{id}/run{ trigger_payload? }Returns { execution_id }; opens WebSocket
ArchivePOST/api/v1/agents/{id}/archive--Optimistic: status -> 'archived'
DeleteDELETE/api/v1/agents/{id}--Soft delete

Tab: Overview -- no additional API calls. Uses data from mount.

Tab: Activity

#MethodEndpointQuery ParamsResponse
1GET/api/v1/agents/{id}/executionspage=1&page_size=20PaginatedResponse<Execution>

User actions: paginate, click row to navigate to /agents/runs/{execution_id}.

Tab: Data Access

#MethodEndpointResponse
1GET/api/v1/agents/{id}/data-sourcesSingleResponse<DataSourceBinding[]>
User ActionMethodEndpointBody
Add data sourcePOST/api/v1/agents/{id}/data-sources{ data_source_id, access_level, config? }
Remove data sourceDELETE/api/v1/agents/{id}/data-sources/{binding_id}--

Tab: Governance

#MethodEndpointResponse
1GET/api/v1/agents/{id}/policiesSingleResponse<GovernancePolicy[]>
User ActionMethodEndpointBody
Add policyPOST/api/v1/agents/{id}/policies{ policy_id } or inline policy object
Update policyPUT/api/v1/agents/{id}/policies/{policy_id}Updated fields
Remove policyDELETE/api/v1/agents/{id}/policies/{policy_id}--

Tab: Monitoring

#MethodEndpointQuery ParamsResponse
1GET/api/v1/agents/{id}/metricsperiod=30dSingleResponse<AgentMetricsPeriod>

Period switch (7d, 30d, 90d) re-fetches with updated period param.

Tab: Audit and Logs

#MethodEndpointQuery ParamsResponse
1GET/api/v1/agents/{id}/auditpage=1&page_size=20PaginatedResponse<AuditLogEntry>

Export: GET /api/v1/agents/{id}/audit?format=csv

Tab: Settings -- all three fire in parallel on tab activation:

#MethodEndpointResponse
1GET/api/v1/agents/{id}/triggersSingleResponse<AgentTrigger[]>
2GET/api/v1/agents/{id}/toolsSingleResponse<ToolBinding[]>
3GET/api/v1/agents/{id}/versionsSingleResponse<AgentVersion[]>
User ActionMethodEndpointBody
Add triggerPOST/api/v1/agents/{id}/triggersTrigger object sans id
Update triggerPUT/api/v1/agents/{id}/triggers/{tid}Updated fields
Delete triggerDELETE/api/v1/agents/{id}/triggers/{tid}--
Rollback versionPOST/api/v1/agents/{id}/versions/{vid}/rollback--

Template Library (/agents/templates)

On mount:

#MethodEndpointQuery ParamsResponse
1GET/api/v1/agents/templatespage=1&page_size=20PaginatedResponse<Template>

User actions:

ActionMethodEndpointParams
SearchGET/api/v1/agents/templates?search={query}&page=1
Filter by categoryGET/api/v1/agents/templates?category={cat}&page=1
Use template----Navigate to /agents/create/guided?template={id}

Template Detail (/agents/templates/{id})

On mount:

#MethodEndpointResponse
1GET/api/v1/agents/templates/{id}SingleResponse<Template>

"Use This Template" navigates to /agents/create/guided?template={id}. No additional API call.


System Monitoring (/agents/monitoring)

On mount -- all four calls fire in parallel:

#MethodEndpointQuery ParamsResponsePurpose
1GET/api/v1/agents/monitoring/stats--SingleResponse<MonitoringStats>System health KPIs
2GET/api/v1/agents/monitoring/throughputperiod=24h&granularity=1hSingleResponse<ThroughputDataPoint[]>Request/error/latency chart
3GET/api/v1/agents/monitoring/alerts--SingleResponse<Alert[]>Active alerts list
4GET/api/v1/agents/monitoring/health--SingleResponse<AgentHealth[]>Per-agent health table

After initial load: subscribe to monitoring.stats_update WebSocket events at WS /api/v1/agents/ws/monitoring. When connected, stop HTTP polling. On WebSocket disconnect, fall back to polling all four endpoints every 30 seconds.

User actions:

ActionMethodEndpointBody
Acknowledge alertPATCH/api/v1/agents/monitoring/alerts/{id}{ acknowledged: true }
Change periodGET/api/v1/agents/monitoring/throughput?period={1h/6h/24h/7d}

Runs List (/agents/runs)

On mount -- both calls fire in parallel:

#MethodEndpointQuery ParamsResponsePurpose
1GET/api/v1/agents/runspage=1&page_size=20PaginatedResponse<Execution>All runs across agents
2GET/api/v1/agents/runs/stats--SingleResponse<RunStats>Summary cards

User actions:

ActionTriggerMethodEndpointParams / BodyResponse
SearchDebounced 300msGET/api/v1/agents/runs?search={query}&page=1PaginatedResponse<Execution>
Filter by statusDropdownGET/api/v1/agents/runs?status={status}&page=1PaginatedResponse<Execution>
Filter by agentDropdownGET/api/v1/agents/runs?agent_id={id}&page=1PaginatedResponse<Execution>
Cancel run"Stop" buttonPOST/api/v1/agents/runs/{id}/stop--SingleResponse<Execution>
Retry run"Retry" buttonPOST/api/v1/agents/runs/{id}/retry--SingleResponse<{ execution_id: number }>
Open approval"Review" buttonGET/api/v1/approvals/{approval_id}--SingleResponse<ApprovalRequest>
ApproveModal confirmPATCH/api/v1/approvals/{id}{ status: 'approved', resolution_comment? }SingleResponse<ApprovalRequest>
RejectModal confirmPATCH/api/v1/approvals/{id}{ status: 'rejected', resolution_comment: string }SingleResponse<ApprovalRequest>
Edit and approveModify + confirmPATCH/api/v1/approvals/{id}{ status: 'edited_approved', modified_payload: {...}, resolution_comment? }SingleResponse<ApprovalRequest>

Run Detail (/agents/runs/{id})

On mount:

#MethodEndpointResponsePurpose
1GET/api/v1/agents/runs/{id}SingleResponse<Execution>Execution metadata
2GET/api/v1/agents/runs/{id}/logsSingleResponse<ExecutionStep[]>Full step trace

If status is running or awaiting_approval, open the per-execution WebSocket: WS /api/v1/agents/ws/runs/{id}. Append incoming steps to executionStore.steps in real time.

User actions:

ActionMethodEndpointBody
Stop executionPOST/api/v1/agents/runs/{id}/stop--
Retry executionPOST/api/v1/agents/runs/{id}/retry--
Download logsGET/api/v1/agents/runs/{id}/logs?format=json-- (file download)

Workspace Settings (/agents/settings)

On mount:

#MethodEndpointResponse
1GET/api/v1/agents/settingsSingleResponse<WorkspaceSettings>

Per-tab save actions (each tab has its own "Save Changes" button):

TabMethodEndpointBody
GeneralPUT/api/v1/agents/settings/generalWorkspaceSettings['general']
Compute and LimitsPUT/api/v1/agents/settings/computeWorkspaceSettings['compute']
GovernancePUT/api/v1/agents/settings/governanceWorkspaceSettings['governance']
Access ControlPUT/api/v1/agents/settings/accessWorkspaceSettings['access']
NotificationsPUT/api/v1/agents/settings/notificationsWorkspaceSettings['notifications']
API and WebhooksPUT/api/v1/agents/settings/apiWorkspaceSettings['api']

API and Webhooks tab -- additional actions:

ActionMethodEndpointBodyNotes
Create API keyPOST/api/v1/agents/settings/api/keys{ name, permissions, expires_at? }Returns ApiKey & { secret: string } -- shown once
Delete API keyDELETE/api/v1/agents/settings/api/keys/{id}----
Create webhookPOST/api/v1/agents/settings/api/webhooks{ url, events, name, secret? }--
Update webhookPUT/api/v1/agents/settings/api/webhooks/{id}Updated fields--
Delete webhookDELETE/api/v1/agents/settings/api/webhooks/{id}----
Test webhookPOST/api/v1/agents/settings/api/webhooks/{id}/test--Returns { status_code, response_time_ms }

Audit and Logs (/agents/audit)

On mount -- both calls fire in parallel:

#MethodEndpointQuery ParamsResponse
1GET/api/v1/agents/auditpage=1&page_size=20PaginatedResponse<AuditLogEntry>
2GET/api/v1/agents/audit/stats--SingleResponse<AuditStats>

User actions:

ActionTriggerMethodEndpointParams
SearchDebounced 300msGET/api/v1/agents/audit?search={query}&page=1
Filter by event typeDropdownGET/api/v1/agents/audit?event_type={type}&page=1
Filter by agentDropdownGET/api/v1/agents/audit?agent_id={id}&page=1
Filter by userDropdownGET/api/v1/agents/audit?user_id={id}&page=1
Filter by date rangeDate pickerGET/api/v1/agents/audit?date_from={iso}&date_to={iso}&page=1
Export"Export" buttonGET/api/v1/agents/audit/export?format=csv&{current_filters}

No edit or delete controls are rendered. The UI enforces the write-once audit log constraint by not exposing mutation actions.

Zustand Store Designs

agentStore

interface AgentListFilters {
search?: string;
category?: AgentCategory;
status?: AgentStatus;
sort_by?: string;
sort_order?: 'asc' | 'desc';
page: number;
page_size: number;
}

interface LoadingState {
list: boolean;
detail: boolean;
create: boolean;
update: boolean;
delete: boolean;
[actionKey: string]: boolean; // e.g., loading['deploy'] = true
}

interface AgentStore {
agents: Agent[];
currentAgent: Agent | null;
stats: AgentStats | null;
versions: AgentVersion[];
draftConfig: Partial<GeneratedAgentResult['agent_config']> | null;
filters: AgentListFilters;
pagination: PaginationMeta | null;
loading: LoadingState;
error: string | null;

fetchAgents: () => Promise<void>;
fetchAgentStats: () => Promise<void>;
fetchAgentDetail: (id: number) => Promise<void>;
fetchVersions: (id: number) => Promise<void>;
createAgent: (data: AgentCreateRequest) => Promise<Agent>;
updateAgent: (id: number, data: Partial<AgentCreateRequest>) => Promise<Agent>;
deleteAgent: (id: number, force?: boolean) => Promise<void>;
deployAgent: (id: number) => Promise<Agent>;
pauseAgent: (id: number) => Promise<Agent>;
resumeAgent: (id: number) => Promise<Agent>;
archiveAgent: (id: number) => Promise<Agent>;
runAgent: (id: number, payload?: Record<string, unknown>) => Promise<number>; // -> execution_id
generateAgent: (prompt: string) => Promise<GeneratedAgentResult>;
setFilters: (filters: Partial<AgentListFilters>) => void;
setDraftConfig: (config: Partial<GeneratedAgentResult['agent_config']> | null) => void;
clearCurrentAgent: () => void;
reset: () => void;
}
  • fetchAgents calls GET /api/v1/agents with serialized filters; updates agents and pagination.
  • setFilters merges the partial update into filters and calls fetchAgents.
  • deployAgent, pauseAgent, resumeAgent, archiveAgent all apply optimistic status updates before the API call and roll back on failure.
  • setDraftConfig is local-only -- does not call the API. Used by the AI creation flow to pass generated config to the wizard.

executionStore

interface RunFilters {
search?: string;
status?: ExecutionStatus | 'all';
agent_id?: number | 'all';
page: number;
page_size: number;
}

interface ExecutionStore {
executions: Execution[];
currentExecution: Execution | null;
currentSteps: ExecutionStep[];
stats: RunStats | null;
pagination: PaginationMeta | null;
filters: RunFilters;
wsConnected: boolean;
loading: LoadingState;
error: string | null;

fetchRuns: () => Promise<void>;
fetchRunStats: () => Promise<void>;
fetchRunDetail: (id: number) => Promise<void>;
fetchRunSteps: (id: number) => Promise<void>;
fetchAgentExecutions: (agentId: number, page?: number) => Promise<void>;
stopRun: (id: number) => Promise<void>;
retryRun: (id: number) => Promise<number>; // -> new execution_id
connectRunWebSocket: (executionId: number) => void;
disconnectRunWebSocket: () => void;
appendStep: (step: ExecutionStep) => void; // Called by WebSocket handler
updateExecutionStatus: (id: number, status: ExecutionStatus) => void;
setFilters: (filters: Partial<RunFilters>) => void;
reset: () => void;
}
  • connectRunWebSocket is called by Run Detail on mount when status is non-terminal. Sets wsConnected = true and wires the WebSocket message handler to call appendStep and updateExecutionStatus.
  • disconnectRunWebSocket is called on component unmount or when a terminal execution.status_change WebSocket event arrives.
  • appendStep appends to currentSteps -- steps are displayed in chronological order in the log viewer.
  • cancelRun calls POST /api/v1/agents/runs/{id}/stop and calls updateExecutionStatus(id, 'cancelled') on success.

approvalStore

interface ApprovalStore {
currentApproval: ApprovalRequest | null;
isLoading: boolean;
error: string | null;

fetchApproval: (id: number) => Promise<void>;
approveRequest: (id: number, comment?: string) => Promise<void>;
rejectRequest: (id: number, comment: string) => Promise<void>;
editAndApprove: (
id: number,
modifiedPayload: Record<string, unknown>,
comment?: string
) => Promise<void>;
clearApproval: () => void;
}
  • approveRequest applies an optimistic update (sets status = 'approved') before the API call and rolls back on failure.
  • rejectRequest and editAndApprove follow the same optimistic pattern.

templateStore

interface TemplateFilters {
search?: string;
category?: AgentCategory | 'all';
page: number;
page_size: number;
}

interface TemplateStore {
templates: Template[];
currentTemplate: Template | null;
pagination: PaginationMeta | null;
filters: TemplateFilters;
loading: LoadingState;
error: string | null;

fetchTemplates: () => Promise<void>;
fetchTemplateDetail: (id: number) => Promise<void>;
setFilters: (filters: Partial<TemplateFilters>) => void;
reset: () => void;
}
  • fetchTemplateDetail is called by both Template Detail and the Guided Wizard (when ?template={id} is present). If currentTemplate already matches the requested ID, the store skips the network call.

monitoringStore

interface MonitoringStore {
stats: MonitoringStats | null;
throughput: ThroughputDataPoint[];
alerts: Alert[];
agentHealth: AgentHealth[];
period: '1h' | '6h' | '24h' | '7d';
wsConnected: boolean;
loading: LoadingState;
error: string | null;
lastRefreshedAt: string | null;

fetchAll: () => Promise<void>; // Parallel fetch all 4 endpoints
fetchStats: () => Promise<void>;
fetchThroughput: () => Promise<void>;
fetchAlerts: () => Promise<void>;
fetchAgentHealth: () => Promise<void>;
acknowledgeAlert: (id: number) => Promise<void>;
connectWebSocket: () => void;
disconnectWebSocket: () => void;
setPeriod: (period: MonitoringStore['period']) => void;
updateFromWebSocket: (data: MonitoringStats) => void;
reset: () => void;
}
  • connectWebSocket connects to WS /api/v1/agents/ws/monitoring and calls updateFromWebSocket on each incoming event.
  • When wsConnected = false, the component polls fetchAll every 30 seconds via a useEffect timer.

agentSettingsStore

interface AgentSettingsStore {
settings: WorkspaceSettings | null;
originalSettings: WorkspaceSettings | null; // for cancel / reset
isDirty: boolean;
activeTab: string;
isLoading: boolean;
isSaving: boolean;
error: string | null;

fetchSettings: () => Promise<void>;
saveGeneral: (data: WorkspaceSettings['general']) => Promise<void>;
saveCompute: (data: WorkspaceSettings['compute']) => Promise<void>;
saveGovernance: (data: WorkspaceSettings['governance']) => Promise<void>;
saveAccess: (data: WorkspaceSettings['access']) => Promise<void>;
saveNotifications: (data: WorkspaceSettings['notifications']) => Promise<void>;
saveApi: (data: WorkspaceSettings['api']) => Promise<void>;
createApiKey: (data: { name: string; permissions: Record<string, unknown> }) => Promise<ApiKey & { secret: string }>;
deleteApiKey: (id: number) => Promise<void>;
createWebhook: (data: Partial<Webhook>) => Promise<Webhook>;
updateWebhook: (id: number, data: Partial<Webhook>) => Promise<Webhook>;
deleteWebhook: (id: number) => Promise<void>;
testWebhook: (id: number) => Promise<{ status_code: number; response_time_ms: number }>;
setLocalField: (path: string, value: unknown) => void;
resetChanges: () => void;
setActiveTab: (tab: string) => void;
}
  • setLocalField makes a local change to settings and sets isDirty = true. It does not call the API.
  • resetChanges restores settings from originalSettings and sets isDirty = false.
  • isSaving is a dedicated flag separate from isLoading so the Save button spinner does not affect the page-level skeleton.

auditStore

interface AuditFilters {
search?: string;
event_type?: AuditEventType | 'all';
agent_id?: number | 'all';
user_id?: number | 'all';
date_from?: string | null;
date_to?: string | null;
page: number;
page_size: number;
}

interface AuditStore {
logs: AuditLogEntry[];
stats: AuditStats | null;
pagination: PaginationMeta | null;
filters: AuditFilters;
expandedRowId: number | null;
isLoading: boolean;
exporting: boolean;
error: string | null;

fetchLogs: () => Promise<void>;
fetchAuditStats: () => Promise<void>;
exportLogs: () => Promise<void>;
setFilters: (filters: Partial<AuditFilters>) => void;
toggleExpandedRow: (id: number) => void;
reset: () => void;
}
  • fetchLogs never triggers an optimistic update -- audit data is read-only.
  • exportLogs sets exporting = true, calls GET /api/v1/agents/audit/export?format=csv, then sets exporting = false. For direct-download responses it creates and clicks an <a download> element. For email-delivery responses it shows a toast.
  • No update, delete, or clearLogs actions exist in this store. The absence of mutation actions is intentional and documents the write-once enforcement.

WebSocket Event Contract

Connection Endpoints

WS /api/v1/agents/ws/runs/{execution_id}  -- per-execution streaming (Run Detail)
WS /api/v1/agents/ws/monitoring -- workspace-wide monitoring (Monitoring)
WS /api/v1/agents/ws/workspace -- workspace-wide event bus (Agent Library, Runs List)

Authentication: Sec-WebSocket-Protocol: Bearer {access_token}

Server-to-Client Events

execution.step_update

Fired when a reasoning turn or tool call step completes.

interface ExecutionStepUpdateEvent {
type: 'execution.step_update';
execution_id: number;
step: {
step_number: number;
step_type: StepType;
tool_name: string | null;
tool_category: ToolCategory | null;
status: 'processing' | 'completed' | 'failed' | 'blocked';
message: string | null;
governance_decision: string | null;
latency_ms: number | null;
};
timestamp: string;
}

Subscribed by: Run Detail, Agent Detail "Run Now" flow


execution.status_change

Fired when the overall execution status changes.

interface ExecutionStatusChangeEvent {
type: 'execution.status_change';
execution_id: number;
agent_id: number;
previous_status: ExecutionStatus;
new_status: ExecutionStatus;
error_message: string | null;
timestamp: string;
}

Subscribed by: Run Detail, Runs List, Agent Detail


execution.progress

Fired periodically during execution to indicate overall progress.

interface ExecutionProgressEvent {
type: 'execution.progress';
execution_id: number;
turn: number;
max_turns: number;
progress_percentage: number; // 0-100
current_phase: string; // e.g., "Analyzing data sources"
timestamp: string;
}

Subscribed by: Run Detail (progress bar), Agent Detail "Run Now" modal


approval.requested

Fired when an execution hits an approval gate.

interface ApprovalRequestedEvent {
type: 'approval.requested';
approval_id: number;
execution_id: number;
agent_id: number;
agent_name: string;
action_tool: string;
action_description: string;
reasoning_summary: string;
risk_context: string | null;
expires_at: string;
timestamp: string;
}

Subscribed by: Runs List (badge update), Agent Detail, global notification system


approval.resolved

Fired when a pending approval is resolved.

interface ApprovalResolvedEvent {
type: 'approval.resolved';
approval_id: number;
execution_id: number;
agent_id: number;
status: ApprovalStatus;
resolved_by_name: string | null;
resolution_comment: string | null;
timestamp: string;
}

Subscribed by: Runs List, Run Detail, Agent Detail


agent.status_change

Fired when an agent's lifecycle status changes.

interface AgentStatusChangeEvent {
type: 'agent.status_change';
agent_id: number;
agent_name: string;
previous_status: AgentStatus;
new_status: AgentStatus;
changed_by: string; // User display name
timestamp: string;
}

Subscribed by: Agent Library, Agent Detail, Monitoring


monitoring.stats_update

Fired every 10 seconds with live monitoring data.

interface MonitoringStatsUpdateEvent {
type: 'monitoring.stats_update';
stats: MonitoringStats;
throughput_latest: ThroughputDataPoint;
timestamp: string;
}

Subscribed by: Monitoring page


notification

General notification for any significant event.

interface NotificationEvent {
type: 'notification';
id: string;
severity: AlertSeverity;
title: string;
message: string;
agent_id: number | null;
execution_id: number | null;
action_url: string | null; // Deep link within the app
timestamp: string;
}

Subscribed by: Global notification bell in the app header


Client-to-Server Events

user_response

Sent when a user responds to a clarification or confirmation request during an execution.

interface UserResponseMessage {
type: 'user_response';
execution_id: number;
request_id: string;
response_type: 'selection' | 'confirmation' | 'input' | 'cancelled';
value: unknown;
free_text?: string;
timestamp: string;
}

Page Subscription Matrix

PageEvents SubscribedConnection Lifecycle
Agent Libraryagent.status_change, notificationGlobal WS, connected while on page
Agent Detailagent.status_change, execution.status_change, approval.requestedGlobal WS, per-agent filter
Run Detailexecution.step_update, execution.progress, execution.status_change, approval.requested, approval.resolvedPer-execution WS, opened on mount if run is active; closed on terminal event
Runs Listexecution.status_change, approval.requested, approval.resolvedGlobal WS, connected while on page
Monitoringmonitoring.stats_update, agent.status_changeGlobal WS or 30s HTTP polling (fallback)
SettingsNoneNo WS
Audit and LogsNoneNo WS (historical data)
TemplatesNoneNo WS

Error Handling Contract

Standard Error Response Shape

{
"success": false,
"status": 422,
"message": "Validation failed",
"data": null,
"error": "validation_error",
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-05-10T08:04:22Z"
}
}

Error Code Reference

HTTP StatusError CodeUI TreatmentUser Message
400validation_errorInline field errors from details.fields arrayField-level messages
400agent_invalid_state_transitionInline error banner; revert optimistic update"Agent cannot transition to that state"
400agent_budget_exhaustedTerminal status badge in Run Detail(shown via execution status)
401unauthorizedAuto-refresh token; if fails, redirect to /signin--
401invalid_refresh_tokenClear all auth; redirect to /signin--
403forbiddenToast (error)"You don't have permission to perform this action"
403governance_policy_blockedModal showing policy name and block reasonPolicy name from details.policy_name
403tool_acl_deniedAppend error step to Run Detail log(shown in step trace)
404agent_not_foundRedirect to /agents with toast"Agent not found"
404execution_not_foundRedirect to /agents/runs with toast"Execution not found"
404template_not_foundRedirect to /agents/templates with toast"Template not found"
409conflictToast (warning)"Agent is already in the requested state"
409agent_already_runningToast (warning)"Agent is already executing. Wait for the current run to complete."
409approval_already_resolvedToast (warning); refresh approval list"This approval was already resolved"
422unprocessable_entityModal with detailed validation errorsField-level detail from details.fields
422trigger_config_invalidWizard Step 2 inline field errorsFrom details.fields
422data_source_unreachableWizard Step 3 inline error below selector--
422validation_failedAsync validation errors in wizard; navigate back to failing stepFrom validation polling response
429rate_limitedToast (warning)"Too many requests. Please wait {retry_after} seconds."
402workspace_quota_exceededModal with quota details"Workspace limit reached"
500internal_errorToast (error)"Something went wrong. Please try again." + show request_id
502, 503service_unavailableFull-page error state with retry button"The service is temporarily unavailable. Retrying..."

Error Display Rules

  • All errors surface the human-readable message field.
  • The meta.request_id is shown in a collapsed "Technical Details" section for support escalation.
  • Network errors (no response received) display: "Connection error -- check your network and try again."
  • 401 responses trigger the token refresh flow. If refresh fails, the current URL is preserved as ?redirect before redirecting to /signin.
  • Error responses to optimistic mutations must always trigger a rollback to the prior state before displaying the error message.

Loading and Optimistic Update Patterns

LoadingState Design

Each store uses per-action loading flags rather than a single isLoading boolean. This allows the UI to show targeted spinners -- for example, a spinner on the Deploy button without freezing the entire Agent Detail page.

interface LoadingState {
list: boolean; // paginated list fetches
detail: boolean; // single-entity fetches
create: boolean; // POST operations
update: boolean; // PUT/PATCH operations
delete: boolean; // DELETE operations
[actionKey: string]: boolean; // named lifecycle actions: loading['deploy'] = true
}

All flags start as false. The calling action sets the relevant flag to true before the await and to false in the finally block.

Optimistic Update Pattern

Used for lifecycle transitions and approval resolutions where the API response is fast and failures are rare.

// Example: agentStore -- deploy action
async deployAgent(id: number) {
const prior = get().currentAgent;

// 1. Apply optimistic update immediately
set(state => ({
currentAgent: state.currentAgent
? { ...state.currentAgent, status: 'deployed' as AgentStatus }
: null,
loading: { ...state.loading, deploy: true }
}));

try {
const updated = await api.post<Agent>(`/api/v1/agents/${id}/deploy`);
// 2. Confirm with server response
set({ currentAgent: updated.data });
} catch (err) {
// 3. Roll back to prior state on failure
set({ currentAgent: prior });
throw err;
} finally {
set(state => ({ loading: { ...state.loading, deploy: false } }));
}
}

Optimistic Rollback Rule

Any action with an optimistic update must snapshot the prior state before modifying it. On catch, the snapshot is restored and the error is re-thrown so the calling component can display the error message. Optimistic failures are never swallowed silently.

Non-Optimistic Operations

These operations are not optimistic and display a blocking spinner:

  • createAgent -- the wizard is already a multi-step flow; the user expects to wait
  • exportAuditLog -- exporting flag drives a disabled button state, not an inline spinner
  • All fetchX operations -- show skeleton loaders, not optimistic state

Cache Invalidation Rules

User ActionInvalidated Store Slices
Agent lifecycle transition (deploy, pause, resume, archive)agentStore.currentAgent (optimistic update in place); update matching entry in agentStore.agents
Agent configuration saveagentStore.currentAgent, agentStore.versions
Agent deleteRemove from agentStore.agents; clear agentStore.currentAgent if matching
Manual run triggeredPrepend to executionStore.executions; invalidate agentStore.currentAgent metrics summary
Run completed (via WebSocket execution.status_change)executionStore.currentExecution, executionStore.executions, agent metrics summary
Run cancelledexecutionStore.currentExecution, executionStore.executions
Approval resolvedapprovalStore.currentApproval; update matching entry in executionStore.executions
Data source boundRe-fetch GET /api/v1/agents/{id}/data-sources
Data source unboundRemove from local DataSourceBinding[]; no full re-fetch needed
Template applied to new agentNo template cache invalidation -- templates are read-only during wizard
Settings tab savedagentSettingsStore.settings, agentSettingsStore.originalSettings
API key createdAppend to agentSettingsStore.settings.api.api_keys
API key deletedRemove from agentSettingsStore.settings.api.api_keys
Webhook created / updatedRefresh agentSettingsStore.settings.api.webhooks
Audit log viewedNo invalidation -- audit data is never mutated
Metrics period changeexecutionStore metrics only; does not invalidate agentStore.agents