From 024067ea12381961a4ac5ee7be995f268dd9243a Mon Sep 17 00:00:00 2001 From: Lev Neiman Date: Wed, 25 Feb 2026 17:32:20 -0500 Subject: [PATCH 1/6] feat: hard-cut migrate agent identity to name-native --- CONTRIBUTING.md | 2 +- README.md | 6 +- docs/REFERENCE.md | 14 +- docs/observability.md | 40 +- docs/testing.md | 2 +- engine/tests/test_core.py | 64 +- examples/README.md | 2 +- examples/agent_control_demo/demo_agent.py | 3 +- examples/agent_control_demo/setup_controls.py | 42 +- .../agent_control_demo/update_controls.py | 22 +- examples/crewai/content_agent_protection.py | 3 +- examples/crewai/setup_content_controls.py | 8 +- examples/customer_support_agent/README.md | 2 +- examples/customer_support_agent/demo.sh | 6 +- examples/customer_support_agent/run_demo.py | 10 +- .../setup_demo_controls.py | 11 +- .../customer_support_agent/support_agent.py | 3 +- examples/deepeval/qa_agent.py | 3 +- examples/deepeval/setup_controls.py | 10 +- .../langchain/langgraph_auto_schema_agent.py | 3 +- examples/langchain/setup_sql_controls.py | 10 +- examples/langchain/sql_agent_protection.py | 5 +- models/README.md | 14 +- models/src/agent_control_models/agent.py | 36 +- models/src/agent_control_models/errors.py | 14 +- models/src/agent_control_models/evaluation.py | 27 +- .../src/agent_control_models/observability.py | 83 +- models/src/agent_control_models/server.py | 7 +- sdks/python/ARCHITECTURE.md | 8 +- sdks/python/README.md | 18 +- sdks/python/src/agent_control/__init__.py | 66 +- sdks/python/src/agent_control/agents.py | 149 +- .../src/agent_control/control_decorators.py | 30 +- sdks/python/src/agent_control/evaluation.py | 243 +- sdks/python/src/agent_control/policies.py | 11 +- sdks/python/src/agent_control/validation.py | 28 +- sdks/python/tests/README.md | 4 +- sdks/python/tests/conftest.py | 13 +- sdks/python/tests/test_agent_id_validation.py | 36 +- sdks/python/tests/test_control_decorators.py | 38 +- sdks/python/tests/test_evaluation.py | 2 +- sdks/python/tests/test_init_conflict.py | 5 +- sdks/python/tests/test_init_step_merge.py | 12 +- sdks/python/tests/test_init_validation.py | 6 +- sdks/python/tests/test_integration_agents.py | 24 +- sdks/python/tests/test_local_evaluation.py | 67 +- sdks/python/tests/test_observability.py | 2 +- .../tests/test_observability_updates.py | 17 +- sdks/python/tests/test_validation.py | 42 +- .../overlays/method-names.overlay.yaml | 16 +- sdks/typescript/src/client.ts | 1 - .../generated/funcs/agents-delete-policy.ts | 14 +- .../generated/funcs/agents-get-evaluator.ts | 14 +- .../src/generated/funcs/agents-get-policy.ts | 14 +- .../src/generated/funcs/agents-get.ts | 14 +- .../src/generated/funcs/agents-init.ts | 8 +- .../generated/funcs/agents-list-controls.ts | 14 +- .../generated/funcs/agents-list-evaluators.ts | 16 +- .../src/generated/funcs/agents-list.ts | 4 +- .../generated/funcs/agents-update-policy.ts | 16 +- .../src/generated/funcs/agents-update.ts | 14 +- .../src/generated/funcs/evaluators-list.ts | 2 +- .../funcs/observability-get-control-stats.ts | 4 +- .../funcs/observability-get-stats.ts | 4 +- .../funcs/observability-query-events.ts | 2 +- .../src/generated/models/agent-ref.ts | 8 +- .../src/generated/models/agent-summary.ts | 8 +- sdks/typescript/src/generated/models/agent.ts | 13 +- .../models/control-execution-event.ts | 14 +- .../models/control-stats-response.ts | 10 +- .../generated/models/evaluation-request.ts | 12 +- .../generated/models/event-query-request.ts | 12 +- .../generated/models/get-agent-response.ts | 2 +- .../generated/models/init-agent-request.ts | 2 +- ...cy-api-v1-agents-agent-id-policy-delete.ts | 42 - ...-api-v1-agents-agent-name-policy-delete.ts | 42 + .../get-agent-api-v1-agents-agent-id-get.ts | 40 - .../get-agent-api-v1-agents-agent-name-get.ts | 42 + ...-agent-id-evaluators-evaluator-name-get.ts | 49 - ...gent-name-evaluators-evaluator-name-get.ts | 49 + ...olicy-api-v1-agents-agent-id-policy-get.ts | 42 - ...icy-api-v1-agents-agent-name-policy-get.ts | 42 + ...rvability-stats-controls-control-id-get.ts | 8 +- ...et-stats-api-v1-observability-stats-get.ts | 8 +- .../src/generated/models/operations/index.ts | 16 +- ...ols-api-v1-agents-agent-id-controls-get.ts | 42 - ...s-api-v1-agents-agent-name-controls-get.ts | 41 + ...s-api-v1-agents-agent-id-evaluators-get.ts | 48 - ...api-v1-agents-agent-name-evaluators-get.ts | 48 + ...atch-agent-api-v1-agents-agent-id-patch.ts | 46 - ...ch-agent-api-v1-agents-agent-name-patch.ts | 46 + ...1-agents-agent-id-policy-policy-id-post.ts | 46 - ...agents-agent-name-policy-policy-id-post.ts | 46 + .../src/generated/models/stats-response.ts | 10 +- sdks/typescript/src/generated/sdk/agents.ts | 45 +- .../src/generated/sdk/evaluators.ts | 2 +- .../src/generated/sdk/observability.ts | 6 +- sdks/typescript/tests/client-api.test.ts | 1 - .../test_generate_method_names_overlay.py | 6 +- server/README.md | 14 +- .../alembic/versions/9f2c5f7a722f_initial.py | 4 +- .../versions/simplify_observability_schema.py | 6 +- .../agent_control_server/endpoints/agents.py | 260 +- .../endpoints/controls.py | 7 +- .../endpoints/evaluation.py | 11 +- .../endpoints/evaluators.py | 2 +- .../endpoints/observability.py | 19 +- server/src/agent_control_server/errors.py | 4 +- server/src/agent_control_server/main.py | 2 +- server/src/agent_control_server/models.py | 17 +- .../observability/ingest/direct.py | 1 - .../observability/store/base.py | 5 +- .../observability/store/postgres.py | 31 +- .../agent_control_server/services/controls.py | 5 +- .../services/schema_compat.py | 2 +- server/tests/test_agents_additional.py | 88 +- server/tests/test_controls_additional.py | 14 +- server/tests/test_error_handling.py | 23 +- server/tests/test_evaluation_e2e.py | 56 +- .../test_evaluation_e2e_list_evaluator.py | 68 +- .../test_evaluation_e2e_sql_evaluator.py | 80 +- .../tests/test_evaluation_error_handling.py | 18 +- server/tests/test_evaluator_schemas.py | 15 +- server/tests/test_init_agent.py | 56 +- server/tests/test_init_agent_conflict_mode.py | 17 +- server/tests/test_init_agent_force_replace.py | 18 +- server/tests/test_new_features.py | 39 +- .../tests/test_observability_direct_ingest.py | 10 +- server/tests/test_observability_endpoints.py | 90 +- server/tests/test_observability_models.py | 31 +- .../test_observability_store_postgres.py | 32 +- server/tests/test_policy_integration.py | 14 +- server/tests/test_services_controls.py | 6 +- server/tests/utils.py | 17 +- ui/src/core/api/client.ts | 35 +- ui/src/core/api/generated/api-types.ts | 7963 +++++++++-------- ui/src/core/api/types.ts | 10 +- .../hooks/query-hooks/use-agent-controls.ts | 8 +- .../hooks/query-hooks/use-agent-monitor.ts | 8 +- ui/src/core/hooks/query-hooks/use-agent.ts | 12 +- .../hooks/query-hooks/use-has-monitor-data.ts | 8 +- ui/src/core/layouts/app-layout.tsx | 6 +- .../agent-detail/agent-detail.tsx | 4 +- .../modals/control-store/index.tsx | 4 +- ui/src/core/page-components/home/home.tsx | 5 +- ui/tests/fixtures.ts | 22 +- ui/tests/home.spec.ts | 10 +- 147 files changed, 5634 insertions(+), 5867 deletions(-) delete mode 100644 sdks/typescript/src/generated/models/operations/delete-agent-policy-api-v1-agents-agent-id-policy-delete.ts create mode 100644 sdks/typescript/src/generated/models/operations/delete-agent-policy-api-v1-agents-agent-name-policy-delete.ts delete mode 100644 sdks/typescript/src/generated/models/operations/get-agent-api-v1-agents-agent-id-get.ts create mode 100644 sdks/typescript/src/generated/models/operations/get-agent-api-v1-agents-agent-name-get.ts delete mode 100644 sdks/typescript/src/generated/models/operations/get-agent-evaluator-api-v1-agents-agent-id-evaluators-evaluator-name-get.ts create mode 100644 sdks/typescript/src/generated/models/operations/get-agent-evaluator-api-v1-agents-agent-name-evaluators-evaluator-name-get.ts delete mode 100644 sdks/typescript/src/generated/models/operations/get-agent-policy-api-v1-agents-agent-id-policy-get.ts create mode 100644 sdks/typescript/src/generated/models/operations/get-agent-policy-api-v1-agents-agent-name-policy-get.ts delete mode 100644 sdks/typescript/src/generated/models/operations/list-agent-controls-api-v1-agents-agent-id-controls-get.ts create mode 100644 sdks/typescript/src/generated/models/operations/list-agent-controls-api-v1-agents-agent-name-controls-get.ts delete mode 100644 sdks/typescript/src/generated/models/operations/list-agent-evaluators-api-v1-agents-agent-id-evaluators-get.ts create mode 100644 sdks/typescript/src/generated/models/operations/list-agent-evaluators-api-v1-agents-agent-name-evaluators-get.ts delete mode 100644 sdks/typescript/src/generated/models/operations/patch-agent-api-v1-agents-agent-id-patch.ts create mode 100644 sdks/typescript/src/generated/models/operations/patch-agent-api-v1-agents-agent-name-patch.ts delete mode 100644 sdks/typescript/src/generated/models/operations/set-agent-policy-api-v1-agents-agent-id-policy-policy-id-post.ts create mode 100644 sdks/typescript/src/generated/models/operations/set-agent-policy-api-v1-agents-agent-name-policy-policy-id-post.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59b79f57..94e20531 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -159,7 +159,7 @@ sdks/python/src/agent_control/ import agent_control # Initialization -agent_control.init(agent_name="...", agent_id="...") +agent_control.init(agent_name="...", agent_name="...") # Decorator @agent_control.control() diff --git a/README.md b/README.md index af6a0b85..d0dff1eb 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ async def setup(): # 1. Register agent first (required before assigning policy) agent = Agent( # Your agent's UUID - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="550e8400-e29b-41d4-a716-446655440000", agent_name="My Chatbot", agent_created_at=datetime.now(UTC).isoformat() ) @@ -165,7 +165,7 @@ async def setup(): # 5. Assign policy to agent await policies.assign_policy_to_agent( client, - agent_id=AGENT_ID, + agent_name=AGENT_ID, policy_id=policy["policy_id"] ) @@ -199,7 +199,7 @@ from agent_control import control, ControlViolationError # Initialize your agent agent_control.init( agent_name="My Chatbot", - agent_id="550e8400-e29b-41d4-a716-446655440000" + agent_name="550e8400-e29b-41d4-a716-446655440000" ) # Protect any function (like LLM calls) diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index e41cee46..2b155237 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -587,7 +587,7 @@ import agent_control agent_control.init( agent_name="my-agent", # Required: human-readable name - agent_id="550e8400-e29b-41d4-a716-446655440000", # Required: UUID + agent_name="550e8400-e29b-41d4-a716-446655440000", # Required: UUID server_url="http://localhost:8000", # Optional: defaults to env var steps=[ # Optional: register available steps { @@ -770,12 +770,12 @@ Default: `http://localhost:8000/api/v1` |--------|----------|-------------| | `GET` | `/agents` | List all agents | | `POST` | `/agents/initAgent` | Register a new agent | -| `GET` | `/agents/{agent_id}` | Get agent details | -| `PATCH` | `/agents/{agent_id}` | Update agent | -| `GET` | `/agents/{agent_id}/controls` | List controls for agent | -| `GET` | `/agents/{agent_id}/policy` | Get agent's policy | -| `POST` | `/agents/{agent_id}/policy/{policy_id}` | Assign policy | -| `DELETE` | `/agents/{agent_id}/policy` | Remove policy | +| `GET` | `/agents/{agent_name}` | Get agent details | +| `PATCH` | `/agents/{agent_name}` | Update agent | +| `GET` | `/agents/{agent_name}/controls` | List controls for agent | +| `GET` | `/agents/{agent_name}/policy` | Get agent's policy | +| `POST` | `/agents/{agent_name}/policy/{policy_id}` | Assign policy | +| `DELETE` | `/agents/{agent_name}/policy` | Remove policy | **Controls**: diff --git a/docs/observability.md b/docs/observability.md index 96be1753..856bc7a9 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -125,7 +125,7 @@ class EventStore(ABC): @abstractmethod async def query_stats( - self, agent_uuid: UUID, time_range: timedelta, control_id: int | None = None + self, agent_name: UUID, time_range: timedelta, control_id: int | None = None ) -> StatsResult: """Query stats (aggregated at query time).""" pass @@ -220,12 +220,12 @@ Events are stored with minimal indexed columns + JSONB for flexibility: CREATE TABLE control_execution_events ( control_execution_id VARCHAR(36) PRIMARY KEY, timestamp TIMESTAMPTZ NOT NULL, - agent_uuid UUID NOT NULL, + agent_name UUID NOT NULL, data JSONB NOT NULL -- Full event stored here ); -- Primary index for time-range queries per agent -CREATE INDEX ix_events_agent_time ON control_execution_events (agent_uuid, timestamp DESC); +CREATE INDEX ix_events_agent_time ON control_execution_events (agent_name, timestamp DESC); -- Expression index for grouping by control CREATE INDEX ix_events_data_control_id ON control_execution_events ((data->>'control_id')); @@ -245,7 +245,7 @@ Each control evaluation produces an event (stored in the `data` JSONB column): control_execution_id: string, // Unique ID (for correlation) trace_id: string, // OpenTelemetry trace ID (32 hex chars) span_id: string, // OpenTelemetry span ID (16 hex chars) - agent_uuid: UUID, + agent_name: UUID, agent_name: string, control_id: number, control_name: string, @@ -272,9 +272,9 @@ All observability endpoints are under `/api/v1/observability/`. |----------|----------|------------|---------| | **Health check** | `GET /status` | — | System status | | **Ingest events** | `POST /events` | `events[]` in body | Ingestion result | -| **Agent overview** | `GET /stats` | `agent_uuid`, `time_range` | `totals` + `controls[]` | +| **Agent overview** | `GET /stats` | `agent_name`, `time_range` | `totals` + `controls[]` | | **Agent trends** | `GET /stats` | + `include_timeseries=true` | `totals.timeseries[]` included | -| **Control stats** | `GET /stats/controls/{id}` | `agent_uuid`, `time_range` | `control_id`, `control_name`, `stats` | +| **Control stats** | `GET /stats/controls/{id}` | `agent_name`, `time_range` | `control_id`, `control_name`, `stats` | | **Control trends** | `GET /stats/controls/{id}` | + `include_timeseries=true` | `stats.timeseries[]` included | | **Query raw events** | `POST /events/query` | Filters in body | `events[]` with pagination | @@ -323,7 +323,7 @@ Content-Type: application/json "control_execution_id": "...", "trace_id": "...", "span_id": "...", - "agent_uuid": "...", + "agent_name": "...", "control_id": 1, "control_name": "block-toxic", "matched": true, @@ -350,14 +350,14 @@ Content-Type: application/json Get agent-level aggregated statistics with per-control breakdown. ```http -GET /api/v1/observability/stats?agent_uuid=&time_range=&include_timeseries= +GET /api/v1/observability/stats?agent_name=&time_range=&include_timeseries= ``` **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `agent_uuid` | UUID | Yes | Agent to get stats for | +| `agent_name` | UUID | Yes | Agent to get stats for | | `time_range` | string | No | Time range: `1m`, `5m`, `15m`, `1h`, `24h`, `7d`, `30d`, `180d`, `365d` (default: `5m`) | | `include_timeseries` | boolean | No | Include time-series data for trend visualization (default: `false`) | @@ -379,13 +379,13 @@ When `include_timeseries=true`, data is bucketed automatically based on the time **Example Request:** ```bash -curl "http://localhost:8000/api/v1/observability/stats?agent_uuid=563de065-23aa-5d75-b594-cfa73abcc53c&time_range=1h" +curl "http://localhost:8000/api/v1/observability/stats?agent_name=563de065-23aa-5d75-b594-cfa73abcc53c&time_range=1h" ``` **Example Response:** ```json { - "agent_uuid": "563de065-23aa-5d75-b594-cfa73abcc53c", + "agent_name": "563de065-23aa-5d75-b594-cfa73abcc53c", "time_range": "1h", "totals": { "execution_count": 8, @@ -442,13 +442,13 @@ curl "http://localhost:8000/api/v1/observability/stats?agent_uuid=563de065-23aa- **Example Request with Time-Series:** ```bash -curl "http://localhost:8000/api/v1/observability/stats?agent_uuid=563de065-23aa-5d75-b594-cfa73abcc53c&time_range=1h&include_timeseries=true" +curl "http://localhost:8000/api/v1/observability/stats?agent_name=563de065-23aa-5d75-b594-cfa73abcc53c&time_range=1h&include_timeseries=true" ``` **Example Response with Time-Series:** ```json { - "agent_uuid": "563de065-23aa-5d75-b594-cfa73abcc53c", + "agent_name": "563de065-23aa-5d75-b594-cfa73abcc53c", "time_range": "1h", "totals": { "execution_count": 8, @@ -512,7 +512,7 @@ Empty buckets are included with zero counts and `null` averages to ensure consis Get statistics for a single control. ```http -GET /api/v1/observability/stats/controls/{control_id}?agent_uuid=&time_range=&include_timeseries= +GET /api/v1/observability/stats/controls/{control_id}?agent_name=&time_range=&include_timeseries= ``` **Path Parameters:** @@ -525,19 +525,19 @@ GET /api/v1/observability/stats/controls/{control_id}?agent_uuid=&time_ran | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `agent_uuid` | UUID | Yes | Agent to get stats for | +| `agent_name` | UUID | Yes | Agent to get stats for | | `time_range` | string | No | Time range: `1m`, `5m`, `15m`, `1h`, `24h`, `7d`, `30d`, `180d`, `365d` (default: `5m`) | | `include_timeseries` | boolean | No | Include time-series data for trend visualization (default: `false`) | **Example Request:** ```bash -curl "http://localhost:8000/api/v1/observability/stats/controls/1?agent_uuid=563de065-23aa-5d75-b594-cfa73abcc53c&time_range=1h&include_timeseries=true" +curl "http://localhost:8000/api/v1/observability/stats/controls/1?agent_name=563de065-23aa-5d75-b594-cfa73abcc53c&time_range=1h&include_timeseries=true" ``` **Example Response:** ```json { - "agent_uuid": "563de065-23aa-5d75-b594-cfa73abcc53c", + "agent_name": "563de065-23aa-5d75-b594-cfa73abcc53c", "time_range": "1h", "control_id": 1, "control_name": "block-prompt-injection", @@ -595,7 +595,7 @@ Content-Type: application/json | `trace_id` | string | No | Filter by trace ID | | `span_id` | string | No | Filter by span ID | | `control_execution_id` | string | No | Get specific event | -| `agent_uuid` | UUID | No | Filter by agent | +| `agent_name` | UUID | No | Filter by agent | | `control_ids` | integer[] | No | Filter by control IDs | | `actions` | string[] | No | Filter by actions: `allow`, `deny`, `warn`, `log` | | `matched` | boolean | No | Filter by matched status | @@ -611,7 +611,7 @@ Content-Type: application/json curl -X POST "http://localhost:8000/api/v1/observability/events/query" \ -H "Content-Type: application/json" \ -d '{ - "agent_uuid": "563de065-23aa-5d75-b594-cfa73abcc53c", + "agent_name": "563de065-23aa-5d75-b594-cfa73abcc53c", "matched": true, "limit": 5 }' @@ -625,7 +625,7 @@ curl -X POST "http://localhost:8000/api/v1/observability/events/query" \ "control_execution_id": "92df0332-170c-4bc6-aefd-ab50be311062", "trace_id": "5848335875e1d7269e148170ccb617ca", "span_id": "c25549deddcaecbe", - "agent_uuid": "563de065-23aa-5d75-b594-cfa73abcc53c", + "agent_name": "563de065-23aa-5d75-b594-cfa73abcc53c", "agent_name": "Customer Support Agent", "control_id": 3, "control_name": "block-credit-card", diff --git a/docs/testing.md b/docs/testing.md index 16895487..5d6d3840 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -91,7 +91,7 @@ async def test_sdk_denies_on_local_control() -> None: # When: evaluating via the SDK public API result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="db_query", input={"sql": "SELECT 1"}, output=None), stage="pre", controls=controls, diff --git a/engine/tests/test_core.py b/engine/tests/test_core.py index dad8fcbe..46e25eee 100644 --- a/engine/tests/test_core.py +++ b/engine/tests/test_core.py @@ -236,7 +236,7 @@ async def test_parallel_evaluation_starts_all_controls(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -268,7 +268,7 @@ async def test_parallel_evaluation_faster_than_sequential(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -303,7 +303,7 @@ async def test_cancel_on_deny_cancels_blocking_tasks(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -338,7 +338,7 @@ async def test_cancel_on_deny_with_multiple_blockers(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -364,7 +364,7 @@ async def test_no_cancel_on_non_deny_match(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -390,7 +390,7 @@ async def test_first_deny_wins(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -424,7 +424,7 @@ async def test_collect_all_completed_results(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -447,7 +447,7 @@ async def test_no_matches_when_all_allow(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -535,7 +535,7 @@ async def test_evaluator_error_fails_closed_for_deny(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -571,7 +571,7 @@ async def test_error_does_not_affect_other_controls(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -609,7 +609,7 @@ async def test_error_with_log_action_fails_open(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -641,7 +641,7 @@ async def test_missing_evaluator_error_sets_error_field(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -678,7 +678,7 @@ async def test_errors_array_exposes_evaluator_failures(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -724,7 +724,7 @@ async def test_errors_array_empty_when_no_errors(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -765,7 +765,7 @@ async def test_confidence_is_full_on_deny_match(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -799,7 +799,7 @@ async def test_confidence_excludes_cancelled_tasks(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -831,7 +831,7 @@ async def test_confidence_proportional_without_deny_match(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -860,7 +860,7 @@ async def test_confidence_zero_when_deny_errors_despite_other_successes(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -949,7 +949,7 @@ async def test_step_names_filters_tasks(self): ] engine = ControlEngine(controls) request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="tool", name="copy_file", input={}, output=None), stage="pre", ) @@ -986,7 +986,7 @@ async def test_step_name_regex_filters_tasks(self): ] engine = ControlEngine(controls) request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="tool", name="db_query", input={}, output=None), stage="pre", ) @@ -1014,7 +1014,7 @@ async def test_or_semantics_names_or_regex(self): engine = ControlEngine(controls) # Matches by regex despite name mismatch request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="tool", name="db_export", input={}, output=None), stage="pre", ) @@ -1039,7 +1039,7 @@ async def test_path_optional_defaults_to_star(self): ] engine = ControlEngine(controls) request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="tool", name="copy_file", input={}, output=None), stage="pre", ) @@ -1104,7 +1104,7 @@ async def test_evaluator_timeout_is_enforced(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -1162,7 +1162,7 @@ async def test_timeout_does_not_affect_fast_evaluators(self): # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -1247,7 +1247,7 @@ async def evaluate(self, data: Any) -> EvaluatorResult: # When: Processing request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -1327,7 +1327,7 @@ async def test_server_context_only_runs_server_controls(self): engine = ControlEngine(controls, context="server") request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -1357,7 +1357,7 @@ async def test_sdk_context_only_runs_sdk_controls(self): engine = ControlEngine(controls, context="sdk") request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -1387,7 +1387,7 @@ async def test_default_context_is_server(self): engine = ControlEngine(controls) # No context param request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -1417,7 +1417,7 @@ async def test_sdk_context_empty_when_no_local_controls(self): engine = ControlEngine(controls, context="sdk") request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -1449,7 +1449,7 @@ async def test_server_context_empty_when_all_local_controls(self): engine = ControlEngine(controls, context="server") request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -1478,7 +1478,7 @@ async def test_sdk_deny_works_in_sdk_context(self): engine = ControlEngine(controls, context="sdk") request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="llm", name="test-step", input="test", output=None), stage="pre", ) @@ -1522,7 +1522,7 @@ async def test_context_filtering_combined_with_step_scoping(self): engine = ControlEngine(controls, context="sdk") request = EvaluationRequest( - agent_uuid="00000000-0000-0000-0000-000000000001", + agent_name="00000000-0000-0000-0000-000000000001", step=Step(type="tool", name="copy_file", input={}, output=None), stage="pre", ) diff --git a/examples/README.md b/examples/README.md index da4e369f..48f968c9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -99,7 +99,7 @@ from agent_control import control, ControlViolationError # Initialize agent (connects to server, loads policy) agent_control.init( agent_name="my-bot", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="550e8400-e29b-41d4-a716-446655440000", ) # Apply the agent's assigned policy diff --git a/examples/agent_control_demo/demo_agent.py b/examples/agent_control_demo/demo_agent.py index 8a1e7cfe..f0fd4e10 100644 --- a/examples/agent_control_demo/demo_agent.py +++ b/examples/agent_control_demo/demo_agent.py @@ -161,8 +161,7 @@ async def run_demo(): try: logger.info(f"Initializing agent: {AGENT_NAME}") agent_control.init( - agent_name=AGENT_NAME, - agent_id=AGENT_ID, + agent_name=AGENT_ID, server_url=SERVER_URL, agent_description="Demo chatbot for testing controls" ) diff --git a/examples/agent_control_demo/setup_controls.py b/examples/agent_control_demo/setup_controls.py index cf4b6c73..658d0dda 100644 --- a/examples/agent_control_demo/setup_controls.py +++ b/examples/agent_control_demo/setup_controls.py @@ -45,14 +45,14 @@ async def create_agent(client: AgentControlClient) -> str: print("=" * 60) # Use the provided UUID for the agent - agent_uuid = UUID(AGENT_ID) + agent_name = UUID(AGENT_ID) try: response = await client.http_client.post( "/api/v1/agents/initAgent", # Correct endpoint json={ "agent": { - "agent_id": str(agent_uuid), + "agent_name": str(agent_name), "agent_name": AGENT_NAME, "agent_description": "Demo chatbot for testing controls", }, @@ -69,8 +69,8 @@ async def create_agent(client: AgentControlClient) -> str: else: print(f"✓ Agent already exists: {AGENT_NAME}") - print(f" Agent UUID: {agent_uuid}") - return str(agent_uuid) + print(f" Agent UUID: {agent_name}") + return str(agent_name) except Exception as e: print(f"✗ Failed to create agent: {e}") @@ -234,7 +234,7 @@ async def add_control_to_policy( async def assign_policy_to_agent( client: AgentControlClient, - agent_uuid: str, + agent_name: str, policy_id: int ) -> bool: """Assign a policy to an agent.""" @@ -244,11 +244,11 @@ async def assign_policy_to_agent( try: response = await client.http_client.post( - f"/api/v1/agents/{agent_uuid}/policy/{policy_id}" + f"/api/v1/agents/{agent_name}/policy/{policy_id}" ) response.raise_for_status() data = response.json() - print(f"✓ Assigned policy {policy_id} to agent {agent_uuid}") + print(f"✓ Assigned policy {policy_id} to agent {agent_name}") print(f" Response: {data}") return True except Exception as e: @@ -256,7 +256,7 @@ async def assign_policy_to_agent( return False -async def list_agent_controls(client: AgentControlClient, agent_uuid: str) -> list: +async def list_agent_controls(client: AgentControlClient, agent_name: str) -> list: """List all controls for the agent.""" print("\n" + "=" * 60) print("STEP 6: Listing Agent's Controls") @@ -264,7 +264,7 @@ async def list_agent_controls(client: AgentControlClient, agent_uuid: str) -> li try: response = await client.http_client.get( - f"/api/v1/agents/{agent_uuid}/controls" + f"/api/v1/agents/{agent_name}/controls" ) response.raise_for_status() data = response.json() @@ -363,7 +363,7 @@ async def get_control_data(client: AgentControlClient, control_id: int) -> dict: raise -async def verify_full_chain(client: AgentControlClient, agent_uuid: str) -> None: +async def verify_full_chain(client: AgentControlClient, agent_name: str) -> None: """Debug function to verify the entire chain.""" print("\n" + "=" * 60) print("DEBUG: Verifying Full Chain") @@ -372,7 +372,7 @@ async def verify_full_chain(client: AgentControlClient, agent_uuid: str) -> None # 1. Get agent info print("\n1. Agent Info:") try: - resp = await client.http_client.get(f"/api/v1/agents/{agent_uuid}") + resp = await client.http_client.get(f"/api/v1/agents/{agent_name}") resp.raise_for_status() agent_data = resp.json() print(f" Agent: {agent_data}") @@ -382,7 +382,7 @@ async def verify_full_chain(client: AgentControlClient, agent_uuid: str) -> None # 2. Get agent's policy print("\n2. Agent's Policy:") try: - resp = await client.http_client.get(f"/api/v1/agents/{agent_uuid}/policy") + resp = await client.http_client.get(f"/api/v1/agents/{agent_name}/policy") if resp.status_code == 404: print(" No policy assigned to agent") policy_id = None @@ -407,7 +407,7 @@ async def verify_full_chain(client: AgentControlClient, agent_uuid: str) -> None # 4. Final: List agent controls (the API we're testing) print("\n4. Final Agent Controls (via /agents/{id}/controls):") try: - resp = await client.http_client.get(f"/api/v1/agents/{agent_uuid}/controls") + resp = await client.http_client.get(f"/api/v1/agents/{agent_name}/controls") resp.raise_for_status() controls = resp.json() print(f" Controls: {controls}") @@ -440,15 +440,15 @@ async def main(): return # Use the provided UUID for verification - agent_uuid = AGENT_ID + agent_name = AGENT_ID # If verify-only mode, just run verification if args.verify_only: - await verify_full_chain(client, agent_uuid) + await verify_full_chain(client, agent_name) return # 1. Create agent - agent_uuid = await create_agent(client) + agent_name = await create_agent(client) # 2. Create controls regex_control_id = await create_regex_control(client) @@ -457,14 +457,14 @@ async def main(): # Skip remaining steps if controls already existed if regex_control_id == -1 or list_control_id == -1: print("\n⚠️ Some controls already exist. Running verification...") - await verify_full_chain(client, agent_uuid) + await verify_full_chain(client, agent_name) return # 3. Create policy policy_id = await create_policy(client, "demo-policy") if policy_id == -1: print("\n⚠️ Policy already exists. Running verification...") - await verify_full_chain(client, agent_uuid) + await verify_full_chain(client, agent_name) return # 4. Add controls to policy @@ -487,7 +487,7 @@ async def main(): print(f" Failed to verify policy: {e}") # 5. Assign policy to agent - ok3 = await assign_policy_to_agent(client, agent_uuid, policy_id) + ok3 = await assign_policy_to_agent(client, agent_name, policy_id) if not ok3: print("\n⚠️ Failed to assign policy to agent!") @@ -495,7 +495,7 @@ async def main(): print("\n Verifying agent policy assignment...") try: resp = await client.http_client.get( - f"/api/v1/agents/{agent_uuid}/policy" + f"/api/v1/agents/{agent_name}/policy" ) resp.raise_for_status() agent_policy = resp.json() @@ -508,7 +508,7 @@ async def main(): print(f" ✗ Failed to verify agent policy: {e}") # 6. List controls - await list_agent_controls(client, agent_uuid) + await list_agent_controls(client, agent_name) # 7. Update the list control await update_control(client, list_control_id) diff --git a/examples/agent_control_demo/update_controls.py b/examples/agent_control_demo/update_controls.py index 6c7ab3f9..ebd1e25a 100644 --- a/examples/agent_control_demo/update_controls.py +++ b/examples/agent_control_demo/update_controls.py @@ -29,10 +29,10 @@ SERVER_URL = os.getenv("AGENT_CONTROL_URL", "http://localhost:8000") -async def get_control_by_name(client: AgentControlClient, agent_uuid: str, name: str) -> dict | None: +async def get_control_by_name(client: AgentControlClient, agent_name: str, name: str) -> dict | None: """Find a control by name from the agent's controls.""" try: - response = await client.http_client.get(f"/api/v1/agents/{agent_uuid}/controls") + response = await client.http_client.get(f"/api/v1/agents/{agent_name}/controls") response.raise_for_status() controls = response.json().get("controls", []) @@ -126,13 +126,13 @@ async def block_ssn(client: AgentControlClient, control_id: int) -> None: raise -async def show_current_status(client: AgentControlClient, agent_uuid: str) -> None: +async def show_current_status(client: AgentControlClient, agent_name: str) -> None: """Show the current status of the SSN control.""" print("\n" + "=" * 60) print("Current SSN Control Status") print("=" * 60) - ctrl = await get_control_by_name(client, agent_uuid, "block-ssn-output") + ctrl = await get_control_by_name(client, agent_name, "block-ssn-output") if ctrl: ctrl_def = ctrl.get("control", {}) enabled = ctrl_def.get("enabled", True) @@ -155,13 +155,13 @@ async def main(): args = parser.parse_args() # Use the provided UUID - agent_uuid = AGENT_ID + agent_name = AGENT_ID print("\n" + "=" * 60) print("AGENT CONTROL DEMO: Update Controls") print("=" * 60) print(f"\nServer URL: {SERVER_URL}") - print(f"Agent UUID: {agent_uuid}") + print(f"Agent UUID: {agent_name}") async with AgentControlClient(base_url=SERVER_URL) as client: # Check server health @@ -174,7 +174,7 @@ async def main(): return # Find the SSN control - ctrl = await get_control_by_name(client, agent_uuid, "block-ssn-output") + ctrl = await get_control_by_name(client, agent_name, "block-ssn-output") if not ctrl: print("\n✗ SSN control not found!") print(" Run setup_controls.py first:") @@ -184,16 +184,16 @@ async def main(): control_id = ctrl.get("id") if args.status: - await show_current_status(client, agent_uuid) + await show_current_status(client, agent_name) elif args.allow_ssn: await allow_ssn(client, control_id) - await show_current_status(client, agent_uuid) + await show_current_status(client, agent_name) elif args.block_ssn: await block_ssn(client, control_id) - await show_current_status(client, agent_uuid) + await show_current_status(client, agent_name) else: # Default: show status and usage - await show_current_status(client, agent_uuid) + await show_current_status(client, agent_name) print("\n" + "=" * 60) print("Usage") print("=" * 60) diff --git a/examples/crewai/content_agent_protection.py b/examples/crewai/content_agent_protection.py index 118be45a..2827d4e3 100644 --- a/examples/crewai/content_agent_protection.py +++ b/examples/crewai/content_agent_protection.py @@ -53,8 +53,7 @@ server_url = os.getenv("AGENT_CONTROL_URL", "http://localhost:8000") agent_control.init( - agent_name=AGENT_NAME, - agent_id=AGENT_ID, + agent_name=AGENT_ID, agent_description=AGENT_DESCRIPTION, server_url=server_url, ) diff --git a/examples/crewai/setup_content_controls.py b/examples/crewai/setup_content_controls.py index 94674e4d..779ae814 100644 --- a/examples/crewai/setup_content_controls.py +++ b/examples/crewai/setup_content_controls.py @@ -10,7 +10,6 @@ import asyncio import os -from uuid import UUID from agent_control import Agent, AgentControlClient, agents, controls, policies @@ -22,11 +21,10 @@ async def setup_content_controls(): """Create PII protection and unauthorized access controls, policy, and assign to agent.""" async with AgentControlClient(base_url=SERVER_URL) as client: # 1. Register Agent - agent_uuid = UUID(AGENT_ID) + agent_name = AGENT_ID agent = Agent( - agent_id=agent_uuid, - agent_name="Customer Support Crew", + agent_name=agent_name, agent_description="Customer support crew with PII protection and access controls" ) @@ -226,7 +224,7 @@ async def setup_content_controls(): # 8. Assign Policy to Agent try: - await policies.assign_policy_to_agent(client, agent_uuid, policy_id) + await policies.assign_policy_to_agent(client, agent_name, policy_id) print(f"✓ Assigned policy to agent") except Exception as e: if "409" in str(e) or "already" in str(e).lower(): diff --git a/examples/customer_support_agent/README.md b/examples/customer_support_agent/README.md index 8eeedd49..30571390 100644 --- a/examples/customer_support_agent/README.md +++ b/examples/customer_support_agent/README.md @@ -161,7 +161,7 @@ import agent_control agent_control.init( agent_name="Customer Support Agent", - agent_id="646d5dea-c2e6-4453-b446-7035482b38e4", + agent_name="646d5dea-c2e6-4453-b446-7035482b38e4", agent_description="AI-powered customer support assistant", ) ``` diff --git a/examples/customer_support_agent/demo.sh b/examples/customer_support_agent/demo.sh index 859c338a..f71c0630 100755 --- a/examples/customer_support_agent/demo.sh +++ b/examples/customer_support_agent/demo.sh @@ -228,7 +228,7 @@ get_agent_uuid() { } show_observability_stats() { - local agent_uuid=$(get_agent_uuid) + local agent_name=$(get_agent_uuid) local server_url="${AGENT_CONTROL_URL:-http://localhost:8000}" echo "" @@ -246,7 +246,7 @@ show_observability_stats() { # Fetch stats for 5 minutes echo "Last 5 minutes:" echo "---------------" - local stats_5m=$(curl -s "${server_url}/api/v1/observability/stats?agent_uuid=${agent_uuid}&time_range=5m") + local stats_5m=$(curl -s "${server_url}/api/v1/observability/stats?agent_name=${agent_name}&time_range=5m") if echo "$stats_5m" | python3 -c "import sys, json; d=json.load(sys.stdin); print(f\" Total executions: {d.get('total_executions', 0)}\"); print(f\" Matches: {d.get('total_matches', 0)}\"); print(f\" Non-matches: {d.get('total_non_matches', 0)}\"); print(f\" Errors: {d.get('total_errors', 0)}\"); actions=d.get('action_counts', {}); print(f\" Actions: allow={actions.get('allow', 0)}, deny={actions.get('deny', 0)}, warn={actions.get('warn', 0)}, log={actions.get('log', 0)}\")" 2>/dev/null; then : else @@ -258,7 +258,7 @@ show_observability_stats() { # Fetch stats for 1 hour (closest to 30 mins available) echo "Last 1 hour:" echo "------------" - local stats_1h=$(curl -s "${server_url}/api/v1/observability/stats?agent_uuid=${agent_uuid}&time_range=1h") + local stats_1h=$(curl -s "${server_url}/api/v1/observability/stats?agent_name=${agent_name}&time_range=1h") if echo "$stats_1h" | python3 -c "import sys, json; d=json.load(sys.stdin); print(f\" Total executions: {d.get('total_executions', 0)}\"); print(f\" Matches: {d.get('total_matches', 0)}\"); print(f\" Non-matches: {d.get('total_non_matches', 0)}\"); print(f\" Errors: {d.get('total_errors', 0)}\"); actions=d.get('action_counts', {}); print(f\" Actions: allow={actions.get('allow', 0)}, deny={actions.get('deny', 0)}, warn={actions.get('warn', 0)}, log={actions.get('log', 0)}\")" 2>/dev/null; then : else diff --git a/examples/customer_support_agent/run_demo.py b/examples/customer_support_agent/run_demo.py index 84eac08a..34ad71ef 100644 --- a/examples/customer_support_agent/run_demo.py +++ b/examples/customer_support_agent/run_demo.py @@ -60,17 +60,17 @@ async def reset_agent(): """Reset the agent by removing its policy (which disconnects all controls).""" - agent_uuid = AGENT_ID + agent_name = AGENT_ID server_url = os.getenv("AGENT_CONTROL_URL", "http://localhost:8000") - logger.info(f"Resetting agent '{AGENT_ID}' (UUID: {agent_uuid})") - print(f"Resetting agent '{AGENT_ID}' (UUID: {agent_uuid})...") + logger.info(f"Resetting agent '{AGENT_ID}' (UUID: {agent_name})") + print(f"Resetting agent '{AGENT_ID}' (UUID: {agent_name})...") print() async with AgentControlClient(base_url=server_url) as client: # Check if agent exists try: - await agents.get_agent(client, agent_uuid) + await agents.get_agent(client, agent_name) logger.debug("Agent exists, proceeding with reset") except Exception as e: if "404" in str(e): @@ -83,7 +83,7 @@ async def reset_agent(): # Remove policy from agent (disconnects all controls) try: - await agents.remove_agent_policy(client, agent_uuid) + await agents.remove_agent_policy(client, agent_name) logger.info("Successfully removed policy from agent") print("Removed policy from agent (all controls disconnected).") except Exception as e: diff --git a/examples/customer_support_agent/setup_demo_controls.py b/examples/customer_support_agent/setup_demo_controls.py index 904f38ce..bce590fe 100644 --- a/examples/customer_support_agent/setup_demo_controls.py +++ b/examples/customer_support_agent/setup_demo_controls.py @@ -226,8 +226,8 @@ async def setup_demo(quiet: bool = False): """Set up the demo agent with controls.""" - # Use the provided UUID (must match support_agent.py) - agent_uuid = AGENT_ID + # Use the provided agent identifier (must match support_agent.py) + agent_name = AGENT_ID async with AgentControlClient(base_url=SERVER_URL, timeout=30.0) as client: # Check server health @@ -242,8 +242,7 @@ async def setup_demo(quiet: bool = False): # Register the agent try: agent = Agent( - agent_id=agent_uuid, - agent_name=AGENT_NAME, + agent_name=agent_name, agent_description=AGENT_DESCRIPTION, ) result = await agents.register_agent(client, agent, steps=[]) @@ -259,7 +258,7 @@ async def setup_demo(quiet: bool = False): # Check if agent already has a policy try: - policy_info = await agents.get_agent_policy(client, agent_uuid) + policy_info = await agents.get_agent_policy(client, agent_name) policy_id = policy_info.get("policy_id") except Exception: policy_id = None # No policy yet @@ -282,7 +281,7 @@ async def setup_demo(quiet: bool = False): return False try: - await policies.assign_policy_to_agent(client, agent_uuid, policy_id) + await policies.assign_policy_to_agent(client, agent_name, policy_id) except Exception as e: print(f" Error assigning policy: {e}") return False diff --git a/examples/customer_support_agent/support_agent.py b/examples/customer_support_agent/support_agent.py index 0cd7a876..82273005 100644 --- a/examples/customer_support_agent/support_agent.py +++ b/examples/customer_support_agent/support_agent.py @@ -29,8 +29,7 @@ # The agent registers with the server and loads its assigned policy. agent_control.init( - agent_name="Customer Support Agent", - agent_id="646d5dea-c2e6-4453-b446-7035482b38e4", + agent_name="646d5dea-c2e6-4453-b446-7035482b38e4", agent_description="AI-powered customer support assistant that helps with inquiries, " "searches knowledge bases, and creates support tickets.", agent_version="1.0.0", diff --git a/examples/deepeval/qa_agent.py b/examples/deepeval/qa_agent.py index 63a205f7..b4552326 100755 --- a/examples/deepeval/qa_agent.py +++ b/examples/deepeval/qa_agent.py @@ -37,8 +37,7 @@ # ============================================================================= agent_control.init( - agent_name="Q&A Agent with DeepEval", - agent_id="qa-agent-deepeval", + agent_name="qa-agent-deepeval", agent_description="Question answering agent with DeepEval quality controls", agent_version="1.0.0", ) diff --git a/examples/deepeval/setup_controls.py b/examples/deepeval/setup_controls.py index ec1f37da..f3178cb5 100755 --- a/examples/deepeval/setup_controls.py +++ b/examples/deepeval/setup_controls.py @@ -149,11 +149,11 @@ async def setup_demo(quiet: bool = False): """Set up the demo agent with DeepEval controls.""" # Generate the same UUID5 that the SDK generates - agent_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, AGENT_ID)) + agent_name = str(uuid.uuid5(uuid.NAMESPACE_DNS, AGENT_ID)) print(f"Setting up agent: {AGENT_NAME}") print(f"Agent ID: {AGENT_ID}") - print(f"Agent UUID: {agent_uuid}") + print(f"Agent UUID: {agent_name}") print(f"Server URL: {SERVER_URL}") print() @@ -175,7 +175,7 @@ async def setup_demo(quiet: bool = False): "/api/v1/agents/initAgent", json={ "agent": { - "agent_id": agent_uuid, + "agent_name": agent_name, "agent_name": AGENT_NAME, "agent_description": AGENT_DESCRIPTION, }, @@ -196,7 +196,7 @@ async def setup_demo(quiet: bool = False): # Check if agent already has a policy try: - resp = await client.get(f"/api/v1/agents/{agent_uuid}/policy") + resp = await client.get(f"/api/v1/agents/{agent_name}/policy") if resp.status_code == 200: policy_id = resp.json().get("policy_id") print(f"✓ Found existing policy: {policy_id}") @@ -224,7 +224,7 @@ async def setup_demo(quiet: bool = False): print(f"✓ Created policy: {policy_name}") # Assign policy to agent - resp = await client.post(f"/api/v1/agents/{agent_uuid}/policy/{policy_id}") + resp = await client.post(f"/api/v1/agents/{agent_name}/policy/{policy_id}") resp.raise_for_status() print(f"✓ Assigned policy to agent") except httpx.HTTPError as e: diff --git a/examples/langchain/langgraph_auto_schema_agent.py b/examples/langchain/langgraph_auto_schema_agent.py index 7a6e1c3e..b077d0df 100644 --- a/examples/langchain/langgraph_auto_schema_agent.py +++ b/examples/langchain/langgraph_auto_schema_agent.py @@ -241,8 +241,7 @@ async def main() -> None: """Run the demo end-to-end.""" print("Initializing Agent Control (no explicit steps passed)...") agent_control.init( - agent_name=AGENT_NAME, - agent_id=AGENT_ID, + agent_name=AGENT_ID, agent_description=AGENT_DESCRIPTION, server_url=os.getenv("AGENT_CONTROL_URL"), ) diff --git a/examples/langchain/setup_sql_controls.py b/examples/langchain/setup_sql_controls.py index 59bafda2..28512907 100644 --- a/examples/langchain/setup_sql_controls.py +++ b/examples/langchain/setup_sql_controls.py @@ -13,7 +13,6 @@ import asyncio import os import pathlib -from uuid import UUID import requests @@ -52,11 +51,10 @@ async def setup_sql_controls(): """Create SQL control, policy, and assign to agent.""" async with AgentControlClient(base_url=SERVER_URL) as client: # 1. Register Agent - agent_uuid = UUID(AGENT_ID) + agent_name = AGENT_ID agent = Agent( - agent_id=agent_uuid, - agent_name="SQL Demo Agent", + agent_name=agent_name, agent_description="SQL agent with server-side controls" ) @@ -141,7 +139,7 @@ async def setup_sql_controls(): if "409" in str(e): print("ℹ️ Policy 'sql-protection-policy' already exists, checking agent...") try: - policy_info = await agents.get_agent_policy(client, str(agent_uuid)) + policy_info = await agents.get_agent_policy(client, str(agent_name)) policy_id = policy_info.get("policy_id") if policy_id is None: raise ValueError("No policy assigned to agent.") @@ -172,7 +170,7 @@ async def setup_sql_controls(): # 5. Assign Policy to Agent try: - await policies.assign_policy_to_agent(client, agent_uuid, policy_id) + await policies.assign_policy_to_agent(client, agent_name, policy_id) print(f"✓ Assigned policy to agent") except Exception as e: if "409" in str(e) or "already" in str(e).lower(): diff --git a/examples/langchain/sql_agent_protection.py b/examples/langchain/sql_agent_protection.py index 3e6dfb08..b0fd84f6 100644 --- a/examples/langchain/sql_agent_protection.py +++ b/examples/langchain/sql_agent_protection.py @@ -131,7 +131,7 @@ async def safe_query_tool(query: str): async with AgentControlClient() as client: result = await check_evaluation_with_local( client=client, - agent_uuid=agent.agent_id, + agent_name=agent.agent_name, step=step, stage="pre", controls=local_controls, @@ -210,8 +210,7 @@ async def main(): print("Initializing SQL Agent...") agent_control.init( - agent_name=AGENT_NAME, - agent_id=AGENT_ID, + agent_name=AGENT_ID, agent_description=AGENT_DESCRIPTION, server_url=os.getenv("AGENT_CONTROL_URL"), ) diff --git a/models/README.md b/models/README.md index cfb52396..fabc69e8 100644 --- a/models/README.md +++ b/models/README.md @@ -50,7 +50,7 @@ from agent_control_models import Agent, Step # Create an agent agent = Agent( agent_name="Customer Support Bot", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="550e8400-e29b-41d4-a716-446655440000", agent_description="Handles customer inquiries", agent_version="1.0.0" ) @@ -90,7 +90,7 @@ from agent_control_models import EvaluationRequest, EvaluationResponse # Create evaluation request request = EvaluationRequest( - agent_uuid="agent-uuid-here", + agent_name="agent-uuid-here", step=Step( type="llm_inference", name="chat", @@ -129,7 +129,7 @@ Agent metadata and configuration. **Fields:** - `agent_name` (str): Human-readable agent name -- `agent_id` (UUID): Unique identifier +- `agent_name` (UUID): Unique identifier - `agent_description` (Optional[str]): Agent description - `agent_version` (Optional[str]): Agent version - `tools` (Optional[List[str]]): List of available tools @@ -165,7 +165,7 @@ Complete control specification. Request for evaluating controls. **Fields:** -- `agent_uuid` (str): Agent identifier +- `agent_name` (str): Agent identifier - `step` (Step): Step to evaluate - `stage` (str): Evaluation stage ("pre" or "post") @@ -198,7 +198,7 @@ from agent_control_models import Agent # Create with validation agent = Agent( agent_name="My Agent", - agent_id="550e8400-e29b-41d4-a716-446655440000" + agent_name="550e8400-e29b-41d4-a716-446655440000" ) # Serialize to dict @@ -227,7 +227,7 @@ step = Step( # Type-safe evaluation request request = EvaluationRequest( - agent_uuid="uuid-here", + agent_name="uuid-here", step=step, stage="pre" ) @@ -243,7 +243,7 @@ from agent_control_models import Agent # Add custom metadata agent = Agent( agent_name="Support Bot", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="550e8400-e29b-41d4-a716-446655440000", metadata={ "team": "customer-success", "environment": "production", diff --git a/models/src/agent_control_models/agent.py b/models/src/agent_control_models/agent.py index 785039b4..6a0eedba 100644 --- a/models/src/agent_control_models/agent.py +++ b/models/src/agent_control_models/agent.py @@ -1,8 +1,8 @@ """Agent entity and step models.""" from __future__ import annotations +import re from typing import Any -from uuid import UUID from pydantic import Field, field_validator, model_validator @@ -15,19 +15,37 @@ STEP_TYPE_LLM = "llm" BUILTIN_STEP_TYPES: tuple[str, str] = (STEP_TYPE_TOOL, STEP_TYPE_LLM) +AGENT_NAME_MIN_LENGTH = 10 +AGENT_NAME_PATTERN = r"^[a-z0-9:_-]+$" +_AGENT_NAME_REGEX = re.compile(AGENT_NAME_PATTERN) + + +def normalize_agent_name(value: str) -> str: + """Normalize and validate an agent identifier.""" + normalized = value.strip().lower() + if len(normalized) < AGENT_NAME_MIN_LENGTH: + raise ValueError( + f"agent_name must be at least {AGENT_NAME_MIN_LENGTH} characters long" + ) + if not _AGENT_NAME_REGEX.fullmatch(normalized): + raise ValueError( + "agent_name may only contain lowercase letters, digits, ':', '_' or '-'" + ) + return normalized + class Agent(BaseModel): """ Agent metadata for registration and tracking. An agent represents an AI system that can be protected and monitored. - Each agent has a unique ID and can have multiple steps registered with it. + Each agent has a unique immutable name and can have multiple steps registered with it. """ - agent_id: UUID = Field( - ..., description="Unique identifier for the agent (UUID format)" - ) agent_name: str = Field( - ..., description="Human-readable name for the agent", min_length=1 + ..., + min_length=AGENT_NAME_MIN_LENGTH, + pattern=AGENT_NAME_PATTERN, + description="Unique immutable identifier for the agent", ) agent_description: str | None = Field( None, description="Optional description of the agent's purpose" @@ -49,7 +67,6 @@ class Agent(BaseModel): "json_schema_extra": { "examples": [ { - "agent_id": "550e8400-e29b-41d4-a716-446655440000", "agent_name": "customer-service-bot", "agent_description": "Handles customer inquiries and support tickets", "agent_version": "1.0.0", @@ -59,6 +76,11 @@ class Agent(BaseModel): } } + @field_validator("agent_name", mode="before") + @classmethod + def validate_and_normalize_agent_name(cls, value: str) -> str: + return normalize_agent_name(str(value)) + class StepSchema(BaseModel): """Schema for a registered agent step.""" diff --git a/models/src/agent_control_models/errors.py b/models/src/agent_control_models/errors.py index 2022a271..6ee41c42 100644 --- a/models/src/agent_control_models/errors.py +++ b/models/src/agent_control_models/errors.py @@ -12,8 +12,8 @@ "type": "https://agent-control.dev/errors/not-found", "title": "Resource Not Found", "status": 404, - "detail": "Agent with ID '550e8400-e29b-41d4-a716-446655440000' not found", - "instance": "/api/v1/agents/550e8400-e29b-41d4-a716-446655440000", + "detail": "Agent with name 'customer-service-bot' not found", + "instance": "/api/v1/agents/customer-service-bot", "error_code": "AGENT_NOT_FOUND", "kind": "Status", "api_version": "v1", @@ -25,9 +25,9 @@ "errors": [ { "resource": "Agent", - "field": "agent_id", + "field": "agent_name", "code": "not_found", - "message": "Agent with ID '550e8400-e29b-41d4-a716-446655440000' does not exist" + "message": "Agent with name 'customer-service-bot' does not exist" } ] } @@ -65,7 +65,6 @@ class ErrorCode(StrEnum): # Conflict Errors (3xx pattern) AGENT_NAME_CONFLICT = "AGENT_NAME_CONFLICT" - AGENT_UUID_CONFLICT = "AGENT_UUID_CONFLICT" POLICY_NAME_CONFLICT = "POLICY_NAME_CONFLICT" CONTROL_NAME_CONFLICT = "CONTROL_NAME_CONFLICT" EVALUATOR_NAME_CONFLICT = "EVALUATOR_NAME_CONFLICT" @@ -281,8 +280,8 @@ class ProblemDetail(BaseModel): "type": "https://agent-control.dev/errors/not-found", "title": "Resource Not Found", "status": 404, - "detail": "Agent with ID '550e8400-e29b-41d4-a716-446655440000' not found", - "instance": "/api/v1/agents/550e8400-e29b-41d4-a716-446655440000", + "detail": "Agent with name 'customer-service-bot' not found", + "instance": "/api/v1/agents/customer-service-bot", "error_code": "AGENT_NOT_FOUND", "kind": "Status", "api_version": "v1", @@ -356,7 +355,6 @@ def make_error_type(error_code: ErrorCode) -> str: ErrorCode.EVALUATOR_CONFIG_NOT_FOUND: "Evaluator Config Not Found", # Conflict errors ErrorCode.AGENT_NAME_CONFLICT: "Agent Name Already Exists", - ErrorCode.AGENT_UUID_CONFLICT: "Agent UUID Conflict", ErrorCode.POLICY_NAME_CONFLICT: "Policy Name Already Exists", ErrorCode.CONTROL_NAME_CONFLICT: "Control Name Already Exists", ErrorCode.EVALUATOR_NAME_CONFLICT: "Evaluator Name Conflict", diff --git a/models/src/agent_control_models/evaluation.py b/models/src/agent_control_models/evaluation.py index d22b44a9..07ab4810 100644 --- a/models/src/agent_control_models/evaluation.py +++ b/models/src/agent_control_models/evaluation.py @@ -1,10 +1,9 @@ """Evaluation-related models.""" from typing import Literal -from uuid import UUID -from pydantic import Field +from pydantic import Field, field_validator -from .agent import Step +from .agent import AGENT_NAME_MIN_LENGTH, AGENT_NAME_PATTERN, Step, normalize_agent_name from .base import BaseModel from .controls import ControlMatch @@ -17,12 +16,15 @@ class EvaluationRequest(BaseModel): policy compliance, and control rules. Attributes: - agent_uuid: UUID of the agent making the request + agent_name: Unique identifier of the agent making the request step: Step payload for evaluation stage: 'pre' (before execution) or 'post' (after execution) """ - agent_uuid: UUID = Field( - ..., description="UUID of the agent making the evaluation request" + agent_name: str = Field( + ..., + min_length=AGENT_NAME_MIN_LENGTH, + pattern=AGENT_NAME_PATTERN, + description="Identifier of the agent making the evaluation request", ) step: Step = Field( ..., description="Agent step payload to evaluate" @@ -35,7 +37,7 @@ class EvaluationRequest(BaseModel): "json_schema_extra": { "examples": [ { - "agent_uuid": "550e8400-e29b-41d4-a716-446655440000", + "agent_name": "customer-service-bot", "step": { "type": "llm", "name": "support-answer", @@ -45,7 +47,7 @@ class EvaluationRequest(BaseModel): "stage": "pre" }, { - "agent_uuid": "550e8400-e29b-41d4-a716-446655440000", + "agent_name": "customer-service-bot", "step": { "type": "llm", "name": "support-answer", @@ -56,7 +58,7 @@ class EvaluationRequest(BaseModel): "stage": "post" }, { - "agent_uuid": "550e8400-e29b-41d4-a716-446655440000", + "agent_name": "customer-service-bot", "step": { "type": "tool", "name": "search_database", @@ -66,7 +68,7 @@ class EvaluationRequest(BaseModel): "stage": "pre" }, { - "agent_uuid": "550e8400-e29b-41d4-a716-446655440000", + "agent_name": "customer-service-bot", "step": { "type": "tool", "name": "search_database", @@ -80,6 +82,11 @@ class EvaluationRequest(BaseModel): } } + @field_validator("agent_name", mode="before") + @classmethod + def validate_and_normalize_agent_name(cls, value: str) -> str: + return normalize_agent_name(str(value)) + class EvaluationResponse(BaseModel): """ diff --git a/models/src/agent_control_models/observability.py b/models/src/agent_control_models/observability.py index faa66087..ae01422c 100644 --- a/models/src/agent_control_models/observability.py +++ b/models/src/agent_control_models/observability.py @@ -10,10 +10,11 @@ from datetime import UTC, datetime from typing import Any, Literal -from uuid import UUID, uuid4 +from uuid import uuid4 from pydantic import Field, field_validator +from .agent import AGENT_NAME_MIN_LENGTH, AGENT_NAME_PATTERN, normalize_agent_name from .base import BaseModel # ============================================================================= @@ -36,8 +37,7 @@ class ControlExecutionEvent(BaseModel): control_execution_id: Unique ID for this specific control execution trace_id: OpenTelemetry-compatible trace ID (128-bit hex, 32 chars) span_id: OpenTelemetry-compatible span ID (64-bit hex, 16 chars) - agent_uuid: UUID of the agent that executed the control - agent_name: Name of the agent (denormalized for queries) + agent_name: Identifier of the agent that executed the control control_id: Database ID of the control control_name: Name of the control (denormalized for queries) check_stage: "pre" (before execution) or "post" (after execution) @@ -70,8 +70,12 @@ class ControlExecutionEvent(BaseModel): ) # Agent identity - agent_uuid: UUID = Field(..., description="UUID of the agent") - agent_name: str = Field(..., description="Name of the agent (denormalized)") + agent_name: str = Field( + ..., + min_length=AGENT_NAME_MIN_LENGTH, + pattern=AGENT_NAME_PATTERN, + description="Identifier of the agent", + ) # Control info control_id: int = Field(..., description="Database ID of the control") @@ -151,6 +155,11 @@ def validate_span_id(cls, v: str) -> str: raise ValueError("span_id cannot be empty") return v + @field_validator("agent_name", mode="before") + @classmethod + def validate_and_normalize_agent_name(cls, value: str) -> str: + return normalize_agent_name(str(value)) + model_config = { "json_schema_extra": { "examples": [ @@ -158,7 +167,6 @@ def validate_span_id(cls, v: str) -> str: "control_execution_id": "550e8400-e29b-41d4-a716-446655440000", "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7", - "agent_uuid": "550e8400-e29b-41d4-a716-446655440001", "agent_name": "my-agent", "control_id": 123, "control_name": "sql-injection-check", @@ -205,7 +213,6 @@ class BatchEventsRequest(BaseModel): { "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7", - "agent_uuid": "550e8400-e29b-41d4-a716-446655440001", "agent_name": "my-agent", "control_id": 123, "control_name": "sql-injection-check", @@ -256,7 +263,7 @@ class EventQueryRequest(BaseModel): trace_id: Filter by trace ID (get all events for a request) span_id: Filter by span ID (get all events for a function call) control_execution_id: Filter by specific event ID - agent_uuid: Filter by agent UUID + agent_name: Filter by agent identifier control_ids: Filter by control IDs actions: Filter by actions (allow, deny, warn, log) matched: Filter by matched status @@ -277,7 +284,12 @@ class EventQueryRequest(BaseModel): control_execution_id: str | None = Field( default=None, description="Filter by specific event ID" ) - agent_uuid: UUID | None = Field(default=None, description="Filter by agent UUID") + agent_name: str | None = Field( + default=None, + min_length=AGENT_NAME_MIN_LENGTH, + pattern=AGENT_NAME_PATTERN, + description="Filter by agent identifier", + ) control_ids: list[int] | None = Field( default=None, description="Filter by control IDs" ) @@ -305,7 +317,7 @@ class EventQueryRequest(BaseModel): "examples": [ {"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"}, { - "agent_uuid": "550e8400-e29b-41d4-a716-446655440001", + "agent_name": "my-agent", "actions": ["deny", "warn"], "start_time": "2025-01-09T00:00:00Z", "limit": 50, @@ -314,6 +326,15 @@ class EventQueryRequest(BaseModel): } } + @field_validator("agent_name", mode="before") + @classmethod + def validate_and_normalize_agent_name( + cls, value: str | None + ) -> str | None: + if value is None: + return None + return normalize_agent_name(str(value)) + class EventQueryResponse(BaseModel): """ @@ -377,12 +398,17 @@ class StatsRequest(BaseModel): Request model for aggregated statistics. Attributes: - agent_uuid: Agent to get stats for + agent_name: Agent to get stats for time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) include_timeseries: Whether to include time-series data points """ - agent_uuid: UUID = Field(..., description="Agent UUID") + agent_name: str = Field( + ..., + min_length=AGENT_NAME_MIN_LENGTH, + pattern=AGENT_NAME_PATTERN, + description="Agent identifier", + ) time_range: Literal["1m", "5m", "15m", "1h", "24h", "7d", "30d", "180d", "365d"] = Field( default="5m", description="Time range" ) @@ -390,6 +416,11 @@ class StatsRequest(BaseModel): default=False, description="Include time-series data points for trend visualization" ) + @field_validator("agent_name", mode="before") + @classmethod + def validate_and_normalize_agent_name(cls, value: str) -> str: + return normalize_agent_name(str(value)) + class TimeseriesBucket(BaseModel): """ @@ -476,19 +507,29 @@ class StatsResponse(BaseModel): Contains agent-level totals (with optional timeseries) and per-control breakdown. Attributes: - agent_uuid: Agent UUID + agent_name: Agent identifier time_range: Time range used totals: Agent-level aggregate statistics (includes timeseries) controls: Per-control breakdown for discovery and detail """ - agent_uuid: UUID = Field(..., description="Agent UUID") + agent_name: str = Field( + ..., + min_length=AGENT_NAME_MIN_LENGTH, + pattern=AGENT_NAME_PATTERN, + description="Agent identifier", + ) time_range: str = Field(..., description="Time range used") totals: StatsTotals = Field(..., description="Agent-level aggregate statistics") controls: list[ControlStats] = Field( ..., description="Per-control breakdown" ) + @field_validator("agent_name", mode="before") + @classmethod + def validate_and_normalize_agent_name(cls, value: str) -> str: + return normalize_agent_name(str(value)) + class ControlStatsResponse(BaseModel): """ @@ -497,15 +538,25 @@ class ControlStatsResponse(BaseModel): Contains stats for a single control (with optional timeseries). Attributes: - agent_uuid: Agent UUID + agent_name: Agent identifier time_range: Time range used control_id: Control ID control_name: Control name stats: Control statistics (includes timeseries when requested) """ - agent_uuid: UUID = Field(..., description="Agent UUID") + agent_name: str = Field( + ..., + min_length=AGENT_NAME_MIN_LENGTH, + pattern=AGENT_NAME_PATTERN, + description="Agent identifier", + ) time_range: str = Field(..., description="Time range used") control_id: int = Field(..., description="Control ID") control_name: str = Field(..., description="Control name") stats: StatsTotals = Field(..., description="Control statistics") + + @field_validator("agent_name", mode="before") + @classmethod + def validate_and_normalize_agent_name(cls, value: str) -> str: + return normalize_agent_name(str(value)) diff --git a/models/src/agent_control_models/server.py b/models/src/agent_control_models/server.py index cdd8d03e..693c439a 100644 --- a/models/src/agent_control_models/server.py +++ b/models/src/agent_control_models/server.py @@ -141,7 +141,6 @@ class InitAgentRequest(BaseModel): "examples": [ { "agent": { - "agent_id": "550e8400-e29b-41d4-a716-446655440000", "agent_name": "customer-service-bot", "agent_description": "Handles customer inquiries", "agent_version": "1.0.0", @@ -310,8 +309,7 @@ class PatchAgentResponse(BaseModel): class AgentSummary(BaseModel): """Summary of an agent for list responses.""" - agent_id: str = Field(..., description="UUID of the agent") - agent_name: str = Field(..., description="Human-readable name of the agent") + agent_name: str = Field(..., description="Unique identifier of the agent") policy_id: int | None = Field(None, description="ID of assigned policy, if any") created_at: str | None = Field(None, description="ISO 8601 timestamp when agent was created") step_count: int = Field(0, description="Number of steps registered with the agent") @@ -347,8 +345,7 @@ class ListAgentsResponse(BaseModel): class AgentRef(BaseModel): """Reference to an agent (for listing which agents use a control).""" - agent_id: str = Field(..., description="Agent UUID") - agent_name: str = Field(..., description="Agent name") + agent_name: str = Field(..., description="Agent identifier") class ControlSummary(BaseModel): diff --git a/sdks/python/ARCHITECTURE.md b/sdks/python/ARCHITECTURE.md index 86625b10..6c1d8db3 100644 --- a/sdks/python/ARCHITECTURE.md +++ b/sdks/python/ARCHITECTURE.md @@ -49,11 +49,11 @@ async with AgentControlClient(base_url="http://localhost:8000") as client: **Endpoints Covered**: - `POST /api/v1/agents/initAgent` - Register or update an agent -- `GET /api/v1/agents/{agent_id}` - Get agent details +- `GET /api/v1/agents/{agent_name}` - Get agent details **Functions**: - `async def register_agent(client, agent, tools)` - Register an agent with tools -- `async def get_agent(client, agent_id)` - Fetch agent details by ID +- `async def get_agent(client, agent_name)` - Fetch agent details by ID **Usage**: ```python @@ -219,8 +219,8 @@ class MyCustomEvaluator(Evaluator): **Purpose**: Public API, convenience functions, and initialization. **Key Functions**: -- `init(agent_name, agent_id, ...)` - Initialize Agent Control -- `get_agent(agent_id, server_url)` - Convenience function for fetching agents +- `init(agent_name, agent_name, ...)` - Initialize Agent Control +- `get_agent(agent_name, server_url)` - Convenience function for fetching agents - `list_agents()` - List all registered agents - `current_agent()` - Get the currently initialized agent - `control()` - Decorator for server-side policy enforcement diff --git a/sdks/python/README.md b/sdks/python/README.md index 8607c60b..8b607e2a 100644 --- a/sdks/python/README.md +++ b/sdks/python/README.md @@ -18,7 +18,7 @@ import agent_control # Initialize at the base of your agent agent_control.init( agent_name="My Customer Service Bot", - agent_id="550e8400-e29b-41d4-a716-446655440000" + agent_name="550e8400-e29b-41d4-a716-446655440000" ) # Use the control decorator @@ -36,7 +36,7 @@ import agent_control agent_control.init( agent_name="Customer Service Bot", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="550e8400-e29b-41d4-a716-446655440000", agent_description="Handles customer inquiries and support", agent_version="2.1.0", server_url="http://localhost:8000", @@ -55,7 +55,7 @@ One line to set up your agent with full protection: ```python agent_control.init( agent_name="...", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="550e8400-e29b-41d4-a716-446655440000", ) ``` @@ -90,7 +90,7 @@ async with AgentControlClient() as client: # Evaluate a step result = await agent_control.evaluation.check_evaluation( client, - agent_uuid="550e8400-e29b-41d4-a716-446655440000", + agent_name="550e8400-e29b-41d4-a716-446655440000", step={"type": "llm_inference", "input": "User input here"}, stage="pre" ) @@ -103,7 +103,7 @@ Access your agent information: ```python agent = agent_control.current_agent() print(f"Agent: {agent.agent_name}") -print(f"ID: {agent.agent_id}") +print(f"ID: {agent.agent_name}") print(f"Version: {agent.agent_version}") ``` @@ -117,7 +117,7 @@ from agent_control import control, ControlViolationError # Initialize agent_control.init( agent_name="Customer Support Bot", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="550e8400-e29b-41d4-a716-446655440000", agent_version="1.0.0" ) @@ -158,7 +158,7 @@ asyncio.run(main()) ```python def init( agent_name: str, - agent_id: str | UUID, + agent_name: str | UUID, agent_description: Optional[str] = None, agent_version: Optional[str] = None, server_url: Optional[str] = None, @@ -171,7 +171,7 @@ Initialize Agent Control with your agent's information. **Parameters:** - `agent_name`: Human-readable name -- `agent_id`: UUID string (or UUID instance) +- `agent_name`: UUID string (or UUID instance) - `agent_description`: Optional description - `agent_version`: Optional version string - `server_url`: Optional server URL (defaults to `AGENT_CONTROL_URL` env var) @@ -278,7 +278,7 @@ from agent_control import control, ControlViolationError agent_control.init( agent_name="...", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="550e8400-e29b-41d4-a716-446655440000", ) @control() diff --git a/sdks/python/src/agent_control/__init__.py b/sdks/python/src/agent_control/__init__.py index c7078f5e..b48db32f 100644 --- a/sdks/python/src/agent_control/__init__.py +++ b/sdks/python/src/agent_control/__init__.py @@ -9,8 +9,7 @@ # Initialize at the base of your agent file agent_control.init( - agent_name="my-customer-service-bot", - agent_id="550e8400-e29b-41d4-a716-446655440000" + agent_name="my-customer-service-bot" ) # Apply server-defined controls using the decorator @@ -33,7 +32,7 @@ async def handle_input(user_message: str) -> str: async with agent_control.AgentControlClient() as client: result = await agent_control.evaluation.check_evaluation( client, - agent_uuid, + agent_name, step={"type": "llm", "name": "chat", "input": "Hello"}, stage="pre", ) @@ -50,7 +49,6 @@ async def handle_input(user_message: str) -> str: from collections.abc import Callable from datetime import UTC, datetime from typing import TYPE_CHECKING, Any, Literal, TypeVar -from uuid import UUID import httpx @@ -107,7 +105,7 @@ async def handle_input(user_message: str) -> str: is_otel_available, with_trace, ) -from .validation import ensure_uuid +from .validation import ensure_agent_name # Module logger logger = get_logger(__name__) @@ -156,11 +154,9 @@ class EvaluatorSpec: class Agent: # runtime fallback def __init__( self, - agent_id: str | UUID, agent_name: str, **kwargs: object ): - self.agent_id = agent_id self.agent_name = agent_name for k, v in kwargs.items(): setattr(self, k, v) @@ -200,11 +196,11 @@ def __init__( class EvaluationRequest: # runtime fallback def __init__( self, - agent_uuid: UUID, + agent_name: str, step: Step, stage: str, ): - self.agent_uuid = agent_uuid + self.agent_name = agent_name self.step = step self.stage = stage @@ -350,7 +346,6 @@ def run_in_thread() -> None: def init( agent_name: str, - agent_id: str | UUID, agent_description: str | None = None, agent_version: str | None = None, server_url: str | None = None, @@ -374,8 +369,7 @@ def init( 5. Enable the @control decorator Args: - agent_name: Human-readable name for your agent (e.g., "Customer Service Bot") - agent_id: Unique identifier for your agent (UUID string or UUID instance) + agent_name: Unique identifier for your agent (will be normalized to lowercase) agent_description: Optional description of what your agent does agent_version: Optional version string (e.g., "1.0.0") server_url: Optional server URL (defaults to AGENT_CONTROL_URL env var @@ -398,8 +392,7 @@ def init( import agent_control agent_control.init( - agent_name="Customer Service Bot", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="customer-service-bot", agent_description="Handles customer inquiries and support tickets", agent_version="2.1.0", steps=[ @@ -424,16 +417,15 @@ async def handle(message: str): """ global _current_agent, _control_engine, _client, _server_controls, _server_url, _api_key - if not agent_id: + if not agent_name: raise ValueError( - "The 'agent_id' argument is required for initialization.\n" - "Please provide a valid UUID string for your agent, e.g.:\n" - ' agent_control.init(agent_name="my-agent", ' - 'agent_id="550e8400-e29b-41d4-a716-446655440000")' + "The 'agent_name' argument is required for initialization.\n" + "Please provide a valid agent identifier, e.g.:\n" + ' agent_control.init(agent_name="customer-service-bot")' ) - # Validate agent_id is a UUID (string or UUID instance) - _agent_uuid = ensure_uuid(agent_id) + # Validate and normalize agent_name + _agent_name = ensure_agent_name(agent_name) # Configure logging if provided (do this early before any logging happens) if log_config: @@ -441,8 +433,7 @@ async def handle(message: str): # Create agent instance with metadata _current_agent = Agent( - agent_id=_agent_uuid, - agent_name=agent_name, + agent_name=_agent_name, agent_description=agent_description, agent_created_at=datetime.now(UTC).isoformat(), agent_updated_at=None, @@ -505,16 +496,16 @@ async def register() -> list[dict[str, Any]] | None: controls: list[dict[str, Any]] = response.get('controls', []) if created: - logger.info("Agent registered: %s (ID: %s)", agent_name, _agent_uuid) + logger.info("Agent registered: %s", _agent_name) else: - logger.info("Agent updated: %s (ID: %s)", agent_name, _agent_uuid) + logger.info("Agent updated: %s", _agent_name) if registration_steps: logger.debug("Registered %d step(s)", len(registration_steps)) return controls except httpx.HTTPStatusError: - # Surface API errors like UUID conflicts + # Surface API errors like name conflicts raise except Exception as e: logger.error("Failed to register agent: %s", e, exc_info=True) @@ -556,7 +547,7 @@ def run_in_thread() -> None: loop.close() except httpx.HTTPStatusError: - # Surface server-side errors (e.g., 409 UUID conflicts) + # Surface server-side errors (e.g., 409 conflicts) raise except Exception as e: logger.error("Could not connect to server: %s", e, exc_info=True) @@ -585,21 +576,21 @@ def run_in_thread() -> None: async def get_agent( - agent_id: str | UUID, + agent_name: str, server_url: str | None = None, api_key: str | None = None, ) -> dict[str, Any]: """ - Get agent details from the server by ID. + Get agent details from the server by name. Args: - agent_id: UUID string or UUID instance + agent_name: Agent identifier server_url: Optional server URL (defaults to AGENT_CONTROL_URL env var) api_key: Optional API key for authentication (defaults to AGENT_CONTROL_API_KEY env var) Returns: Dictionary containing: - - agent: Agent metadata (agent_name, agent_id, etc.) + - agent: Agent metadata - steps: List of steps registered with the agent Raises: @@ -612,7 +603,7 @@ async def get_agent( # Fetch agent from server async def main(): agent_data = await agent_control.get_agent( - "550e8400-e29b-41d4-a716-446655440000" + "customer-service-bot" ) print(f"Agent: {agent_data['agent']['agent_name']}") print(f"Steps: {len(agent_data['steps'])}") @@ -622,13 +613,13 @@ async def main(): # Or using the client directly async with agent_control.AgentControlClient() as client: agent_data = await agent_control.agents.get_agent( - client, "550e8400-e29b-41d4-a716-446655440000" + client, "customer-service-bot" ) """ _final_server_url = server_url or os.getenv('AGENT_CONTROL_URL') or 'http://localhost:8000' async with AgentControlClient(base_url=_final_server_url, api_key=api_key) as client: - return await agents.get_agent(client, agent_id) + return await agents.get_agent(client, agent_name) def current_agent() -> Agent | None: @@ -641,7 +632,6 @@ def current_agent() -> Agent | None: Example: agent_control.init( agent_name="My Bot", - agent_id="550e8400-e29b-41d4-a716-446655440000", ) agent = agent_control.current_agent() print(agent.agent_name) # "My Bot" @@ -661,12 +651,12 @@ async def list_agents( Args: server_url: Optional server URL (defaults to AGENT_CONTROL_URL env var) api_key: Optional API key for authentication (defaults to AGENT_CONTROL_API_KEY env var) - cursor: Optional cursor for pagination (UUID of last agent from previous page) + cursor: Optional cursor for pagination (agent name of last item from previous page) limit: Number of results per page (default 20, max 100) Returns: Dictionary containing: - - agents: List of agent summaries with agent_id, agent_name, + - agents: List of agent summaries with agent_name, policy_id, created_at, step_count, evaluator_count - pagination: Object with limit, total, next_cursor, has_more @@ -681,7 +671,7 @@ async def main(): result = await agent_control.list_agents() print(f"Total agents: {result['pagination']['total']}") for agent in result['agents']: - print(f" - {agent['agent_name']} ({agent['agent_id']})") + print(f" - {agent['agent_name']}") # Fetch next page if result['pagination']['has_more']: next_page = await agent_control.list_agents( diff --git a/sdks/python/src/agent_control/agents.py b/sdks/python/src/agent_control/agents.py index 2706f4bd..1a390be0 100644 --- a/sdks/python/src/agent_control/agents.py +++ b/sdks/python/src/agent_control/agents.py @@ -1,16 +1,16 @@ """Agent management operations for Agent Control SDK.""" from typing import Any, Literal, cast -from uuid import UUID from agent_control_engine import ensure_evaluators_discovered from .client import AgentControlClient -from .validation import ensure_uuid_str +from .validation import ensure_agent_name # Import models if available try: from agent_control_models import Agent + MODELS_AVAILABLE = True except ImportError: MODELS_AVAILABLE = False @@ -23,28 +23,7 @@ async def register_agent( steps: list[dict[str, Any]] | None = None, conflict_mode: Literal["strict", "overwrite"] = "overwrite", ) -> dict[str, Any]: - """ - Register an agent with the server via /initAgent endpoint. - - Args: - client: AgentControlClient instance - agent: Agent instance to register - steps: Optional list of step schemas - conflict_mode: How to handle step/evaluator conflicts during initAgent. - Defaults to "overwrite" for SDK registration flows. - - Returns: - InitAgentResponse with created flag and controls - - Raises: - httpx.HTTPError: If request fails - - Example: - async with AgentControlClient() as client: - response = await register_agent(client, agent, steps=[...]) - print(f"Created: {response['created']}") - """ - # Ensure evaluators are discovered for local evaluation support + """Register an agent with the server via /initAgent endpoint.""" ensure_evaluators_discovered() if steps is None: @@ -52,9 +31,7 @@ async def register_agent( if MODELS_AVAILABLE: agent_dict = agent.to_dict() - # Ensure UUID is converted to string for JSON serialization - if isinstance(agent_dict.get('agent_id'), UUID): - agent_dict['agent_id'] = str(agent_dict['agent_id']) + agent_dict["agent_name"] = ensure_agent_name(str(agent_dict.get("agent_name", ""))) payload = { "agent": agent_dict, "steps": steps, @@ -63,11 +40,10 @@ async def register_agent( else: payload = { "agent": { - "agent_id": str(agent.agent_id), - "agent_name": agent.agent_name, - "agent_description": getattr(agent, 'agent_description', None), - "agent_version": getattr(agent, 'agent_version', None), - "agent_metadata": getattr(agent, 'agent_metadata', None), + "agent_name": ensure_agent_name(str(agent.agent_name)), + "agent_description": getattr(agent, "agent_description", None), + "agent_version": getattr(agent, "agent_version", None), + "agent_metadata": getattr(agent, "agent_metadata", None), }, "steps": steps, "conflict_mode": conflict_mode, @@ -78,33 +54,10 @@ async def register_agent( return cast(dict[str, Any], response.json()) -async def get_agent( - client: AgentControlClient, - agent_id: str | UUID -) -> dict[str, Any]: - """ - Get agent details by ID from the server. - - Args: - client: AgentControlClient instance - agent_id: UUID string or UUID instance - - Returns: - Dictionary containing: - - agent: Agent metadata - - steps: List of steps registered with the agent - - Raises: - httpx.HTTPError: If request fails or agent not found (404) - - Example: - async with AgentControlClient() as client: - agent_data = await get_agent(client, "550e8400-e29b-41d4-a716-446655440000") - print(f"Agent: {agent_data['agent']['agent_name']}") - print(f"Steps: {len(agent_data['steps'])}") - """ - agent_id_str = ensure_uuid_str(agent_id) - response = await client.http_client.get(f"/api/v1/agents/{agent_id_str}") +async def get_agent(client: AgentControlClient, agent_name: str) -> dict[str, Any]: + """Get agent details by name from the server.""" + normalized_name = ensure_agent_name(agent_name) + response = await client.http_client.get(f"/api/v1/agents/{normalized_name}") response.raise_for_status() return cast(dict[str, Any], response.json()) @@ -114,38 +67,10 @@ async def list_agents( cursor: str | None = None, limit: int = 20, ) -> dict[str, Any]: - """ - List all registered agents from the server. - - Args: - client: AgentControlClient instance - cursor: Optional cursor for pagination (UUID of last agent from previous page) - limit: Number of results per page (default 20, max 100) - - Returns: - Dictionary containing: - - agents: List of agent summaries with agent_id, agent_name, - policy_id, created_at, step_count, evaluator_count - - pagination: Object with limit, total, next_cursor, has_more - - Raises: - httpx.HTTPError: If request fails - - Example: - async with AgentControlClient() as client: - result = await list_agents(client, limit=10) - print(f"Total agents: {result['pagination']['total']}") - for agent in result['agents']: - print(f" - {agent['agent_name']} ({agent['agent_id']})") - # Fetch next page if available - if result['pagination']['has_more']: - next_result = await list_agents( - client, cursor=result['pagination']['next_cursor'] - ) - """ + """List all registered agents from the server.""" params: dict[str, Any] = {"limit": limit} if cursor: - params["cursor"] = cursor + params["cursor"] = ensure_agent_name(cursor) response = await client.http_client.get("/api/v1/agents", params=params) response.raise_for_status() return cast(dict[str, Any], response.json()) @@ -153,51 +78,21 @@ async def list_agents( async def get_agent_policy( client: AgentControlClient, - agent_id: str | UUID, + agent_name: str, ) -> dict[str, Any]: - """ - Get the policy assigned to an agent. - - Args: - client: AgentControlClient instance - agent_id: UUID string or UUID instance - - Returns: - Dictionary containing: - - policy_id: ID of the policy assigned to the agent - - Raises: - httpx.HTTPError: If request fails or agent has no policy - - Example: - async with AgentControlClient() as client: - policy = await get_agent_policy(client, agent_id) - print(f"Policy ID: {policy['policy_id']}") - """ - agent_id_str = ensure_uuid_str(agent_id) - response = await client.http_client.get(f"/api/v1/agents/{agent_id_str}/policy") + """Get the policy assigned to an agent.""" + normalized_name = ensure_agent_name(agent_name) + response = await client.http_client.get(f"/api/v1/agents/{normalized_name}/policy") response.raise_for_status() return cast(dict[str, Any], response.json()) async def remove_agent_policy( client: AgentControlClient, - agent_id: str | UUID, + agent_name: str, ) -> dict[str, Any]: - """ - Remove the policy assignment from an agent. - - Args: - client: AgentControlClient instance - agent_id: UUID string or UUID instance - - Returns: - Dictionary containing success flag/details - - Raises: - httpx.HTTPError: If request fails or agent has no policy - """ - agent_id_str = ensure_uuid_str(agent_id) - response = await client.http_client.delete(f"/api/v1/agents/{agent_id_str}/policy") + """Remove the policy assignment from an agent.""" + normalized_name = ensure_agent_name(agent_name) + response = await client.http_client.delete(f"/api/v1/agents/{normalized_name}/policy") response.raise_for_status() return cast(dict[str, Any], response.json()) diff --git a/sdks/python/src/agent_control/control_decorators.py b/sdks/python/src/agent_control/control_decorators.py index fdcfcf37..e671e4fb 100644 --- a/sdks/python/src/agent_control/control_decorators.py +++ b/sdks/python/src/agent_control/control_decorators.py @@ -12,8 +12,7 @@ import agent_control agent_control.init( - agent_name="my-agent", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="my-agent-identity", ) # Apply the agent's assigned policy @@ -60,7 +59,6 @@ class ControlContext: control wrappers, including stats tracking, result processing, and logging. """ - agent_uuid: str agent_name: str server_url: str func: Callable @@ -188,14 +186,14 @@ def _get_server_url() -> str: async def _evaluate( - agent_uuid: str, + agent_name: str, step: dict[str, Any], stage: str, server_url: str, trace_id: str | None = None, span_id: str | None = None, controls: list[dict[str, Any]] | None = None, - agent_name: str | None = None, + event_agent_name: str | None = None, ) -> dict[str, Any]: """Call evaluation with support for local (SDK) and server execution. @@ -216,8 +214,6 @@ async def _evaluate( # If we have controls, use local evaluation which handles both SDK and server controls if controls is not None: try: - from uuid import UUID - from agent_control.evaluation import check_evaluation_with_local # Build Step object for evaluation @@ -229,13 +225,13 @@ async def _evaluate( result = await check_evaluation_with_local( client=client, - agent_uuid=UUID(agent_uuid), + agent_name=agent_name, step=step_obj, stage=stage, # type: ignore controls=controls, trace_id=trace_id, span_id=span_id, - agent_name=agent_name, + event_agent_name=event_agent_name, ) # Convert result to dict format expected by process_result @@ -308,7 +304,7 @@ async def _evaluate( response = await client.http_client.post( "/api/v1/evaluation", json={ - "agent_uuid": str(agent_uuid), + "agent_name": str(agent_name), "step": step, "stage": stage }, @@ -577,7 +573,6 @@ async def _execute_with_control( trace_id, span_id = get_trace_and_span_ids() # New trace and span ctx = ControlContext( - agent_uuid=str(agent.agent_id), agent_name=agent.agent_name, server_url=_get_server_url(), func=func, @@ -594,10 +589,10 @@ async def _execute_with_control( # PRE-EXECUTION: Check controls with check_stage="pre" try: result = await _evaluate( - ctx.agent_uuid, ctx.pre_payload(), "pre", + ctx.agent_name, ctx.pre_payload(), "pre", ctx.server_url, ctx.trace_id, ctx.span_id, controls=controls, - agent_name=ctx.agent_name, + event_agent_name=ctx.agent_name, ) ctx.process_result(result, "pre") except ControlViolationError: @@ -618,10 +613,10 @@ async def _execute_with_control( # POST-EXECUTION: Check controls with check_stage="post" try: result = await _evaluate( - ctx.agent_uuid, ctx.post_payload(output), "post", + ctx.agent_name, ctx.post_payload(output), "post", ctx.server_url, ctx.trace_id, ctx.span_id, controls=controls, - agent_name=ctx.agent_name, + event_agent_name=ctx.agent_name, ) ctx.process_result(result, "post") except ControlViolationError: @@ -666,8 +661,7 @@ def control(policy: str | None = None, step_name: str | None = None) -> Callable # Initialize agent (connects to server, loads policy) agent_control.init( - agent_name="my-bot", - agent_id="550e8400-e29b-41d4-a716-446655440000", + agent_name="my-bot-identity", ) # Apply the agent's policy (all controls) @@ -695,7 +689,7 @@ async def handle_user_input(user_message: str) -> str: POST /api/v1/policies/{policy_id}/controls/{control_id} 3. Assign policy to agent: - POST /api/v1/agents/{agent_id}/policy/{policy_id} + POST /api/v1/agents/{agent_name}/policy/{policy_id} """ # The policy parameter is for documentation only - the server uses # the agent's assigned policy automatically diff --git a/sdks/python/src/agent_control/evaluation.py b/sdks/python/src/agent_control/evaluation.py index deddd6ee..81d45cff 100644 --- a/sdks/python/src/agent_control/evaluation.py +++ b/sdks/python/src/agent_control/evaluation.py @@ -3,16 +3,15 @@ from dataclasses import dataclass from datetime import UTC, datetime from typing import Any, Literal, cast -from uuid import UUID from .client import AgentControlClient from .observability import add_event, get_logger, is_observability_enabled +from .validation import ensure_agent_name _logger = get_logger(__name__) # Fallback IDs used when trace context is missing. -# All-zero values are invalid trace/span IDs per OpenTelemetry, making them -# easy to filter in observability queries while still recording the event. +# All-zero values are invalid trace/span IDs per OpenTelemetry. _FALLBACK_TRACE_ID = "0" * 32 _FALLBACK_SPAN_ID = "0" * 16 _trace_warning_logged = False @@ -37,7 +36,6 @@ except ImportError: MODELS_AVAILABLE = False ENGINE_AVAILABLE = False - # Runtime fallbacks Step = Any # type: ignore EvaluationRequest = Any # type: ignore EvaluationResponse = Any # type: ignore @@ -50,10 +48,6 @@ def _map_applies_to(step_type: str) -> Literal["llm_call", "tool_call"]: - """Map step type to observability applies_to value. - - Matches the server pattern at endpoints/evaluation.py. - """ return "tool_call" if step_type == "tool" else "llm_call" @@ -65,19 +59,8 @@ def _emit_local_events( span_id: str | None, agent_name: str | None, ) -> None: - """Emit observability events for locally-evaluated controls. - - Mirrors the server's _emit_observability_events() so that SDK-evaluated - controls are visible in the observability pipeline. - - When trace_id/span_id are missing, fallback all-zero IDs are used so events - are still recorded (but clearly marked as uncorrelated). - - Only runs when observability is enabled. - """ - if not is_observability_enabled(): - return - if not ENGINE_AVAILABLE: + """Emit observability events for locally-evaluated controls.""" + if not is_observability_enabled() or not ENGINE_AVAILABLE: return global _trace_warning_logged # noqa: PLW0603 @@ -95,31 +78,31 @@ def _emit_local_events( applies_to = _map_applies_to(request.step.type) control_lookup = {c.id: c for c in local_controls} now = datetime.now(UTC) + resolved_agent_name = agent_name or request.agent_name def _emit_matches(matches: list[ControlMatch] | None, matched: bool) -> None: if not matches: return - for m in matches: - ctrl = control_lookup.get(m.control_id) + for match in matches: + ctrl = control_lookup.get(match.control_id) add_event( ControlExecutionEvent( - control_execution_id=m.control_execution_id, + control_execution_id=match.control_execution_id, trace_id=trace_id, span_id=span_id, - agent_uuid=request.agent_uuid, - agent_name=agent_name or "unknown", - control_id=m.control_id, - control_name=m.control_name, + agent_name=resolved_agent_name, + control_id=match.control_id, + control_name=match.control_name, check_stage=request.stage, applies_to=applies_to, - action=m.action, + action=match.action, matched=matched, - confidence=m.result.confidence, + confidence=match.result.confidence, timestamp=now, evaluator_name=ctrl.control.evaluator.name if ctrl else None, selector_path=ctrl.control.selector.path if ctrl else None, - error_message=m.result.error if not matched else None, - metadata=m.result.metadata or {}, + error_message=match.result.error if not matched else None, + metadata=match.result.metadata or {}, ) ) @@ -130,58 +113,21 @@ def _emit_matches(matches: list[ControlMatch] | None, matched: bool) -> None: async def check_evaluation( client: AgentControlClient, - agent_uuid: UUID, + agent_name: str, step: "Step", stage: Literal["pre", "post"], ) -> EvaluationResult: - """ - Check if agent interaction is safe. - - Args: - client: AgentControlClient instance - agent_uuid: UUID of the agent making the request - step: Step payload to evaluate - stage: 'pre' for pre-execution check, 'post' for post-execution check - - Returns: - EvaluationResult with safety analysis - - Raises: - httpx.HTTPError: If request fails - - Example: - # Pre-check before LLM step - async with AgentControlClient() as client: - result = await check_evaluation( - client=client, - agent_uuid=agent.agent_id, - step={"type": "llm", "name": "support-answer", "input": "User question"}, - stage="pre" - ) + """Check if agent interaction is safe.""" + normalized_name = ensure_agent_name(agent_name) - # Post-check after tool execution - async with AgentControlClient() as client: - result = await check_evaluation( - client=client, - agent_uuid=agent.agent_id, - step={ - "type": "tool", - "name": "search", - "input": {"query": "test"}, - "output": {"results": []}, - }, - stage="post" - ) - """ if MODELS_AVAILABLE: request = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=normalized_name, step=step, stage=stage, ) request_payload = request.model_dump(mode="json") else: - # Fallback for when models aren't available if isinstance(step, dict): step_dict = step else: @@ -198,7 +144,7 @@ async def check_evaluation( raise ValueError("step.name is required for evaluation requests") request_payload = { - "agent_uuid": str(agent_uuid), + "agent_name": normalized_name, "step": step_dict, "stage": stage, } @@ -208,15 +154,16 @@ async def check_evaluation( if MODELS_AVAILABLE: return cast(EvaluationResult, EvaluationResult.from_dict(response.json())) - else: - data = response.json() - # Create a simple result object - class _EvaluationResult: - def __init__(self, is_safe: bool, confidence: float, reason: str | None = None): - self.is_safe = is_safe - self.confidence = confidence - self.reason = reason - return cast(EvaluationResult, _EvaluationResult(**data)) + + data = response.json() + + class _EvaluationResult: + def __init__(self, is_safe: bool, confidence: float, reason: str | None = None): + self.is_safe = is_safe + self.confidence = confidence + self.reason = reason + + return cast(EvaluationResult, _EvaluationResult(**data)) @dataclass @@ -232,35 +179,22 @@ def _merge_results( local_result: "EvaluationResponse", server_result: "EvaluationResponse", ) -> "EvaluationResult": - """Merge local and server evaluation results. - - Merge semantics: - - is_safe: False if either is False (deny from either → deny) - - confidence: min of both (most conservative) - - matches: combined from both - - errors: combined from both - """ + """Merge local and server evaluation results.""" is_safe = local_result.is_safe and server_result.is_safe - - # Use minimum confidence (most conservative) confidence = min(local_result.confidence, server_result.confidence) - # Combine matches matches: list[ControlMatch] | None = None if local_result.matches or server_result.matches: matches = (local_result.matches or []) + (server_result.matches or []) - # Combine errors errors: list[ControlMatch] | None = None if local_result.errors or server_result.errors: errors = (local_result.errors or []) + (server_result.errors or []) - # Combine non_matches non_matches: list[ControlMatch] | None = None if local_result.non_matches or server_result.non_matches: non_matches = (local_result.non_matches or []) + (server_result.non_matches or []) - # Combine reasons reason = None if local_result.reason and server_result.reason: reason = f"{local_result.reason}; {server_result.reason}" @@ -281,113 +215,70 @@ def _merge_results( async def check_evaluation_with_local( client: AgentControlClient, - agent_uuid: UUID, + agent_name: str, step: "Step", stage: Literal["pre", "post"], controls: list[dict[str, Any]], trace_id: str | None = None, span_id: str | None = None, - agent_name: str | None = None, + event_agent_name: str | None = None, ) -> EvaluationResult: - """ - Check if agent interaction is safe, running local controls first. - - This function executes controls with execution="sdk" locally in the SDK, - then calls the server for execution="server" controls. If a local control - denies, it short-circuits and returns immediately without calling the server. - - Note on parse errors: If a local control fails to parse/validate, it is - skipped (logged as WARNING) and the error is included in result.errors. - This does NOT affect is_safe or confidence—callers concerned with safety - should check result.errors for any parse failures. - - Args: - client: AgentControlClient instance - agent_uuid: UUID of the agent making the request - step: Step payload to evaluate - stage: 'pre' for pre-execution check, 'post' for post-execution check - controls: List of control dicts from initAgent response - (each has 'id', 'name', 'control' keys) - - Returns: - EvaluationResult with safety analysis (merged from local + server) - - Raises: - httpx.HTTPError: If server request fails - RuntimeError: If engine is not available - - Example: - # Get controls from initAgent - init_response = await register_agent(client, agent, steps) - controls = init_response.get('controls', []) - - # Check with local execution - result = await check_evaluation_with_local( - client=client, - agent_uuid=agent.agent_id, - step={"type": "llm", "name": "support-answer", "input": "User question"}, - stage="pre", - controls=controls, - ) - """ + """Check safety, running local SDK controls before server controls.""" if not ENGINE_AVAILABLE: raise RuntimeError( "Local evaluation requires agent_control_engine. " "Install with: pip install agent-control-engine" ) - # Partition controls by local flag + normalized_name = ensure_agent_name(agent_name) + local_controls: list[_ControlAdapter] = [] parse_errors: list[ControlMatch] = [] has_server_controls = False - for c in controls: - control_data = c.get("control", {}) + for control in controls: + control_data = control.get("control", {}) execution = control_data.get("execution", "server") is_local = execution == "sdk" - # Track server controls early, before any parsing that might fail if not is_local: has_server_controls = True - continue # Server controls are handled by the server, not parsed here + continue - # Parse and validate local controls try: control_def = ControlDefinition.model_validate(control_data) - - # Validate evaluator is available locally evaluator_name = control_def.evaluator.name - # Agent-scoped evaluators (agent:evaluator) are server-only + if ":" in evaluator_name: raise RuntimeError( - f"Control '{c['name']}' is marked execution='sdk' but uses " + f"Control '{control['name']}' is marked execution='sdk' but uses " f"agent-scoped evaluator '{evaluator_name}' which is server-only. " "Set execution='server' or use a built-in evaluator." ) if evaluator_name not in list_evaluators(): raise RuntimeError( - f"Control '{c['name']}' is marked execution='sdk' but evaluator " + f"Control '{control['name']}' is marked execution='sdk' but evaluator " f"'{evaluator_name}' is not available in the SDK. " "Install the evaluator or set execution='server'." ) - local_controls.append(_ControlAdapter( - id=c["id"], - name=c["name"], - control=control_def, - )) + local_controls.append( + _ControlAdapter( + id=control["id"], + name=control["name"], + control=control_def, + ) + ) except RuntimeError: - # Re-raise our explicit errors raise - except Exception as e: - # Validation/parse error - log and add to errors list - control_id = c.get("id", -1) - control_name = c.get("name", "unknown") + except Exception as exc: + control_id = control.get("id", -1) + control_name = control.get("name", "unknown") _logger.warning( "Skipping invalid local control '%s' (id=%s): %s", control_name, control_id, - e, + exc, ) parse_errors.append( ControlMatch( @@ -397,13 +288,12 @@ async def check_evaluation_with_local( result=EvaluatorResult( matched=False, confidence=0.0, - error=f"Failed to parse local control: {e}", + error=f"Failed to parse local control: {exc}", ), ) ) def _with_parse_errors(result: EvaluationResult) -> EvaluationResult: - """Merge parse_errors into result.errors.""" if not parse_errors: return result combined_errors = (result.errors or []) + parse_errors @@ -416,27 +306,26 @@ def _with_parse_errors(result: EvaluationResult) -> EvaluationResult: non_matches=result.non_matches, ) - # Build evaluation request request = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=normalized_name, step=step, stage=stage, ) - # Run local controls if any local_result: EvaluationResponse | None = None if local_controls: engine = ControlEngine(local_controls, context="sdk") local_result = await engine.process(request) - # Emit observability events for locally-evaluated controls - # (before short-circuit so events are always emitted for local controls) _emit_local_events( - local_result, request, local_controls, - trace_id, span_id, agent_name, + local_result, + request, + local_controls, + trace_id, + span_id, + agent_name=event_agent_name, ) - # Short-circuit on local deny if not local_result.is_safe: return _with_parse_errors( EvaluationResult( @@ -449,22 +338,22 @@ def _with_parse_errors(result: EvaluationResult) -> EvaluationResult: ) ) - # Call server for non-local controls (if any exist) if has_server_controls: request_payload = request.model_dump(mode="json", exclude_none=True) - # Forward trace context as headers so server-emitted events have correct IDs headers: dict[str, str] = {} if trace_id: headers["X-Trace-Id"] = trace_id if span_id: headers["X-Span-Id"] = span_id + response = await client.http_client.post( - "/api/v1/evaluation", json=request_payload, headers=headers, + "/api/v1/evaluation", + json=request_payload, + headers=headers, ) response.raise_for_status() server_result = EvaluationResponse.model_validate(response.json()) - # Merge results if we had local controls if local_result is not None: return _with_parse_errors(_merge_results(local_result, server_result)) @@ -479,7 +368,6 @@ def _with_parse_errors(result: EvaluationResult) -> EvaluationResult: ) ) - # Only local controls existed (and they all passed) if local_result is not None: return _with_parse_errors( EvaluationResult( @@ -492,5 +380,4 @@ def _with_parse_errors(result: EvaluationResult) -> EvaluationResult: ) ) - # No controls at all - still include parse_errors if any return _with_parse_errors(EvaluationResult(is_safe=True, confidence=1.0)) diff --git a/sdks/python/src/agent_control/policies.py b/sdks/python/src/agent_control/policies.py index dab7ecaa..aef75bbf 100644 --- a/sdks/python/src/agent_control/policies.py +++ b/sdks/python/src/agent_control/policies.py @@ -1,10 +1,9 @@ """Policy management operations for Agent Control SDK.""" from typing import Any, cast -from uuid import UUID from .client import AgentControlClient -from .validation import ensure_uuid_str +from .validation import ensure_agent_name async def create_policy( @@ -152,7 +151,7 @@ async def list_policy_controls( async def assign_policy_to_agent( client: AgentControlClient, - agent_id: str | UUID, + agent_name: str, policy_id: int ) -> dict[str, Any]: """ @@ -162,7 +161,7 @@ async def assign_policy_to_agent( Args: client: AgentControlClient instance - agent_id: UUID string or UUID instance + agent_name: Agent identifier policy_id: ID of the policy to assign Returns: @@ -172,9 +171,9 @@ async def assign_policy_to_agent( httpx.HTTPError: If request fails HTTPException 404: Agent or policy not found """ - agent_id_str = ensure_uuid_str(agent_id) + agent_name_str = ensure_agent_name(agent_name) response = await client.http_client.post( - f"/api/v1/agents/{agent_id_str}/policy/{policy_id}" + f"/api/v1/agents/{agent_name_str}/policy/{policy_id}" ) response.raise_for_status() return cast(dict[str, Any], response.json()) diff --git a/sdks/python/src/agent_control/validation.py b/sdks/python/src/agent_control/validation.py index aaf95ff3..874b0072 100644 --- a/sdks/python/src/agent_control/validation.py +++ b/sdks/python/src/agent_control/validation.py @@ -2,19 +2,21 @@ from __future__ import annotations -from uuid import UUID +import re +_AGENT_NAME_MIN_LENGTH = 10 +_AGENT_NAME_REGEX = re.compile(r"^[a-z0-9:_-]+$") -def ensure_uuid(value: str | UUID, field_name: str = "agent_id") -> UUID: - """Return a UUID instance or raise ValueError for invalid UUID strings.""" - if isinstance(value, UUID): - return value - try: - return UUID(str(value)) - except (TypeError, ValueError, AttributeError) as exc: - raise ValueError(f"{field_name} must be a valid UUID string") from exc - -def ensure_uuid_str(value: str | UUID, field_name: str = "agent_id") -> str: - """Return a UUID string or raise ValueError for invalid UUID strings.""" - return str(ensure_uuid(value, field_name=field_name)) +def ensure_agent_name(value: str, field_name: str = "agent_name") -> str: + """Return normalized agent name or raise ValueError for invalid values.""" + normalized = str(value).strip().lower() + if len(normalized) < _AGENT_NAME_MIN_LENGTH: + raise ValueError( + f"{field_name} must be at least {_AGENT_NAME_MIN_LENGTH} characters long" + ) + if not _AGENT_NAME_REGEX.fullmatch(normalized): + raise ValueError( + f"{field_name} may only contain lowercase letters, digits, ':', '_' or '-'" + ) + return normalized diff --git a/sdks/python/tests/README.md b/sdks/python/tests/README.md index 71e690d7..b4677039 100644 --- a/sdks/python/tests/README.md +++ b/sdks/python/tests/README.md @@ -317,10 +317,10 @@ async def test_my_new_workflow( - Feature Y returns expected data """ # Arrange - agent_id = test_agent["agent_id"] + agent_name = test_agent["agent_name"] # Act - result = await agent_control.my_module.my_operation(client, agent_id) + result = await agent_control.my_module.my_operation(client, agent_name) # Assert assert result["success"] is True diff --git a/sdks/python/tests/conftest.py b/sdks/python/tests/conftest.py index b1201214..6860c8e9 100644 --- a/sdks/python/tests/conftest.py +++ b/sdks/python/tests/conftest.py @@ -101,8 +101,8 @@ def unique_name() -> str: @pytest.fixture def test_agent_id() -> str: - """Generate a unique agent ID for testing.""" - return str(uuid.uuid4()) + """Generate a unique agent name for testing.""" + return f"agent-{uuid.uuid4().hex[:12]}" @pytest_asyncio.fixture @@ -121,12 +121,8 @@ async def test_agent( from agent_control_models import Agent - # Generate a proper UUID4 for the agent - agent_uuid = uuid.uuid4() - agent = Agent( - agent_id=agent_uuid, - agent_name=f"Test Agent {test_agent_id}", + agent_name=test_agent_id, agent_description="Integration test agent", agent_created_at=datetime.now(UTC).isoformat(), agent_updated_at=None, @@ -150,7 +146,8 @@ async def test_agent( yield { "agent": agent, - "agent_id": str(agent_uuid), + "agent_name": test_agent_id, + "agent_name": test_agent_id, "response": response } diff --git a/sdks/python/tests/test_agent_id_validation.py b/sdks/python/tests/test_agent_id_validation.py index 488538ac..8bc3dd69 100644 --- a/sdks/python/tests/test_agent_id_validation.py +++ b/sdks/python/tests/test_agent_id_validation.py @@ -1,8 +1,6 @@ -"""SDK agent_id validation behavior tests.""" +"""SDK agent name validation behavior tests.""" from unittest.mock import AsyncMock, MagicMock -from uuid import uuid4 - import pytest from agent_control import agents, policies @@ -17,60 +15,60 @@ def json(self) -> dict: @pytest.mark.asyncio -async def test_get_agent_rejects_invalid_uuid() -> None: +async def test_get_agent_rejects_invalid_agent_name() -> None: client = MagicMock() client.http_client = MagicMock() client.http_client.get = AsyncMock() - with pytest.raises(ValueError, match="agent_id must be a valid UUID"): - await agents.get_agent(client, "not-a-uuid") + with pytest.raises(ValueError, match="at least 10 characters"): + await agents.get_agent(client, "short") client.http_client.get.assert_not_called() @pytest.mark.asyncio -async def test_get_agent_policy_rejects_invalid_uuid() -> None: +async def test_get_agent_policy_rejects_invalid_agent_name() -> None: client = MagicMock() client.http_client = MagicMock() client.http_client.get = AsyncMock() - with pytest.raises(ValueError, match="agent_id must be a valid UUID"): - await agents.get_agent_policy(client, "not-a-uuid") + with pytest.raises(ValueError, match="at least 10 characters"): + await agents.get_agent_policy(client, "short") client.http_client.get.assert_not_called() @pytest.mark.asyncio -async def test_remove_agent_policy_rejects_invalid_uuid() -> None: +async def test_remove_agent_policy_rejects_invalid_agent_name() -> None: client = MagicMock() client.http_client = MagicMock() client.http_client.delete = AsyncMock() - with pytest.raises(ValueError, match="agent_id must be a valid UUID"): - await agents.remove_agent_policy(client, "not-a-uuid") + with pytest.raises(ValueError, match="at least 10 characters"): + await agents.remove_agent_policy(client, "short") client.http_client.delete.assert_not_called() @pytest.mark.asyncio -async def test_assign_policy_rejects_invalid_uuid() -> None: +async def test_assign_policy_rejects_invalid_agent_name() -> None: client = MagicMock() client.http_client = MagicMock() client.http_client.post = AsyncMock() - with pytest.raises(ValueError, match="agent_id must be a valid UUID"): - await policies.assign_policy_to_agent(client, "not-a-uuid", policy_id=1) + with pytest.raises(ValueError, match="at least 10 characters"): + await policies.assign_policy_to_agent(client, "short", policy_id=1) client.http_client.post.assert_not_called() @pytest.mark.asyncio -async def test_get_agent_accepts_uuid_object() -> None: +async def test_get_agent_normalizes_agent_name() -> None: client = MagicMock() client.http_client = MagicMock() client.http_client.get = AsyncMock(return_value=DummyResponse()) - agent_id = uuid4() - await agents.get_agent(client, agent_id) + agent_name = "Agent-Example_01" + await agents.get_agent(client, agent_name) - client.http_client.get.assert_awaited_once_with(f"/api/v1/agents/{agent_id}") + client.http_client.get.assert_awaited_once_with("/api/v1/agents/agent-example_01") diff --git a/sdks/python/tests/test_control_decorators.py b/sdks/python/tests/test_control_decorators.py index 40eea1e4..5ff5c35f 100644 --- a/sdks/python/tests/test_control_decorators.py +++ b/sdks/python/tests/test_control_decorators.py @@ -15,7 +15,7 @@ def mock_agent(): """Create a mock agent.""" agent = MagicMock() - agent.agent_id = "550e8400-e29b-41d4-a716-446655440000" + agent.agent_name = "550e8400-e29b-41d4-a716-446655440000" return agent @@ -184,14 +184,14 @@ async def test_calls_pre_and_post(self, mock_agent, mock_safe_response): call_stages = [] async def mock_evaluate( - agent_uuid, + agent_name, step, stage, server_url, trace_id=None, span_id=None, controls=None, - agent_name=None, + event_agent_name=None, ): call_stages.append(stage) return mock_safe_response @@ -214,14 +214,14 @@ async def test_pre_block_prevents_execution(self, mock_agent, mock_safe_response function_executed = False async def mock_evaluate( - agent_uuid, + agent_name, step, stage, server_url, trace_id=None, span_id=None, controls=None, - agent_name=None, + event_agent_name=None, ): if stage == "pre": return mock_unsafe_response @@ -247,14 +247,14 @@ async def test_post_check_receives_output(self, mock_agent, mock_safe_response): captured_step = {} async def mock_evaluate( - agent_uuid, + agent_name, step, stage, server_url, trace_id=None, span_id=None, controls=None, - agent_name=None, + event_agent_name=None, ): if stage == "post": captured_step.update(step) @@ -286,14 +286,14 @@ async def test_extracts_input_param(self, mock_agent, mock_safe_response): captured_step = {} async def mock_evaluate( - agent_uuid, + agent_name, step, stage, server_url, trace_id=None, span_id=None, controls=None, - agent_name=None, + event_agent_name=None, ): if stage == "pre": captured_step.update(step) @@ -316,14 +316,14 @@ async def test_extracts_message_param(self, mock_agent, mock_safe_response): captured_step = {} async def mock_evaluate( - agent_uuid, + agent_name, step, stage, server_url, trace_id=None, span_id=None, controls=None, - agent_name=None, + event_agent_name=None, ): if stage == "pre": captured_step.update(step) @@ -346,14 +346,14 @@ async def test_extracts_query_param(self, mock_agent, mock_safe_response): captured_step = {} async def mock_evaluate( - agent_uuid, + agent_name, step, stage, server_url, trace_id=None, span_id=None, controls=None, - agent_name=None, + event_agent_name=None, ): if stage == "pre": captured_step.update(step) @@ -483,14 +483,14 @@ async def test_custom_step_name_used_in_payload(self, mock_agent, mock_safe_resp captured_steps = [] async def mock_evaluate( - agent_uuid, + agent_name, step, stage, server_url, trace_id=None, span_id=None, controls=None, - agent_name=None, + event_agent_name=None, ): captured_steps.append(step) return mock_safe_response @@ -521,14 +521,14 @@ async def test_default_step_name_uses_function_name(self, mock_agent, mock_safe_ captured_steps = [] async def mock_evaluate( - agent_uuid, + agent_name, step, stage, server_url, trace_id=None, span_id=None, controls=None, - agent_name=None, + event_agent_name=None, ): captured_steps.append(step) return mock_safe_response @@ -559,14 +559,14 @@ async def test_step_name_with_tool_decorator(self, mock_agent, mock_safe_respons captured_steps = [] async def mock_evaluate( - agent_uuid, + agent_name, step, stage, server_url, trace_id=None, span_id=None, controls=None, - agent_name=None, + event_agent_name=None, ): captured_steps.append(step) return mock_safe_response diff --git a/sdks/python/tests/test_evaluation.py b/sdks/python/tests/test_evaluation.py index 6bd50101..7e1fd6a7 100644 --- a/sdks/python/tests/test_evaluation.py +++ b/sdks/python/tests/test_evaluation.py @@ -20,7 +20,7 @@ async def test_check_evaluation_requires_step_name_without_models(monkeypatch): with pytest.raises(ValueError, match="step.name is required"): await evaluation.check_evaluation( client=client, - agent_uuid=UUID("00000000-0000-0000-0000-000000000001"), + agent_name=UUID("00000000-0000-0000-0000-000000000001"), step={"type": "llm", "input": "hello"}, stage="pre", ) diff --git a/sdks/python/tests/test_init_conflict.py b/sdks/python/tests/test_init_conflict.py index 3e712e01..3fb23e14 100644 --- a/sdks/python/tests/test_init_conflict.py +++ b/sdks/python/tests/test_init_conflict.py @@ -19,7 +19,7 @@ def _make_conflict_error() -> httpx.HTTPStatusError: ) -def test_init_surfaces_uuid_conflict() -> None: +def test_init_surfaces_conflict_response() -> None: conflict = _make_conflict_error() with patch( @@ -31,7 +31,6 @@ def test_init_surfaces_uuid_conflict() -> None: ): with pytest.raises(httpx.HTTPStatusError): agent_control.init( - agent_name="Init Conflict Agent", - agent_id=str(uuid4()), + agent_name=f"agent-{uuid4().hex[:12]}", agent_description="Testing init conflict handling", ) diff --git a/sdks/python/tests/test_init_step_merge.py b/sdks/python/tests/test_init_step_merge.py index d56fe0d7..aab24fff 100644 --- a/sdks/python/tests/test_init_step_merge.py +++ b/sdks/python/tests/test_init_step_merge.py @@ -63,8 +63,7 @@ def auto_llm(query: str) -> str: ): with caplog.at_level(logging.WARNING): agent_control.init( - agent_name="Init Merge Agent", - agent_id=str(uuid4()), + agent_name=f"agent-{uuid4().hex[:12]}", steps=explicit_steps, ) @@ -102,8 +101,7 @@ async def auto_chat(message: str, temperature: float = 0.2) -> str: new=register_agent_mock, ): agent_control.init( - agent_name="Auto Discovery Agent", - agent_id=str(uuid4()), + agent_name=f"agent-{uuid4().hex[:12]}", ) # Then register_agent() receives the auto-derived step schema payload. @@ -145,8 +143,7 @@ async def unresolved(payload: DoesNotExist) -> str: ): with caplog.at_level(logging.WARNING): agent_control.init( - agent_name="Fallback Warning Agent", - agent_id=str(uuid4()), + agent_name=f"agent-{uuid4().hex[:12]}", ) # Then initialization continues, using fallback schemas and emitting a warning. @@ -179,8 +176,7 @@ async def test_refresh_controls_uses_strict_conflict_mode() -> None: new=register_agent_mock, ): agent_control.init( - agent_name="Refresh Strict Agent", - agent_id=str(uuid4()), + agent_name=f"agent-{uuid4().hex[:12]}", ) # When: controls are refreshed through refresh_controls_async(). diff --git a/sdks/python/tests/test_init_validation.py b/sdks/python/tests/test_init_validation.py index 80341e3f..241ab8b4 100644 --- a/sdks/python/tests/test_init_validation.py +++ b/sdks/python/tests/test_init_validation.py @@ -8,9 +8,9 @@ import agent_control -def test_init_rejects_invalid_uuid() -> None: - with pytest.raises(ValueError, match="agent_id must be a valid UUID"): - agent_control.init(agent_name="Invalid UUID Agent", agent_id="not-a-uuid") +def test_init_rejects_invalid_agent_name() -> None: + with pytest.raises(ValueError, match="at least 10 characters"): + agent_control.init(agent_name="short") def test_init_exports_control_scope() -> None: diff --git a/sdks/python/tests/test_integration_agents.py b/sdks/python/tests/test_integration_agents.py index f3208a53..f0152b3e 100644 --- a/sdks/python/tests/test_integration_agents.py +++ b/sdks/python/tests/test_integration_agents.py @@ -32,11 +32,8 @@ async def test_agent_registration_workflow( from agent_control_models import Agent - # Generate a proper UUID4 for the agent - agent_uuid = uuid.uuid4() - unique_name = f"Integration Test Agent {uuid.uuid4().hex[:8]}" + unique_name = f"agent-{uuid.uuid4().hex[:12]}" agent = Agent( - agent_id=agent_uuid, agent_name=unique_name, agent_description="Testing agent registration", agent_created_at=datetime.now(UTC).isoformat(), @@ -80,10 +77,10 @@ async def test_agent_retrieval_workflow( - Response includes agent metadata - Response includes registered steps """ - agent_id = test_agent["agent_id"] + agent_name = test_agent["agent_name"] # Retrieve agent - agent_data = await agent_control.agents.get_agent(client, agent_id) + agent_data = await agent_control.agents.get_agent(client, agent_name) # Verify response structure assert "agent" in agent_data @@ -91,7 +88,7 @@ async def test_agent_retrieval_workflow( # Verify agent metadata agent = agent_data["agent"] - assert agent["agent_id"] == agent_id + assert agent["agent_name"] == agent_name assert agent["agent_name"] is not None assert "agent_description" in agent @@ -148,14 +145,14 @@ async def test_convenience_get_agent_function( - Convenience function works without manual client management - Returns same data as client-based approach """ - agent_id = test_agent["agent_id"] + agent_name = test_agent["agent_name"] # Use convenience function - agent_data = await agent_control.get_agent(agent_id, server_url=server_url, api_key=api_key) + agent_data = await agent_control.get_agent(agent_name, server_url=server_url, api_key=api_key) # Verify response assert "agent" in agent_data - assert agent_data["agent"]["agent_id"] == agent_id + assert agent_data["agent"]["agent_name"] == agent_name print("✓ Convenience function works") @@ -177,8 +174,7 @@ async def test_init_function_workflow( """ # Initialize agent agent = agent_control.init( - agent_name=f"Init Test Agent {test_agent_id}", - agent_id=test_agent_id, + agent_name=test_agent_id, agent_description="Testing init function", agent_version="1.0.0", server_url=server_url, @@ -189,8 +185,8 @@ async def test_init_function_workflow( # Verify agent instance assert agent is not None - assert agent.agent_name == f"Init Test Agent {test_agent_id}" - assert hasattr(agent, "agent_id") + assert agent.agent_name == test_agent_id + assert hasattr(agent, "agent_name") # Verify current_agent() current = agent_control.current_agent() diff --git a/sdks/python/tests/test_local_evaluation.py b/sdks/python/tests/test_local_evaluation.py index 0afc907c..550999ae 100644 --- a/sdks/python/tests/test_local_evaluation.py +++ b/sdks/python/tests/test_local_evaluation.py @@ -10,7 +10,6 @@ from typing import Any from unittest.mock import AsyncMock, MagicMock -from uuid import UUID import pytest @@ -35,9 +34,9 @@ @pytest.fixture -def agent_uuid() -> UUID: - """Test agent UUID.""" - return UUID("00000000-0000-0000-0000-000000000001") +def agent_name() -> str: + """Test agent name.""" + return "agent-000000000001" @pytest.fixture @@ -201,7 +200,7 @@ class TestCheckEvaluationWithLocal: """Tests for check_evaluation_with_local function.""" @pytest.mark.asyncio - async def test_local_only_controls_no_server_call(self, agent_uuid, llm_payload): + async def test_local_only_controls_no_server_call(self, agent_name, llm_payload): """When only local controls exist, server should not be called.""" controls = [ make_control_dict(1, "local_ctrl", execution="sdk", pattern=r"never_match"), @@ -214,7 +213,7 @@ async def test_local_only_controls_no_server_call(self, agent_uuid, llm_payload) result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -227,7 +226,7 @@ async def test_local_only_controls_no_server_call(self, agent_uuid, llm_payload) assert result.is_safe is True @pytest.mark.asyncio - async def test_server_only_controls_calls_server(self, agent_uuid, llm_payload): + async def test_server_only_controls_calls_server(self, agent_name, llm_payload): """When only server controls exist, server should be called.""" controls = [ make_control_dict(1, "server_ctrl", execution="server"), @@ -243,7 +242,7 @@ async def test_server_only_controls_calls_server(self, agent_uuid, llm_payload): result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -256,7 +255,7 @@ async def test_server_only_controls_calls_server(self, agent_uuid, llm_payload): assert result.is_safe is True @pytest.mark.asyncio - async def test_local_deny_short_circuits(self, agent_uuid, llm_payload): + async def test_local_deny_short_circuits(self, agent_name, llm_payload): """Local deny should return immediately without calling server.""" controls = [ # Local control that will match (deny) @@ -272,7 +271,7 @@ async def test_local_deny_short_circuits(self, agent_uuid, llm_payload): result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -288,7 +287,7 @@ async def test_local_deny_short_circuits(self, agent_uuid, llm_payload): assert result.matches[0].control_name == "local_deny" @pytest.mark.asyncio - async def test_mixed_controls_local_passes_then_server(self, agent_uuid, llm_payload): + async def test_mixed_controls_local_passes_then_server(self, agent_name, llm_payload): """When local controls pass, server controls should still be called.""" controls = [ # Local control that won't match @@ -307,7 +306,7 @@ async def test_mixed_controls_local_passes_then_server(self, agent_uuid, llm_pay result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -320,7 +319,7 @@ async def test_mixed_controls_local_passes_then_server(self, agent_uuid, llm_pay assert result.is_safe is True @pytest.mark.asyncio - async def test_no_controls_returns_safe(self, agent_uuid, llm_payload): + async def test_no_controls_returns_safe(self, agent_name, llm_payload): """When no controls exist, result should be safe.""" controls: list[dict[str, Any]] = [] @@ -330,7 +329,7 @@ async def test_no_controls_returns_safe(self, agent_uuid, llm_payload): result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -344,7 +343,7 @@ async def test_no_controls_returns_safe(self, agent_uuid, llm_payload): assert result.confidence == 1.0 @pytest.mark.asyncio - async def test_invalid_local_control_skipped(self, agent_uuid, llm_payload): + async def test_invalid_local_control_skipped(self, agent_name, llm_payload): """Invalid local controls should be skipped.""" controls = [ # Invalid control (missing required fields) @@ -364,7 +363,7 @@ async def test_invalid_local_control_skipped(self, agent_uuid, llm_payload): # Should not raise, just skip invalid control result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -375,7 +374,7 @@ async def test_invalid_local_control_skipped(self, agent_uuid, llm_payload): assert result.is_safe is True @pytest.mark.asyncio - async def test_tool_step_local_evaluation(self, agent_uuid, tool_payload): + async def test_tool_step_local_evaluation(self, agent_name, tool_payload): """Local evaluation should work with Step payloads.""" controls = [ make_control_dict( @@ -393,7 +392,7 @@ async def test_tool_step_local_evaluation(self, agent_uuid, tool_payload): result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=tool_payload, stage="pre", controls=controls, @@ -406,7 +405,7 @@ async def test_tool_step_local_evaluation(self, agent_uuid, tool_payload): assert result.is_safe is False @pytest.mark.asyncio - async def test_mixed_controls_merged_results(self, agent_uuid, llm_payload): + async def test_mixed_controls_merged_results(self, agent_name, llm_payload): """Results from local and server should be merged.""" controls = [ # Local control (action=log, will match but not deny) @@ -435,7 +434,7 @@ async def test_mixed_controls_merged_results(self, agent_uuid, llm_payload): result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -450,7 +449,7 @@ async def test_mixed_controls_merged_results(self, agent_uuid, llm_payload): assert len(result.matches) == 2 @pytest.mark.asyncio - async def test_tool_step_mixed_local_and_server_controls(self, agent_uuid, tool_payload): + async def test_tool_step_mixed_local_and_server_controls(self, agent_name, tool_payload): """Test mixed local/server controls for same tool step. Given: A tool step with both local and server controls @@ -498,7 +497,7 @@ async def test_tool_step_mixed_local_and_server_controls(self, agent_uuid, tool_ result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=tool_payload, stage="pre", controls=controls, @@ -514,7 +513,7 @@ async def test_tool_step_mixed_local_and_server_controls(self, agent_uuid, tool_ assert result.matches[0].control_name == "server_tool_ctrl" @pytest.mark.asyncio - async def test_tool_step_local_deny_skips_server(self, agent_uuid, tool_payload): + async def test_tool_step_local_deny_skips_server(self, agent_name, tool_payload): """Test that local deny on tool step short-circuits server call. Given: A tool step with local deny control that matches @@ -548,7 +547,7 @@ async def test_tool_step_local_deny_skips_server(self, agent_uuid, tool_payload) result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=tool_payload, stage="pre", controls=controls, @@ -564,7 +563,7 @@ async def test_tool_step_local_deny_skips_server(self, agent_uuid, tool_payload) assert result.matches[0].control_name == "local_deny_ctrl" @pytest.mark.asyncio - async def test_local_control_with_missing_evaluator_raises(self, agent_uuid, llm_payload): + async def test_local_control_with_missing_evaluator_raises(self, agent_name, llm_payload): """Test that local control with unavailable evaluator raises RuntimeError. Given: A local control referencing an evaluator that doesn't exist @@ -587,7 +586,7 @@ async def test_local_control_with_missing_evaluator_raises(self, agent_uuid, llm with pytest.raises(RuntimeError) as exc_info: await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -598,7 +597,7 @@ async def test_local_control_with_missing_evaluator_raises(self, agent_uuid, llm assert "not available" in str(exc_info.value) @pytest.mark.asyncio - async def test_local_control_with_agent_scoped_evaluator_raises(self, agent_uuid, llm_payload): + async def test_local_control_with_agent_scoped_evaluator_raises(self, agent_name, llm_payload): """Test that local control with agent-scoped evaluator raises RuntimeError. Given: A local control referencing an agent-scoped evaluator (agent:evaluator) @@ -621,7 +620,7 @@ async def test_local_control_with_agent_scoped_evaluator_raises(self, agent_uuid with pytest.raises(RuntimeError) as exc_info: await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -632,7 +631,7 @@ async def test_local_control_with_agent_scoped_evaluator_raises(self, agent_uuid assert "server-only" in str(exc_info.value) @pytest.mark.asyncio - async def test_server_control_with_missing_evaluator_allowed(self, agent_uuid, llm_payload): + async def test_server_control_with_missing_evaluator_allowed(self, agent_name, llm_payload): """Test that server control with unavailable evaluator is allowed (server handles it). Given: A server control (execution="server") referencing an evaluator that doesn't exist locally @@ -660,7 +659,7 @@ async def test_server_control_with_missing_evaluator_allowed(self, agent_uuid, l # Should not raise - server handles unavailable evaluators result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -671,7 +670,7 @@ async def test_server_control_with_missing_evaluator_allowed(self, agent_uuid, l assert result.is_safe is True @pytest.mark.asyncio - async def test_invalid_local_control_populates_errors(self, agent_uuid, llm_payload): + async def test_invalid_local_control_populates_errors(self, agent_name, llm_payload): """Test that invalid local controls appear in result.errors. Given: A local control that fails validation @@ -689,7 +688,7 @@ async def test_invalid_local_control_populates_errors(self, agent_uuid, llm_payl result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, @@ -708,7 +707,7 @@ async def test_invalid_local_control_populates_errors(self, agent_uuid, llm_payl assert "Failed to parse local control" in (result.errors[0].result.error or "") @pytest.mark.asyncio - async def test_malformed_server_control_still_calls_server(self, agent_uuid, llm_payload): + async def test_malformed_server_control_still_calls_server(self, agent_name, llm_payload): """Test that malformed server control data still triggers server call. Given: A server control (execution="server") with missing/malformed 'control' data @@ -736,7 +735,7 @@ async def test_malformed_server_control_still_calls_server(self, agent_uuid, llm result = await check_evaluation_with_local( client=client, - agent_uuid=agent_uuid, + agent_name=agent_name, step=llm_payload, stage="pre", controls=controls, diff --git a/sdks/python/tests/test_observability.py b/sdks/python/tests/test_observability.py index f02f1437..f983afea 100644 --- a/sdks/python/tests/test_observability.py +++ b/sdks/python/tests/test_observability.py @@ -26,7 +26,7 @@ def create_mock_event(): mock_event.model_dump = MagicMock(return_value={ "trace_id": "a" * 32, "span_id": "b" * 16, - "agent_uuid": str(uuid4()), + "agent_name": str(uuid4()), "agent_name": "test-agent", "control_id": 1, "control_name": "test-control", diff --git a/sdks/python/tests/test_observability_updates.py b/sdks/python/tests/test_observability_updates.py index 3dc33208..a99d6b7a 100644 --- a/sdks/python/tests/test_observability_updates.py +++ b/sdks/python/tests/test_observability_updates.py @@ -146,7 +146,7 @@ def _make_request(self, step_type="llm"): # Tool steps require object input, LLM steps accept string step_input = {"query": "hello"} if step_type == "tool" else "hello" return EvaluationRequest( - agent_uuid=UUID("00000000-0000-0000-0000-000000000001"), + agent_name="agent-000000000001", step={"type": step_type, "name": "test-step", "input": step_input}, stage="pre", ) @@ -262,8 +262,8 @@ def test_fallback_warning_logged_only_once(self): with patch("agent_control.evaluation.is_observability_enabled", return_value=True), \ patch("agent_control.evaluation.add_event"), \ patch("agent_control.evaluation._logger") as mock_logger: - _emit_local_events(response, request, [ctrl], None, None, "a") - _emit_local_events(response, request, [ctrl], None, None, "a") + _emit_local_events(response, request, [ctrl], None, None, "agent-test-a1") + _emit_local_events(response, request, [ctrl], None, None, "agent-test-a1") assert mock_logger.warning.call_count == 1 @@ -323,13 +323,13 @@ async def test_emits_events_when_trace_context_provided(self): patch("agent_control.evaluation._emit_local_events") as mock_emit: result = await evaluation.check_evaluation_with_local( client=client, - agent_uuid=UUID("00000000-0000-0000-0000-000000000001"), + agent_name="agent-000000000001", step=step, stage="pre", controls=controls, trace_id="abc123", span_id="def456", - agent_name="test-agent", + event_agent_name="test-agent", ) mock_emit.assert_called_once() @@ -337,7 +337,7 @@ async def test_emits_events_when_trace_context_provided(self): assert call_args[0][2] is not None # local_controls assert call_args[0][3] == "abc123" # trace_id assert call_args[0][4] == "def456" # span_id - assert call_args[0][5] == "test-agent" # agent_name + assert call_args.kwargs["agent_name"] == "test-agent" # Also verify non_matches propagated assert result.non_matches is not None @@ -375,7 +375,7 @@ async def test_emits_events_without_trace_context(self): patch("agent_control.evaluation._emit_local_events") as mock_emit: await evaluation.check_evaluation_with_local( client=client, - agent_uuid=UUID("00000000-0000-0000-0000-000000000001"), + agent_name="agent-000000000001", step=step, stage="pre", controls=controls, @@ -421,7 +421,7 @@ async def test_forwards_trace_headers_to_server(self): with patch("agent_control.evaluation.list_evaluators", return_value=["regex"]): await evaluation.check_evaluation_with_local( client=client, - agent_uuid=UUID("00000000-0000-0000-0000-000000000001"), + agent_name="agent-000000000001", step=step, stage="pre", controls=controls, @@ -472,7 +472,6 @@ async def test_non_matches_populated_in_stats(self): } ctx = ControlContext( - agent_uuid="test-uuid", agent_name="test-agent", server_url="http://localhost:8000", func=lambda: None, diff --git a/sdks/python/tests/test_validation.py b/sdks/python/tests/test_validation.py index 1564f78b..04eb2398 100644 --- a/sdks/python/tests/test_validation.py +++ b/sdks/python/tests/test_validation.py @@ -1,42 +1,24 @@ """Unit tests for SDK validation helpers.""" -from uuid import UUID, uuid4 - import pytest -from agent_control.validation import ensure_uuid, ensure_uuid_str - - -def test_ensure_uuid_accepts_uuid_instance() -> None: - value = uuid4() - assert ensure_uuid(value) == value - - -def test_ensure_uuid_accepts_uuid_string() -> None: - value = uuid4() - assert ensure_uuid(str(value)) == value - - -def test_ensure_uuid_rejects_invalid_value() -> None: - with pytest.raises(ValueError, match="agent_id must be a valid UUID string"): - ensure_uuid("not-a-uuid") +from agent_control.validation import ensure_agent_name -def test_ensure_uuid_respects_field_name() -> None: - with pytest.raises(ValueError, match="agent_uuid must be a valid UUID string"): - ensure_uuid("not-a-uuid", field_name="agent_uuid") +def test_ensure_agent_name_normalizes_to_lowercase() -> None: + assert ensure_agent_name("Agent-Name_123") == "agent-name_123" -def test_ensure_uuid_str_returns_string() -> None: - value = uuid4() - assert ensure_uuid_str(value) == str(value) +def test_ensure_agent_name_rejects_too_short() -> None: + with pytest.raises(ValueError, match="at least 10 characters"): + ensure_agent_name("short") -def test_ensure_uuid_str_accepts_uuid_string() -> None: - value = str(uuid4()) - assert ensure_uuid_str(value) == str(UUID(value)) +def test_ensure_agent_name_rejects_invalid_characters() -> None: + with pytest.raises(ValueError, match="may only contain"): + ensure_agent_name("agent name with spaces") -def test_ensure_uuid_str_rejects_invalid_value() -> None: - with pytest.raises(ValueError, match="agent_id must be a valid UUID string"): - ensure_uuid_str("not-a-uuid") +def test_ensure_agent_name_respects_field_name() -> None: + with pytest.raises(ValueError, match="custom_field must be at least 10 characters"): + ensure_agent_name("small", field_name="custom_field") diff --git a/sdks/typescript/overlays/method-names.overlay.yaml b/sdks/typescript/overlays/method-names.overlay.yaml index 3fd730c7..fd4927f7 100644 --- a/sdks/typescript/overlays/method-names.overlay.yaml +++ b/sdks/typescript/overlays/method-names.overlay.yaml @@ -15,42 +15,42 @@ actions: x-speakeasy-group: agents x-speakeasy-name-override: init - - target: $["paths"]["/api/v1/agents/{agent_id}"]["get"] + - target: $["paths"]["/api/v1/agents/{agent_name}"]["get"] update: x-speakeasy-group: agents x-speakeasy-name-override: get - - target: $["paths"]["/api/v1/agents/{agent_id}"]["patch"] + - target: $["paths"]["/api/v1/agents/{agent_name}"]["patch"] update: x-speakeasy-group: agents x-speakeasy-name-override: update - - target: $["paths"]["/api/v1/agents/{agent_id}/controls"]["get"] + - target: $["paths"]["/api/v1/agents/{agent_name}/controls"]["get"] update: x-speakeasy-group: agents x-speakeasy-name-override: listControls - - target: $["paths"]["/api/v1/agents/{agent_id}/evaluators"]["get"] + - target: $["paths"]["/api/v1/agents/{agent_name}/evaluators"]["get"] update: x-speakeasy-group: agents x-speakeasy-name-override: listEvaluators - - target: $["paths"]["/api/v1/agents/{agent_id}/evaluators/{evaluator_name}"]["get"] + - target: $["paths"]["/api/v1/agents/{agent_name}/evaluators/{evaluator_name}"]["get"] update: x-speakeasy-group: agents x-speakeasy-name-override: getEvaluator - - target: $["paths"]["/api/v1/agents/{agent_id}/policy"]["get"] + - target: $["paths"]["/api/v1/agents/{agent_name}/policy"]["get"] update: x-speakeasy-group: agents x-speakeasy-name-override: getPolicy - - target: $["paths"]["/api/v1/agents/{agent_id}/policy"]["delete"] + - target: $["paths"]["/api/v1/agents/{agent_name}/policy"]["delete"] update: x-speakeasy-group: agents x-speakeasy-name-override: deletePolicy - - target: $["paths"]["/api/v1/agents/{agent_id}/policy/{policy_id}"]["post"] + - target: $["paths"]["/api/v1/agents/{agent_name}/policy/{policy_id}"]["post"] update: x-speakeasy-group: agents x-speakeasy-name-override: updatePolicy diff --git a/sdks/typescript/src/client.ts b/sdks/typescript/src/client.ts index 2836f23f..f9cd94d5 100644 --- a/sdks/typescript/src/client.ts +++ b/sdks/typescript/src/client.ts @@ -9,7 +9,6 @@ export type APIKeyProvider = string | (() => Promise); export interface AgentControlInitOptions { agentName: string; - agentId?: string; serverUrl: string; apiKey?: APIKeyProvider; steps?: StepSchema[]; diff --git a/sdks/typescript/src/generated/funcs/agents-delete-policy.ts b/sdks/typescript/src/generated/funcs/agents-delete-policy.ts index 9251bfd9..2e8a18b6 100644 --- a/sdks/typescript/src/generated/funcs/agents-delete-policy.ts +++ b/sdks/typescript/src/generated/funcs/agents-delete-policy.ts @@ -36,7 +36,7 @@ import { Result } from "../types/fp.js"; * The agent will no longer have any protection controls active. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * db: Database session (injected) * * Returns: @@ -48,7 +48,7 @@ import { Result } from "../types/fp.js"; */ export function agentsDeletePolicy( client: AgentControlSDKCore, - request: operations.DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest, + request: operations.DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest, options?: RequestOptions, ): APIPromise< Result< @@ -73,7 +73,7 @@ export function agentsDeletePolicy( async function $do( client: AgentControlSDKCore, - request: operations.DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest, + request: operations.DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest, options?: RequestOptions, ): Promise< [ @@ -97,7 +97,7 @@ async function $do( (value) => z.parse( operations - .DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest$outboundSchema, + .DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest$outboundSchema, value, ), "Input validation failed", @@ -109,13 +109,13 @@ async function $do( const body = null; const pathParams = { - agent_id: encodeSimple("agent_id", payload.agent_id, { + agent_name: encodeSimple("agent_name", payload.agent_name, { explode: false, charEncoding: "percent", }), }; - const path = pathToFunc("/api/v1/agents/{agent_id}/policy")(pathParams); + const path = pathToFunc("/api/v1/agents/{agent_name}/policy")(pathParams); const headers = new Headers(compactMap({ Accept: "application/json", @@ -128,7 +128,7 @@ async function $do( const context = { options: client._options, baseURL: options?.serverURL ?? client._baseURL ?? "", - operationID: "delete_agent_policy_api_v1_agents__agent_id__policy_delete", + operationID: "delete_agent_policy_api_v1_agents__agent_name__policy_delete", oAuth2Scopes: null, resolvedSecurity: requestSecurity, diff --git a/sdks/typescript/src/generated/funcs/agents-get-evaluator.ts b/sdks/typescript/src/generated/funcs/agents-get-evaluator.ts index ecfff7ae..5404195d 100644 --- a/sdks/typescript/src/generated/funcs/agents-get-evaluator.ts +++ b/sdks/typescript/src/generated/funcs/agents-get-evaluator.ts @@ -34,7 +34,7 @@ import { Result } from "../types/fp.js"; * Get a specific evaluator schema registered with an agent. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * evaluator_name: Name of the evaluator * db: Database session (injected) * @@ -47,7 +47,7 @@ import { Result } from "../types/fp.js"; export function agentsGetEvaluator( client: AgentControlSDKCore, request: - operations.GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest, + operations.GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest, options?: RequestOptions, ): APIPromise< Result< @@ -73,7 +73,7 @@ export function agentsGetEvaluator( async function $do( client: AgentControlSDKCore, request: - operations.GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest, + operations.GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest, options?: RequestOptions, ): Promise< [ @@ -97,7 +97,7 @@ async function $do( (value) => z.parse( operations - .GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest$outboundSchema, + .GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest$outboundSchema, value, ), "Input validation failed", @@ -109,7 +109,7 @@ async function $do( const body = null; const pathParams = { - agent_id: encodeSimple("agent_id", payload.agent_id, { + agent_name: encodeSimple("agent_name", payload.agent_name, { explode: false, charEncoding: "percent", }), @@ -120,7 +120,7 @@ async function $do( }; const path = pathToFunc( - "/api/v1/agents/{agent_id}/evaluators/{evaluator_name}", + "/api/v1/agents/{agent_name}/evaluators/{evaluator_name}", )(pathParams); const headers = new Headers(compactMap({ @@ -135,7 +135,7 @@ async function $do( options: client._options, baseURL: options?.serverURL ?? client._baseURL ?? "", operationID: - "get_agent_evaluator_api_v1_agents__agent_id__evaluators__evaluator_name__get", + "get_agent_evaluator_api_v1_agents__agent_name__evaluators__evaluator_name__get", oAuth2Scopes: null, resolvedSecurity: requestSecurity, diff --git a/sdks/typescript/src/generated/funcs/agents-get-policy.ts b/sdks/typescript/src/generated/funcs/agents-get-policy.ts index ca3c7a92..cbe953f3 100644 --- a/sdks/typescript/src/generated/funcs/agents-get-policy.ts +++ b/sdks/typescript/src/generated/funcs/agents-get-policy.ts @@ -34,7 +34,7 @@ import { Result } from "../types/fp.js"; * Retrieve the policy currently assigned to an agent. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * db: Database session (injected) * * Returns: @@ -45,7 +45,7 @@ import { Result } from "../types/fp.js"; */ export function agentsGetPolicy( client: AgentControlSDKCore, - request: operations.GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest, + request: operations.GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest, options?: RequestOptions, ): APIPromise< Result< @@ -70,7 +70,7 @@ export function agentsGetPolicy( async function $do( client: AgentControlSDKCore, - request: operations.GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest, + request: operations.GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest, options?: RequestOptions, ): Promise< [ @@ -94,7 +94,7 @@ async function $do( (value) => z.parse( operations - .GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest$outboundSchema, + .GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest$outboundSchema, value, ), "Input validation failed", @@ -106,13 +106,13 @@ async function $do( const body = null; const pathParams = { - agent_id: encodeSimple("agent_id", payload.agent_id, { + agent_name: encodeSimple("agent_name", payload.agent_name, { explode: false, charEncoding: "percent", }), }; - const path = pathToFunc("/api/v1/agents/{agent_id}/policy")(pathParams); + const path = pathToFunc("/api/v1/agents/{agent_name}/policy")(pathParams); const headers = new Headers(compactMap({ Accept: "application/json", @@ -125,7 +125,7 @@ async function $do( const context = { options: client._options, baseURL: options?.serverURL ?? client._baseURL ?? "", - operationID: "get_agent_policy_api_v1_agents__agent_id__policy_get", + operationID: "get_agent_policy_api_v1_agents__agent_name__policy_get", oAuth2Scopes: null, resolvedSecurity: requestSecurity, diff --git a/sdks/typescript/src/generated/funcs/agents-get.ts b/sdks/typescript/src/generated/funcs/agents-get.ts index e054ae4b..e650726a 100644 --- a/sdks/typescript/src/generated/funcs/agents-get.ts +++ b/sdks/typescript/src/generated/funcs/agents-get.ts @@ -36,7 +36,7 @@ import { Result } from "../types/fp.js"; * Returns the latest version of each step (deduplicated by type+name). * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * db: Database session (injected) * * Returns: @@ -48,7 +48,7 @@ import { Result } from "../types/fp.js"; */ export function agentsGet( client: AgentControlSDKCore, - request: operations.GetAgentApiV1AgentsAgentIdGetRequest, + request: operations.GetAgentApiV1AgentsAgentNameGetRequest, options?: RequestOptions, ): APIPromise< Result< @@ -73,7 +73,7 @@ export function agentsGet( async function $do( client: AgentControlSDKCore, - request: operations.GetAgentApiV1AgentsAgentIdGetRequest, + request: operations.GetAgentApiV1AgentsAgentNameGetRequest, options?: RequestOptions, ): Promise< [ @@ -96,7 +96,7 @@ async function $do( request, (value) => z.parse( - operations.GetAgentApiV1AgentsAgentIdGetRequest$outboundSchema, + operations.GetAgentApiV1AgentsAgentNameGetRequest$outboundSchema, value, ), "Input validation failed", @@ -108,13 +108,13 @@ async function $do( const body = null; const pathParams = { - agent_id: encodeSimple("agent_id", payload.agent_id, { + agent_name: encodeSimple("agent_name", payload.agent_name, { explode: false, charEncoding: "percent", }), }; - const path = pathToFunc("/api/v1/agents/{agent_id}")(pathParams); + const path = pathToFunc("/api/v1/agents/{agent_name}")(pathParams); const headers = new Headers(compactMap({ Accept: "application/json", @@ -127,7 +127,7 @@ async function $do( const context = { options: client._options, baseURL: options?.serverURL ?? client._baseURL ?? "", - operationID: "get_agent_api_v1_agents__agent_id__get", + operationID: "get_agent_api_v1_agents__agent_name__get", oAuth2Scopes: null, resolvedSecurity: requestSecurity, diff --git a/sdks/typescript/src/generated/funcs/agents-init.ts b/sdks/typescript/src/generated/funcs/agents-init.ts index 9937275c..1cb02298 100644 --- a/sdks/typescript/src/generated/funcs/agents-init.ts +++ b/sdks/typescript/src/generated/funcs/agents-init.ts @@ -34,9 +34,7 @@ import { Result } from "../types/fp.js"; * * This endpoint is idempotent: * - If the agent name doesn't exist, creates a new agent - * - If the agent name exists with the same UUID, updates registration data - * - If the agent name exists with a different UUID, returns 409 Conflict - * - If the UUID exists with a different name, returns 409 Conflict (no renames) + * - If the agent name exists, updates registration data in place * * conflict_mode controls registration conflict handling: * - strict (default): preserve compatibility checks and conflict errors @@ -48,10 +46,6 @@ import { Result } from "../types/fp.js"; * * Returns: * InitAgentResponse with created flag and active controls (if policy assigned) - * - * Raises: - * HTTPException 409: Agent name exists with different UUID - * HTTPException 500: Database error during creation/update */ export function agentsInit( client: AgentControlSDKCore, diff --git a/sdks/typescript/src/generated/funcs/agents-list-controls.ts b/sdks/typescript/src/generated/funcs/agents-list-controls.ts index 764e550c..b44c3402 100644 --- a/sdks/typescript/src/generated/funcs/agents-list-controls.ts +++ b/sdks/typescript/src/generated/funcs/agents-list-controls.ts @@ -37,7 +37,7 @@ import { Result } from "../types/fp.js"; * Returns an empty list if the agent has no policy. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * db: Database session (injected) * * Returns: @@ -48,7 +48,7 @@ import { Result } from "../types/fp.js"; */ export function agentsListControls( client: AgentControlSDKCore, - request: operations.ListAgentControlsApiV1AgentsAgentIdControlsGetRequest, + request: operations.ListAgentControlsApiV1AgentsAgentNameControlsGetRequest, options?: RequestOptions, ): APIPromise< Result< @@ -73,7 +73,7 @@ export function agentsListControls( async function $do( client: AgentControlSDKCore, - request: operations.ListAgentControlsApiV1AgentsAgentIdControlsGetRequest, + request: operations.ListAgentControlsApiV1AgentsAgentNameControlsGetRequest, options?: RequestOptions, ): Promise< [ @@ -97,7 +97,7 @@ async function $do( (value) => z.parse( operations - .ListAgentControlsApiV1AgentsAgentIdControlsGetRequest$outboundSchema, + .ListAgentControlsApiV1AgentsAgentNameControlsGetRequest$outboundSchema, value, ), "Input validation failed", @@ -109,13 +109,13 @@ async function $do( const body = null; const pathParams = { - agent_id: encodeSimple("agent_id", payload.agent_id, { + agent_name: encodeSimple("agent_name", payload.agent_name, { explode: false, charEncoding: "percent", }), }; - const path = pathToFunc("/api/v1/agents/{agent_id}/controls")(pathParams); + const path = pathToFunc("/api/v1/agents/{agent_name}/controls")(pathParams); const headers = new Headers(compactMap({ Accept: "application/json", @@ -128,7 +128,7 @@ async function $do( const context = { options: client._options, baseURL: options?.serverURL ?? client._baseURL ?? "", - operationID: "list_agent_controls_api_v1_agents__agent_id__controls_get", + operationID: "list_agent_controls_api_v1_agents__agent_name__controls_get", oAuth2Scopes: null, resolvedSecurity: requestSecurity, diff --git a/sdks/typescript/src/generated/funcs/agents-list-evaluators.ts b/sdks/typescript/src/generated/funcs/agents-list-evaluators.ts index fd1de814..bded3726 100644 --- a/sdks/typescript/src/generated/funcs/agents-list-evaluators.ts +++ b/sdks/typescript/src/generated/funcs/agents-list-evaluators.ts @@ -38,7 +38,7 @@ import { Result } from "../types/fp.js"; * - UI to display available config options * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * cursor: Optional cursor for pagination (name of last evaluator from previous page) * limit: Pagination limit (default 20, max 100) * db: Database session (injected) @@ -51,7 +51,8 @@ import { Result } from "../types/fp.js"; */ export function agentsListEvaluators( client: AgentControlSDKCore, - request: operations.ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest, + request: + operations.ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest, options?: RequestOptions, ): APIPromise< Result< @@ -76,7 +77,8 @@ export function agentsListEvaluators( async function $do( client: AgentControlSDKCore, - request: operations.ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest, + request: + operations.ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest, options?: RequestOptions, ): Promise< [ @@ -100,7 +102,7 @@ async function $do( (value) => z.parse( operations - .ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest$outboundSchema, + .ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest$outboundSchema, value, ), "Input validation failed", @@ -112,13 +114,13 @@ async function $do( const body = null; const pathParams = { - agent_id: encodeSimple("agent_id", payload.agent_id, { + agent_name: encodeSimple("agent_name", payload.agent_name, { explode: false, charEncoding: "percent", }), }; - const path = pathToFunc("/api/v1/agents/{agent_id}/evaluators")(pathParams); + const path = pathToFunc("/api/v1/agents/{agent_name}/evaluators")(pathParams); const query = encodeFormQuery({ "cursor": payload.cursor, @@ -137,7 +139,7 @@ async function $do( options: client._options, baseURL: options?.serverURL ?? client._baseURL ?? "", operationID: - "list_agent_evaluators_api_v1_agents__agent_id__evaluators_get", + "list_agent_evaluators_api_v1_agents__agent_name__evaluators_get", oAuth2Scopes: null, resolvedSecurity: requestSecurity, diff --git a/sdks/typescript/src/generated/funcs/agents-list.ts b/sdks/typescript/src/generated/funcs/agents-list.ts index 65d12ac0..82e21470 100644 --- a/sdks/typescript/src/generated/funcs/agents-list.ts +++ b/sdks/typescript/src/generated/funcs/agents-list.ts @@ -33,11 +33,11 @@ import { Result } from "../types/fp.js"; * @remarks * List all registered agents with cursor-based pagination. * - * Returns a summary of each agent including ID, name, policy assignment, + * Returns a summary of each agent including identifier, policy assignment, * and counts of registered steps and evaluators. * * Args: - * cursor: Optional cursor for pagination (UUID of last agent from previous page) + * cursor: Optional cursor for pagination (last agent name from previous page) * limit: Pagination limit (default 20, max 100) * name: Optional name filter (case-insensitive partial match) * db: Database session (injected) diff --git a/sdks/typescript/src/generated/funcs/agents-update-policy.ts b/sdks/typescript/src/generated/funcs/agents-update-policy.ts index ef8fbc68..7626a798 100644 --- a/sdks/typescript/src/generated/funcs/agents-update-policy.ts +++ b/sdks/typescript/src/generated/funcs/agents-update-policy.ts @@ -36,7 +36,7 @@ import { Result } from "../types/fp.js"; * The agent will immediately inherit all controls from the assigned policy. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * policy_id: ID of the policy to assign * db: Database session (injected) * @@ -49,7 +49,8 @@ import { Result } from "../types/fp.js"; */ export function agentsUpdatePolicy( client: AgentControlSDKCore, - request: operations.SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest, + request: + operations.SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest, options?: RequestOptions, ): APIPromise< Result< @@ -74,7 +75,8 @@ export function agentsUpdatePolicy( async function $do( client: AgentControlSDKCore, - request: operations.SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest, + request: + operations.SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest, options?: RequestOptions, ): Promise< [ @@ -98,7 +100,7 @@ async function $do( (value) => z.parse( operations - .SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest$outboundSchema, + .SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest$outboundSchema, value, ), "Input validation failed", @@ -110,7 +112,7 @@ async function $do( const body = null; const pathParams = { - agent_id: encodeSimple("agent_id", payload.agent_id, { + agent_name: encodeSimple("agent_name", payload.agent_name, { explode: false, charEncoding: "percent", }), @@ -120,7 +122,7 @@ async function $do( }), }; - const path = pathToFunc("/api/v1/agents/{agent_id}/policy/{policy_id}")( + const path = pathToFunc("/api/v1/agents/{agent_name}/policy/{policy_id}")( pathParams, ); @@ -136,7 +138,7 @@ async function $do( options: client._options, baseURL: options?.serverURL ?? client._baseURL ?? "", operationID: - "set_agent_policy_api_v1_agents__agent_id__policy__policy_id__post", + "set_agent_policy_api_v1_agents__agent_name__policy__policy_id__post", oAuth2Scopes: null, resolvedSecurity: requestSecurity, diff --git a/sdks/typescript/src/generated/funcs/agents-update.ts b/sdks/typescript/src/generated/funcs/agents-update.ts index 1b5aed31..e82644cf 100644 --- a/sdks/typescript/src/generated/funcs/agents-update.ts +++ b/sdks/typescript/src/generated/funcs/agents-update.ts @@ -37,7 +37,7 @@ import { Result } from "../types/fp.js"; * Removals are idempotent - attempting to remove non-existent items is not an error. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * request: Lists of step/evaluator identifiers to remove * db: Database session (injected) * @@ -50,7 +50,7 @@ import { Result } from "../types/fp.js"; */ export function agentsUpdate( client: AgentControlSDKCore, - request: operations.PatchAgentApiV1AgentsAgentIdPatchRequest, + request: operations.PatchAgentApiV1AgentsAgentNamePatchRequest, options?: RequestOptions, ): APIPromise< Result< @@ -75,7 +75,7 @@ export function agentsUpdate( async function $do( client: AgentControlSDKCore, - request: operations.PatchAgentApiV1AgentsAgentIdPatchRequest, + request: operations.PatchAgentApiV1AgentsAgentNamePatchRequest, options?: RequestOptions, ): Promise< [ @@ -98,7 +98,7 @@ async function $do( request, (value) => z.parse( - operations.PatchAgentApiV1AgentsAgentIdPatchRequest$outboundSchema, + operations.PatchAgentApiV1AgentsAgentNamePatchRequest$outboundSchema, value, ), "Input validation failed", @@ -110,13 +110,13 @@ async function $do( const body = encodeJSON("body", payload.body, { explode: true }); const pathParams = { - agent_id: encodeSimple("agent_id", payload.agent_id, { + agent_name: encodeSimple("agent_name", payload.agent_name, { explode: false, charEncoding: "percent", }), }; - const path = pathToFunc("/api/v1/agents/{agent_id}")(pathParams); + const path = pathToFunc("/api/v1/agents/{agent_name}")(pathParams); const headers = new Headers(compactMap({ "Content-Type": "application/json", @@ -130,7 +130,7 @@ async function $do( const context = { options: client._options, baseURL: options?.serverURL ?? client._baseURL ?? "", - operationID: "patch_agent_api_v1_agents__agent_id__patch", + operationID: "patch_agent_api_v1_agents__agent_name__patch", oAuth2Scopes: null, resolvedSecurity: requestSecurity, diff --git a/sdks/typescript/src/generated/funcs/evaluators-list.ts b/sdks/typescript/src/generated/funcs/evaluators-list.ts index 5a59ce8c..2f378a5f 100644 --- a/sdks/typescript/src/generated/funcs/evaluators-list.ts +++ b/sdks/typescript/src/generated/funcs/evaluators-list.ts @@ -38,7 +38,7 @@ import { Result } from "../types/fp.js"; * - **sql**: SQL query validation * * Custom evaluators are registered per-agent via initAgent. - * Use GET /agents/{agent_id}/evaluators to list agent-specific schemas. + * Use GET /agents/{agent_name}/evaluators to list agent-specific schemas. */ export function evaluatorsList( client: AgentControlSDKCore, diff --git a/sdks/typescript/src/generated/funcs/observability-get-control-stats.ts b/sdks/typescript/src/generated/funcs/observability-get-control-stats.ts index dbb0cf69..0a3b349f 100644 --- a/sdks/typescript/src/generated/funcs/observability-get-control-stats.ts +++ b/sdks/typescript/src/generated/funcs/observability-get-control-stats.ts @@ -37,7 +37,7 @@ import { Result } from "../types/fp.js"; * * Args: * control_id: Control ID to get stats for - * agent_uuid: Agent to get stats for + * agent_name: Agent to get stats for * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) * include_timeseries: Include time-series data points for trend visualization * store: Event store (injected) @@ -121,7 +121,7 @@ async function $do( ); const query = encodeFormQuery({ - "agent_uuid": payload.agent_uuid, + "agent_name": payload.agent_name, "include_timeseries": payload.include_timeseries, "time_range": payload.time_range, }); diff --git a/sdks/typescript/src/generated/funcs/observability-get-stats.ts b/sdks/typescript/src/generated/funcs/observability-get-stats.ts index f209a135..fb3fcd44 100644 --- a/sdks/typescript/src/generated/funcs/observability-get-stats.ts +++ b/sdks/typescript/src/generated/funcs/observability-get-stats.ts @@ -37,7 +37,7 @@ import { Result } from "../types/fp.js"; * Use /stats/controls/{control_id} for single control stats. * * Args: - * agent_uuid: Agent to get stats for + * agent_name: Agent to get stats for * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) * include_timeseries: Include time-series data points for trend visualization * store: Event store (injected) @@ -109,7 +109,7 @@ async function $do( const path = pathToFunc("/api/v1/observability/stats")(); const query = encodeFormQuery({ - "agent_uuid": payload.agent_uuid, + "agent_name": payload.agent_name, "include_timeseries": payload.include_timeseries, "time_range": payload.time_range, }); diff --git a/sdks/typescript/src/generated/funcs/observability-query-events.ts b/sdks/typescript/src/generated/funcs/observability-query-events.ts index 1dda335f..f61b92f0 100644 --- a/sdks/typescript/src/generated/funcs/observability-query-events.ts +++ b/sdks/typescript/src/generated/funcs/observability-query-events.ts @@ -36,7 +36,7 @@ import { Result } from "../types/fp.js"; * - trace_id: Get all events for a request * - span_id: Get all events for a function call * - control_execution_id: Get a specific event - * - agent_uuid: Filter by agent + * - agent_name: Filter by agent * - control_ids: Filter by controls * - actions: Filter by actions (allow, deny, warn, log) * - matched: Filter by matched status diff --git a/sdks/typescript/src/generated/models/agent-ref.ts b/sdks/typescript/src/generated/models/agent-ref.ts index 765408c2..e24952df 100644 --- a/sdks/typescript/src/generated/models/agent-ref.ts +++ b/sdks/typescript/src/generated/models/agent-ref.ts @@ -14,11 +14,7 @@ import { SDKValidationError } from "./errors/sdk-validation-error.js"; */ export type AgentRef = { /** - * Agent UUID - */ - agentId: string; - /** - * Agent name + * Agent identifier */ agentName: string; }; @@ -26,12 +22,10 @@ export type AgentRef = { /** @internal */ export const AgentRef$inboundSchema: z.ZodMiniType = z.pipe( z.object({ - agent_id: types.string(), agent_name: types.string(), }), z.transform((v) => { return remap$(v, { - "agent_id": "agentId", "agent_name": "agentName", }); }), diff --git a/sdks/typescript/src/generated/models/agent-summary.ts b/sdks/typescript/src/generated/models/agent-summary.ts index 9915c153..a2cccd70 100644 --- a/sdks/typescript/src/generated/models/agent-summary.ts +++ b/sdks/typescript/src/generated/models/agent-summary.ts @@ -18,11 +18,7 @@ export type AgentSummary = { */ activeControlsCount: number; /** - * UUID of the agent - */ - agentId: string; - /** - * Human-readable name of the agent + * Unique identifier of the agent */ agentName: string; /** @@ -48,7 +44,6 @@ export const AgentSummary$inboundSchema: z.ZodMiniType = z.pipe( z.object({ active_controls_count: z._default(types.number(), 0), - agent_id: types.string(), agent_name: types.string(), created_at: z.optional(z.nullable(types.string())), evaluator_count: z._default(types.number(), 0), @@ -58,7 +53,6 @@ export const AgentSummary$inboundSchema: z.ZodMiniType = z.transform((v) => { return remap$(v, { "active_controls_count": "activeControlsCount", - "agent_id": "agentId", "agent_name": "agentName", "created_at": "createdAt", "evaluator_count": "evaluatorCount", diff --git a/sdks/typescript/src/generated/models/agent.ts b/sdks/typescript/src/generated/models/agent.ts index 0eb4d3ad..3155c4d7 100644 --- a/sdks/typescript/src/generated/models/agent.ts +++ b/sdks/typescript/src/generated/models/agent.ts @@ -15,7 +15,7 @@ import { SDKValidationError } from "./errors/sdk-validation-error.js"; * @remarks * * An agent represents an AI system that can be protected and monitored. - * Each agent has a unique ID and can have multiple steps registered with it. + * Each agent has a unique immutable name and can have multiple steps registered with it. */ export type Agent = { /** @@ -26,16 +26,12 @@ export type Agent = { * Optional description of the agent's purpose */ agentDescription?: string | null | undefined; - /** - * Unique identifier for the agent (UUID format) - */ - agentId: string; /** * Free-form metadata dictionary for custom properties */ agentMetadata?: { [k: string]: any } | null | undefined; /** - * Human-readable name for the agent + * Unique immutable identifier for the agent */ agentName: string; /** @@ -53,7 +49,6 @@ export const Agent$inboundSchema: z.ZodMiniType = z.pipe( z.object({ agent_created_at: z.optional(z.nullable(types.string())), agent_description: z.optional(z.nullable(types.string())), - agent_id: types.string(), agent_metadata: z.optional(z.nullable(z.record(z.string(), z.any()))), agent_name: types.string(), agent_updated_at: z.optional(z.nullable(types.string())), @@ -63,7 +58,6 @@ export const Agent$inboundSchema: z.ZodMiniType = z.pipe( return remap$(v, { "agent_created_at": "agentCreatedAt", "agent_description": "agentDescription", - "agent_id": "agentId", "agent_metadata": "agentMetadata", "agent_name": "agentName", "agent_updated_at": "agentUpdatedAt", @@ -75,7 +69,6 @@ export const Agent$inboundSchema: z.ZodMiniType = z.pipe( export type Agent$Outbound = { agent_created_at?: string | null | undefined; agent_description?: string | null | undefined; - agent_id: string; agent_metadata?: { [k: string]: any } | null | undefined; agent_name: string; agent_updated_at?: string | null | undefined; @@ -88,7 +81,6 @@ export const Agent$outboundSchema: z.ZodMiniType = z z.object({ agentCreatedAt: z.optional(z.nullable(z.string())), agentDescription: z.optional(z.nullable(z.string())), - agentId: z.string(), agentMetadata: z.optional(z.nullable(z.record(z.string(), z.any()))), agentName: z.string(), agentUpdatedAt: z.optional(z.nullable(z.string())), @@ -98,7 +90,6 @@ export const Agent$outboundSchema: z.ZodMiniType = z return remap$(v, { agentCreatedAt: "agent_created_at", agentDescription: "agent_description", - agentId: "agent_id", agentMetadata: "agent_metadata", agentName: "agent_name", agentUpdatedAt: "agent_updated_at", diff --git a/sdks/typescript/src/generated/models/control-execution-event.ts b/sdks/typescript/src/generated/models/control-execution-event.ts index 5feddb49..c5eef756 100644 --- a/sdks/typescript/src/generated/models/control-execution-event.ts +++ b/sdks/typescript/src/generated/models/control-execution-event.ts @@ -69,8 +69,7 @@ export type CheckStage = OpenEnum; * control_execution_id: Unique ID for this specific control execution * trace_id: OpenTelemetry-compatible trace ID (128-bit hex, 32 chars) * span_id: OpenTelemetry-compatible span ID (64-bit hex, 16 chars) - * agent_uuid: UUID of the agent that executed the control - * agent_name: Name of the agent (denormalized for queries) + * agent_name: Identifier of the agent that executed the control * control_id: Database ID of the control * control_name: Name of the control (denormalized for queries) * check_stage: "pre" (before execution) or "post" (after execution) @@ -91,13 +90,9 @@ export type ControlExecutionEvent = { */ action: ControlExecutionEventAction; /** - * Name of the agent (denormalized) + * Identifier of the agent */ agentName: string; - /** - * UUID of the agent - */ - agentUuid: string; /** * Type of call: 'llm_call' or 'tool_call' */ @@ -197,7 +192,6 @@ export const ControlExecutionEvent$inboundSchema: z.ZodMiniType< z.object({ action: ControlExecutionEventAction$inboundSchema, agent_name: types.string(), - agent_uuid: types.string(), applies_to: ControlExecutionEventAppliesTo$inboundSchema, check_stage: CheckStage$inboundSchema, confidence: types.number(), @@ -217,7 +211,6 @@ export const ControlExecutionEvent$inboundSchema: z.ZodMiniType< z.transform((v) => { return remap$(v, { "agent_name": "agentName", - "agent_uuid": "agentUuid", "applies_to": "appliesTo", "check_stage": "checkStage", "control_execution_id": "controlExecutionId", @@ -236,7 +229,6 @@ export const ControlExecutionEvent$inboundSchema: z.ZodMiniType< export type ControlExecutionEvent$Outbound = { action: string; agent_name: string; - agent_uuid: string; applies_to: string; check_stage: string; confidence: number; @@ -262,7 +254,6 @@ export const ControlExecutionEvent$outboundSchema: z.ZodMiniType< z.object({ action: ControlExecutionEventAction$outboundSchema, agentName: z.string(), - agentUuid: z.string(), appliesTo: ControlExecutionEventAppliesTo$outboundSchema, checkStage: CheckStage$outboundSchema, confidence: z.number(), @@ -282,7 +273,6 @@ export const ControlExecutionEvent$outboundSchema: z.ZodMiniType< z.transform((v) => { return remap$(v, { agentName: "agent_name", - agentUuid: "agent_uuid", appliesTo: "applies_to", checkStage: "check_stage", controlExecutionId: "control_execution_id", diff --git a/sdks/typescript/src/generated/models/control-stats-response.ts b/sdks/typescript/src/generated/models/control-stats-response.ts index 619ccf44..b52c61f8 100644 --- a/sdks/typescript/src/generated/models/control-stats-response.ts +++ b/sdks/typescript/src/generated/models/control-stats-response.ts @@ -18,7 +18,7 @@ import { StatsTotals, StatsTotals$inboundSchema } from "./stats-totals.js"; * Contains stats for a single control (with optional timeseries). * * Attributes: - * agent_uuid: Agent UUID + * agent_name: Agent identifier * time_range: Time range used * control_id: Control ID * control_name: Control name @@ -26,9 +26,9 @@ import { StatsTotals, StatsTotals$inboundSchema } from "./stats-totals.js"; */ export type ControlStatsResponse = { /** - * Agent UUID + * Agent identifier */ - agentUuid: string; + agentName: string; /** * Control ID */ @@ -68,7 +68,7 @@ export const ControlStatsResponse$inboundSchema: z.ZodMiniType< unknown > = z.pipe( z.object({ - agent_uuid: types.string(), + agent_name: types.string(), control_id: types.number(), control_name: types.string(), stats: StatsTotals$inboundSchema, @@ -76,7 +76,7 @@ export const ControlStatsResponse$inboundSchema: z.ZodMiniType< }), z.transform((v) => { return remap$(v, { - "agent_uuid": "agentUuid", + "agent_name": "agentName", "control_id": "controlId", "control_name": "controlName", "time_range": "timeRange", diff --git a/sdks/typescript/src/generated/models/evaluation-request.ts b/sdks/typescript/src/generated/models/evaluation-request.ts index 2b5ec0ef..231d4c94 100644 --- a/sdks/typescript/src/generated/models/evaluation-request.ts +++ b/sdks/typescript/src/generated/models/evaluation-request.ts @@ -28,15 +28,15 @@ export type Stage = ClosedEnum; * policy compliance, and control rules. * * Attributes: - * agent_uuid: UUID of the agent making the request + * agent_name: Unique identifier of the agent making the request * step: Step payload for evaluation * stage: 'pre' (before execution) or 'post' (after execution) */ export type EvaluationRequest = { /** - * UUID of the agent making the evaluation request + * Identifier of the agent making the evaluation request */ - agentUuid: string; + agentName: string; /** * Evaluation stage: 'pre' or 'post' */ @@ -52,7 +52,7 @@ export const Stage$outboundSchema: z.ZodMiniEnum = z.enum(Stage); /** @internal */ export type EvaluationRequest$Outbound = { - agent_uuid: string; + agent_name: string; stage: string; step: Step$Outbound; }; @@ -63,13 +63,13 @@ export const EvaluationRequest$outboundSchema: z.ZodMiniType< EvaluationRequest > = z.pipe( z.object({ - agentUuid: z.string(), + agentName: z.string(), stage: Stage$outboundSchema, step: Step$outboundSchema, }), z.transform((v) => { return remap$(v, { - agentUuid: "agent_uuid", + agentName: "agent_name", }); }), ); diff --git a/sdks/typescript/src/generated/models/event-query-request.ts b/sdks/typescript/src/generated/models/event-query-request.ts index 5903d8fb..b222b642 100644 --- a/sdks/typescript/src/generated/models/event-query-request.ts +++ b/sdks/typescript/src/generated/models/event-query-request.ts @@ -37,7 +37,7 @@ export type CheckStages = ClosedEnum; * trace_id: Filter by trace ID (get all events for a request) * span_id: Filter by span ID (get all events for a function call) * control_execution_id: Filter by specific event ID - * agent_uuid: Filter by agent UUID + * agent_name: Filter by agent identifier * control_ids: Filter by control IDs * actions: Filter by actions (allow, deny, warn, log) * matched: Filter by matched status @@ -54,9 +54,9 @@ export type EventQueryRequest = { */ actions?: Array | null | undefined; /** - * Filter by agent UUID + * Filter by agent identifier */ - agentUuid?: string | null | undefined; + agentName?: string | null | undefined; /** * Filter by call types */ @@ -120,7 +120,7 @@ export const CheckStages$outboundSchema: z.ZodMiniEnum = z /** @internal */ export type EventQueryRequest$Outbound = { actions?: Array | null | undefined; - agent_uuid?: string | null | undefined; + agent_name?: string | null | undefined; applies_to?: Array | null | undefined; check_stages?: Array | null | undefined; control_execution_id?: string | null | undefined; @@ -141,7 +141,7 @@ export const EventQueryRequest$outboundSchema: z.ZodMiniType< > = z.pipe( z.object({ actions: z.optional(z.nullable(z.array(Actions$outboundSchema))), - agentUuid: z.optional(z.nullable(z.string())), + agentName: z.optional(z.nullable(z.string())), appliesTo: z.optional(z.nullable(z.array(AppliesTo$outboundSchema))), checkStages: z.optional(z.nullable(z.array(CheckStages$outboundSchema))), controlExecutionId: z.optional(z.nullable(z.string())), @@ -160,7 +160,7 @@ export const EventQueryRequest$outboundSchema: z.ZodMiniType< }), z.transform((v) => { return remap$(v, { - agentUuid: "agent_uuid", + agentName: "agent_name", appliesTo: "applies_to", checkStages: "check_stages", controlExecutionId: "control_execution_id", diff --git a/sdks/typescript/src/generated/models/get-agent-response.ts b/sdks/typescript/src/generated/models/get-agent-response.ts index b4fc8069..10eacfb3 100644 --- a/sdks/typescript/src/generated/models/get-agent-response.ts +++ b/sdks/typescript/src/generated/models/get-agent-response.ts @@ -24,7 +24,7 @@ export type GetAgentResponse = { * @remarks * * An agent represents an AI system that can be protected and monitored. - * Each agent has a unique ID and can have multiple steps registered with it. + * Each agent has a unique immutable name and can have multiple steps registered with it. */ agent: Agent; /** diff --git a/sdks/typescript/src/generated/models/init-agent-request.ts b/sdks/typescript/src/generated/models/init-agent-request.ts index 8a930bb0..adb9b049 100644 --- a/sdks/typescript/src/generated/models/init-agent-request.ts +++ b/sdks/typescript/src/generated/models/init-agent-request.ts @@ -27,7 +27,7 @@ export type InitAgentRequest = { * @remarks * * An agent represents an AI system that can be protected and monitored. - * Each agent has a unique ID and can have multiple steps registered with it. + * Each agent has a unique immutable name and can have multiple steps registered with it. */ agent: Agent; /** diff --git a/sdks/typescript/src/generated/models/operations/delete-agent-policy-api-v1-agents-agent-id-policy-delete.ts b/sdks/typescript/src/generated/models/operations/delete-agent-policy-api-v1-agents-agent-id-policy-delete.ts deleted file mode 100644 index 48ec8257..00000000 --- a/sdks/typescript/src/generated/models/operations/delete-agent-policy-api-v1-agents-agent-id-policy-delete.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - */ - -import * as z from "zod/v4-mini"; -import { remap as remap$ } from "../../lib/primitives.js"; - -export type DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest = { - agentId: string; -}; - -/** @internal */ -export type DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest$Outbound = { - agent_id: string; -}; - -/** @internal */ -export const DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest$outboundSchema: - z.ZodMiniType< - DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest$Outbound, - DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest - > = z.pipe( - z.object({ - agentId: z.string(), - }), - z.transform((v) => { - return remap$(v, { - agentId: "agent_id", - }); - }), - ); - -export function deleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequestToJSON( - deleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest: - DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest, -): string { - return JSON.stringify( - DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest$outboundSchema.parse( - deleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest, - ), - ); -} diff --git a/sdks/typescript/src/generated/models/operations/delete-agent-policy-api-v1-agents-agent-name-policy-delete.ts b/sdks/typescript/src/generated/models/operations/delete-agent-policy-api-v1-agents-agent-name-policy-delete.ts new file mode 100644 index 00000000..ff2543db --- /dev/null +++ b/sdks/typescript/src/generated/models/operations/delete-agent-policy-api-v1-agents-agent-name-policy-delete.ts @@ -0,0 +1,42 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import * as z from "zod/v4-mini"; +import { remap as remap$ } from "../../lib/primitives.js"; + +export type DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest = { + agentName: string; +}; + +/** @internal */ +export type DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest$Outbound = + { + agent_name: string; + }; + +/** @internal */ +export const DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest$outboundSchema: + z.ZodMiniType< + DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest$Outbound, + DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest + > = z.pipe( + z.object({ + agentName: z.string(), + }), + z.transform((v) => { + return remap$(v, { + agentName: "agent_name", + }); + }), + ); + +export function deleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequestToJSON( + deleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest: + DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest, +): string { + return JSON.stringify( + DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest$outboundSchema + .parse(deleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest), + ); +} diff --git a/sdks/typescript/src/generated/models/operations/get-agent-api-v1-agents-agent-id-get.ts b/sdks/typescript/src/generated/models/operations/get-agent-api-v1-agents-agent-id-get.ts deleted file mode 100644 index 84f2a0e9..00000000 --- a/sdks/typescript/src/generated/models/operations/get-agent-api-v1-agents-agent-id-get.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - */ - -import * as z from "zod/v4-mini"; -import { remap as remap$ } from "../../lib/primitives.js"; - -export type GetAgentApiV1AgentsAgentIdGetRequest = { - agentId: string; -}; - -/** @internal */ -export type GetAgentApiV1AgentsAgentIdGetRequest$Outbound = { - agent_id: string; -}; - -/** @internal */ -export const GetAgentApiV1AgentsAgentIdGetRequest$outboundSchema: z.ZodMiniType< - GetAgentApiV1AgentsAgentIdGetRequest$Outbound, - GetAgentApiV1AgentsAgentIdGetRequest -> = z.pipe( - z.object({ - agentId: z.string(), - }), - z.transform((v) => { - return remap$(v, { - agentId: "agent_id", - }); - }), -); - -export function getAgentApiV1AgentsAgentIdGetRequestToJSON( - getAgentApiV1AgentsAgentIdGetRequest: GetAgentApiV1AgentsAgentIdGetRequest, -): string { - return JSON.stringify( - GetAgentApiV1AgentsAgentIdGetRequest$outboundSchema.parse( - getAgentApiV1AgentsAgentIdGetRequest, - ), - ); -} diff --git a/sdks/typescript/src/generated/models/operations/get-agent-api-v1-agents-agent-name-get.ts b/sdks/typescript/src/generated/models/operations/get-agent-api-v1-agents-agent-name-get.ts new file mode 100644 index 00000000..0fa6767a --- /dev/null +++ b/sdks/typescript/src/generated/models/operations/get-agent-api-v1-agents-agent-name-get.ts @@ -0,0 +1,42 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import * as z from "zod/v4-mini"; +import { remap as remap$ } from "../../lib/primitives.js"; + +export type GetAgentApiV1AgentsAgentNameGetRequest = { + agentName: string; +}; + +/** @internal */ +export type GetAgentApiV1AgentsAgentNameGetRequest$Outbound = { + agent_name: string; +}; + +/** @internal */ +export const GetAgentApiV1AgentsAgentNameGetRequest$outboundSchema: + z.ZodMiniType< + GetAgentApiV1AgentsAgentNameGetRequest$Outbound, + GetAgentApiV1AgentsAgentNameGetRequest + > = z.pipe( + z.object({ + agentName: z.string(), + }), + z.transform((v) => { + return remap$(v, { + agentName: "agent_name", + }); + }), + ); + +export function getAgentApiV1AgentsAgentNameGetRequestToJSON( + getAgentApiV1AgentsAgentNameGetRequest: + GetAgentApiV1AgentsAgentNameGetRequest, +): string { + return JSON.stringify( + GetAgentApiV1AgentsAgentNameGetRequest$outboundSchema.parse( + getAgentApiV1AgentsAgentNameGetRequest, + ), + ); +} diff --git a/sdks/typescript/src/generated/models/operations/get-agent-evaluator-api-v1-agents-agent-id-evaluators-evaluator-name-get.ts b/sdks/typescript/src/generated/models/operations/get-agent-evaluator-api-v1-agents-agent-id-evaluators-evaluator-name-get.ts deleted file mode 100644 index 4412a62a..00000000 --- a/sdks/typescript/src/generated/models/operations/get-agent-evaluator-api-v1-agents-agent-id-evaluators-evaluator-name-get.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - */ - -import * as z from "zod/v4-mini"; -import { remap as remap$ } from "../../lib/primitives.js"; - -export type GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest = - { - agentId: string; - evaluatorName: string; - }; - -/** @internal */ -export type GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest$Outbound = - { - agent_id: string; - evaluator_name: string; - }; - -/** @internal */ -export const GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest$outboundSchema: - z.ZodMiniType< - GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest$Outbound, - GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest - > = z.pipe( - z.object({ - agentId: z.string(), - evaluatorName: z.string(), - }), - z.transform((v) => { - return remap$(v, { - agentId: "agent_id", - evaluatorName: "evaluator_name", - }); - }), - ); - -export function getAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequestToJSON( - getAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest: - GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest, -): string { - return JSON.stringify( - GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest$outboundSchema - .parse( - getAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest, - ), - ); -} diff --git a/sdks/typescript/src/generated/models/operations/get-agent-evaluator-api-v1-agents-agent-name-evaluators-evaluator-name-get.ts b/sdks/typescript/src/generated/models/operations/get-agent-evaluator-api-v1-agents-agent-name-evaluators-evaluator-name-get.ts new file mode 100644 index 00000000..39eda5c3 --- /dev/null +++ b/sdks/typescript/src/generated/models/operations/get-agent-evaluator-api-v1-agents-agent-name-evaluators-evaluator-name-get.ts @@ -0,0 +1,49 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import * as z from "zod/v4-mini"; +import { remap as remap$ } from "../../lib/primitives.js"; + +export type GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest = + { + agentName: string; + evaluatorName: string; + }; + +/** @internal */ +export type GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest$Outbound = + { + agent_name: string; + evaluator_name: string; + }; + +/** @internal */ +export const GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest$outboundSchema: + z.ZodMiniType< + GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest$Outbound, + GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest + > = z.pipe( + z.object({ + agentName: z.string(), + evaluatorName: z.string(), + }), + z.transform((v) => { + return remap$(v, { + agentName: "agent_name", + evaluatorName: "evaluator_name", + }); + }), + ); + +export function getAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequestToJSON( + getAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest: + GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest, +): string { + return JSON.stringify( + GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest$outboundSchema + .parse( + getAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest, + ), + ); +} diff --git a/sdks/typescript/src/generated/models/operations/get-agent-policy-api-v1-agents-agent-id-policy-get.ts b/sdks/typescript/src/generated/models/operations/get-agent-policy-api-v1-agents-agent-id-policy-get.ts deleted file mode 100644 index e40885cb..00000000 --- a/sdks/typescript/src/generated/models/operations/get-agent-policy-api-v1-agents-agent-id-policy-get.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - */ - -import * as z from "zod/v4-mini"; -import { remap as remap$ } from "../../lib/primitives.js"; - -export type GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest = { - agentId: string; -}; - -/** @internal */ -export type GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest$Outbound = { - agent_id: string; -}; - -/** @internal */ -export const GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest$outboundSchema: - z.ZodMiniType< - GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest$Outbound, - GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest - > = z.pipe( - z.object({ - agentId: z.string(), - }), - z.transform((v) => { - return remap$(v, { - agentId: "agent_id", - }); - }), - ); - -export function getAgentPolicyApiV1AgentsAgentIdPolicyGetRequestToJSON( - getAgentPolicyApiV1AgentsAgentIdPolicyGetRequest: - GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest, -): string { - return JSON.stringify( - GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest$outboundSchema.parse( - getAgentPolicyApiV1AgentsAgentIdPolicyGetRequest, - ), - ); -} diff --git a/sdks/typescript/src/generated/models/operations/get-agent-policy-api-v1-agents-agent-name-policy-get.ts b/sdks/typescript/src/generated/models/operations/get-agent-policy-api-v1-agents-agent-name-policy-get.ts new file mode 100644 index 00000000..7642f568 --- /dev/null +++ b/sdks/typescript/src/generated/models/operations/get-agent-policy-api-v1-agents-agent-name-policy-get.ts @@ -0,0 +1,42 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import * as z from "zod/v4-mini"; +import { remap as remap$ } from "../../lib/primitives.js"; + +export type GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest = { + agentName: string; +}; + +/** @internal */ +export type GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest$Outbound = { + agent_name: string; +}; + +/** @internal */ +export const GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest$outboundSchema: + z.ZodMiniType< + GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest$Outbound, + GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest + > = z.pipe( + z.object({ + agentName: z.string(), + }), + z.transform((v) => { + return remap$(v, { + agentName: "agent_name", + }); + }), + ); + +export function getAgentPolicyApiV1AgentsAgentNamePolicyGetRequestToJSON( + getAgentPolicyApiV1AgentsAgentNamePolicyGetRequest: + GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest, +): string { + return JSON.stringify( + GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest$outboundSchema.parse( + getAgentPolicyApiV1AgentsAgentNamePolicyGetRequest, + ), + ); +} diff --git a/sdks/typescript/src/generated/models/operations/get-control-stats-api-v1-observability-stats-controls-control-id-get.ts b/sdks/typescript/src/generated/models/operations/get-control-stats-api-v1-observability-stats-controls-control-id-get.ts index e3e26749..56473a89 100644 --- a/sdks/typescript/src/generated/models/operations/get-control-stats-api-v1-observability-stats-controls-control-id-get.ts +++ b/sdks/typescript/src/generated/models/operations/get-control-stats-api-v1-observability-stats-controls-control-id-get.ts @@ -22,7 +22,7 @@ export type QueryParamTimeRange = ClosedEnum; export type GetControlStatsApiV1ObservabilityStatsControlsControlIdGetRequest = { controlId: number; - agentUuid: string; + agentName: string; timeRange?: QueryParamTimeRange | undefined; includeTimeseries?: boolean | undefined; }; @@ -36,7 +36,7 @@ export const QueryParamTimeRange$outboundSchema: z.ZodMiniEnum< export type GetControlStatsApiV1ObservabilityStatsControlsControlIdGetRequest$Outbound = { control_id: number; - agent_uuid: string; + agent_name: string; time_range: string; include_timeseries: boolean; }; @@ -49,14 +49,14 @@ export const GetControlStatsApiV1ObservabilityStatsControlsControlIdGetRequest$o > = z.pipe( z.object({ controlId: z.int(), - agentUuid: z.string(), + agentName: z.string(), timeRange: z._default(QueryParamTimeRange$outboundSchema, "5m"), includeTimeseries: z._default(z.boolean(), false), }), z.transform((v) => { return remap$(v, { controlId: "control_id", - agentUuid: "agent_uuid", + agentName: "agent_name", timeRange: "time_range", includeTimeseries: "include_timeseries", }); diff --git a/sdks/typescript/src/generated/models/operations/get-stats-api-v1-observability-stats-get.ts b/sdks/typescript/src/generated/models/operations/get-stats-api-v1-observability-stats-get.ts index 030726d7..ec6c6ba3 100644 --- a/sdks/typescript/src/generated/models/operations/get-stats-api-v1-observability-stats-get.ts +++ b/sdks/typescript/src/generated/models/operations/get-stats-api-v1-observability-stats-get.ts @@ -20,7 +20,7 @@ export const TimeRange = { export type TimeRange = ClosedEnum; export type GetStatsApiV1ObservabilityStatsGetRequest = { - agentUuid: string; + agentName: string; timeRange?: TimeRange | undefined; includeTimeseries?: boolean | undefined; }; @@ -32,7 +32,7 @@ export const TimeRange$outboundSchema: z.ZodMiniEnum = z.enum( /** @internal */ export type GetStatsApiV1ObservabilityStatsGetRequest$Outbound = { - agent_uuid: string; + agent_name: string; time_range: string; include_timeseries: boolean; }; @@ -44,13 +44,13 @@ export const GetStatsApiV1ObservabilityStatsGetRequest$outboundSchema: GetStatsApiV1ObservabilityStatsGetRequest > = z.pipe( z.object({ - agentUuid: z.string(), + agentName: z.string(), timeRange: z._default(TimeRange$outboundSchema, "5m"), includeTimeseries: z._default(z.boolean(), false), }), z.transform((v) => { return remap$(v, { - agentUuid: "agent_uuid", + agentName: "agent_name", timeRange: "time_range", includeTimeseries: "include_timeseries", }); diff --git a/sdks/typescript/src/generated/models/operations/index.ts b/sdks/typescript/src/generated/models/operations/index.ts index 2a322b0f..8e6bebc2 100644 --- a/sdks/typescript/src/generated/models/operations/index.ts +++ b/sdks/typescript/src/generated/models/operations/index.ts @@ -3,27 +3,27 @@ */ export * from "./add-control-to-policy-api-v1-policies-policy-id-controls-control-id-post.js"; -export * from "./delete-agent-policy-api-v1-agents-agent-id-policy-delete.js"; +export * from "./delete-agent-policy-api-v1-agents-agent-name-policy-delete.js"; export * from "./delete-control-api-v1-controls-control-id-delete.js"; export * from "./delete-evaluator-config-api-v1-evaluator-configs-config-id-delete.js"; export * from "./evaluate-api-v1-evaluation-post.js"; -export * from "./get-agent-api-v1-agents-agent-id-get.js"; -export * from "./get-agent-evaluator-api-v1-agents-agent-id-evaluators-evaluator-name-get.js"; -export * from "./get-agent-policy-api-v1-agents-agent-id-policy-get.js"; +export * from "./get-agent-api-v1-agents-agent-name-get.js"; +export * from "./get-agent-evaluator-api-v1-agents-agent-name-evaluators-evaluator-name-get.js"; +export * from "./get-agent-policy-api-v1-agents-agent-name-policy-get.js"; export * from "./get-control-api-v1-controls-control-id-get.js"; export * from "./get-control-data-api-v1-controls-control-id-data-get.js"; export * from "./get-control-stats-api-v1-observability-stats-controls-control-id-get.js"; export * from "./get-evaluator-config-api-v1-evaluator-configs-config-id-get.js"; export * from "./get-stats-api-v1-observability-stats-get.js"; -export * from "./list-agent-controls-api-v1-agents-agent-id-controls-get.js"; -export * from "./list-agent-evaluators-api-v1-agents-agent-id-evaluators-get.js"; +export * from "./list-agent-controls-api-v1-agents-agent-name-controls-get.js"; +export * from "./list-agent-evaluators-api-v1-agents-agent-name-evaluators-get.js"; export * from "./list-agents-api-v1-agents-get.js"; export * from "./list-controls-api-v1-controls-get.js"; export * from "./list-evaluator-configs-api-v1-evaluator-configs-get.js"; export * from "./list-policy-controls-api-v1-policies-policy-id-controls-get.js"; -export * from "./patch-agent-api-v1-agents-agent-id-patch.js"; +export * from "./patch-agent-api-v1-agents-agent-name-patch.js"; export * from "./patch-control-api-v1-controls-control-id-patch.js"; export * from "./remove-control-from-policy-api-v1-policies-policy-id-controls-control-id-delete.js"; -export * from "./set-agent-policy-api-v1-agents-agent-id-policy-policy-id-post.js"; +export * from "./set-agent-policy-api-v1-agents-agent-name-policy-policy-id-post.js"; export * from "./set-control-data-api-v1-controls-control-id-data-put.js"; export * from "./update-evaluator-config-api-v1-evaluator-configs-config-id-put.js"; diff --git a/sdks/typescript/src/generated/models/operations/list-agent-controls-api-v1-agents-agent-id-controls-get.ts b/sdks/typescript/src/generated/models/operations/list-agent-controls-api-v1-agents-agent-id-controls-get.ts deleted file mode 100644 index 09f5462a..00000000 --- a/sdks/typescript/src/generated/models/operations/list-agent-controls-api-v1-agents-agent-id-controls-get.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - */ - -import * as z from "zod/v4-mini"; -import { remap as remap$ } from "../../lib/primitives.js"; - -export type ListAgentControlsApiV1AgentsAgentIdControlsGetRequest = { - agentId: string; -}; - -/** @internal */ -export type ListAgentControlsApiV1AgentsAgentIdControlsGetRequest$Outbound = { - agent_id: string; -}; - -/** @internal */ -export const ListAgentControlsApiV1AgentsAgentIdControlsGetRequest$outboundSchema: - z.ZodMiniType< - ListAgentControlsApiV1AgentsAgentIdControlsGetRequest$Outbound, - ListAgentControlsApiV1AgentsAgentIdControlsGetRequest - > = z.pipe( - z.object({ - agentId: z.string(), - }), - z.transform((v) => { - return remap$(v, { - agentId: "agent_id", - }); - }), - ); - -export function listAgentControlsApiV1AgentsAgentIdControlsGetRequestToJSON( - listAgentControlsApiV1AgentsAgentIdControlsGetRequest: - ListAgentControlsApiV1AgentsAgentIdControlsGetRequest, -): string { - return JSON.stringify( - ListAgentControlsApiV1AgentsAgentIdControlsGetRequest$outboundSchema.parse( - listAgentControlsApiV1AgentsAgentIdControlsGetRequest, - ), - ); -} diff --git a/sdks/typescript/src/generated/models/operations/list-agent-controls-api-v1-agents-agent-name-controls-get.ts b/sdks/typescript/src/generated/models/operations/list-agent-controls-api-v1-agents-agent-name-controls-get.ts new file mode 100644 index 00000000..ddc50315 --- /dev/null +++ b/sdks/typescript/src/generated/models/operations/list-agent-controls-api-v1-agents-agent-name-controls-get.ts @@ -0,0 +1,41 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import * as z from "zod/v4-mini"; +import { remap as remap$ } from "../../lib/primitives.js"; + +export type ListAgentControlsApiV1AgentsAgentNameControlsGetRequest = { + agentName: string; +}; + +/** @internal */ +export type ListAgentControlsApiV1AgentsAgentNameControlsGetRequest$Outbound = { + agent_name: string; +}; + +/** @internal */ +export const ListAgentControlsApiV1AgentsAgentNameControlsGetRequest$outboundSchema: + z.ZodMiniType< + ListAgentControlsApiV1AgentsAgentNameControlsGetRequest$Outbound, + ListAgentControlsApiV1AgentsAgentNameControlsGetRequest + > = z.pipe( + z.object({ + agentName: z.string(), + }), + z.transform((v) => { + return remap$(v, { + agentName: "agent_name", + }); + }), + ); + +export function listAgentControlsApiV1AgentsAgentNameControlsGetRequestToJSON( + listAgentControlsApiV1AgentsAgentNameControlsGetRequest: + ListAgentControlsApiV1AgentsAgentNameControlsGetRequest, +): string { + return JSON.stringify( + ListAgentControlsApiV1AgentsAgentNameControlsGetRequest$outboundSchema + .parse(listAgentControlsApiV1AgentsAgentNameControlsGetRequest), + ); +} diff --git a/sdks/typescript/src/generated/models/operations/list-agent-evaluators-api-v1-agents-agent-id-evaluators-get.ts b/sdks/typescript/src/generated/models/operations/list-agent-evaluators-api-v1-agents-agent-id-evaluators-get.ts deleted file mode 100644 index 55185ff8..00000000 --- a/sdks/typescript/src/generated/models/operations/list-agent-evaluators-api-v1-agents-agent-id-evaluators-get.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - */ - -import * as z from "zod/v4-mini"; -import { remap as remap$ } from "../../lib/primitives.js"; - -export type ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest = { - agentId: string; - cursor?: string | null | undefined; - limit?: number | undefined; -}; - -/** @internal */ -export type ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest$Outbound = - { - agent_id: string; - cursor?: string | null | undefined; - limit: number; - }; - -/** @internal */ -export const ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest$outboundSchema: - z.ZodMiniType< - ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest$Outbound, - ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest - > = z.pipe( - z.object({ - agentId: z.string(), - cursor: z.optional(z.nullable(z.string())), - limit: z._default(z.int(), 20), - }), - z.transform((v) => { - return remap$(v, { - agentId: "agent_id", - }); - }), - ); - -export function listAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequestToJSON( - listAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest: - ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest, -): string { - return JSON.stringify( - ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest$outboundSchema - .parse(listAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest), - ); -} diff --git a/sdks/typescript/src/generated/models/operations/list-agent-evaluators-api-v1-agents-agent-name-evaluators-get.ts b/sdks/typescript/src/generated/models/operations/list-agent-evaluators-api-v1-agents-agent-name-evaluators-get.ts new file mode 100644 index 00000000..2aaf3560 --- /dev/null +++ b/sdks/typescript/src/generated/models/operations/list-agent-evaluators-api-v1-agents-agent-name-evaluators-get.ts @@ -0,0 +1,48 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import * as z from "zod/v4-mini"; +import { remap as remap$ } from "../../lib/primitives.js"; + +export type ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest = { + agentName: string; + cursor?: string | null | undefined; + limit?: number | undefined; +}; + +/** @internal */ +export type ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest$Outbound = + { + agent_name: string; + cursor?: string | null | undefined; + limit: number; + }; + +/** @internal */ +export const ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest$outboundSchema: + z.ZodMiniType< + ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest$Outbound, + ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest + > = z.pipe( + z.object({ + agentName: z.string(), + cursor: z.optional(z.nullable(z.string())), + limit: z._default(z.int(), 20), + }), + z.transform((v) => { + return remap$(v, { + agentName: "agent_name", + }); + }), + ); + +export function listAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequestToJSON( + listAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest: + ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest, +): string { + return JSON.stringify( + ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest$outboundSchema + .parse(listAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest), + ); +} diff --git a/sdks/typescript/src/generated/models/operations/patch-agent-api-v1-agents-agent-id-patch.ts b/sdks/typescript/src/generated/models/operations/patch-agent-api-v1-agents-agent-id-patch.ts deleted file mode 100644 index bfd30824..00000000 --- a/sdks/typescript/src/generated/models/operations/patch-agent-api-v1-agents-agent-id-patch.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - */ - -import * as z from "zod/v4-mini"; -import { remap as remap$ } from "../../lib/primitives.js"; -import * as models from "../index.js"; - -export type PatchAgentApiV1AgentsAgentIdPatchRequest = { - agentId: string; - body: models.PatchAgentRequest; -}; - -/** @internal */ -export type PatchAgentApiV1AgentsAgentIdPatchRequest$Outbound = { - agent_id: string; - body: models.PatchAgentRequest$Outbound; -}; - -/** @internal */ -export const PatchAgentApiV1AgentsAgentIdPatchRequest$outboundSchema: - z.ZodMiniType< - PatchAgentApiV1AgentsAgentIdPatchRequest$Outbound, - PatchAgentApiV1AgentsAgentIdPatchRequest - > = z.pipe( - z.object({ - agentId: z.string(), - body: models.PatchAgentRequest$outboundSchema, - }), - z.transform((v) => { - return remap$(v, { - agentId: "agent_id", - }); - }), - ); - -export function patchAgentApiV1AgentsAgentIdPatchRequestToJSON( - patchAgentApiV1AgentsAgentIdPatchRequest: - PatchAgentApiV1AgentsAgentIdPatchRequest, -): string { - return JSON.stringify( - PatchAgentApiV1AgentsAgentIdPatchRequest$outboundSchema.parse( - patchAgentApiV1AgentsAgentIdPatchRequest, - ), - ); -} diff --git a/sdks/typescript/src/generated/models/operations/patch-agent-api-v1-agents-agent-name-patch.ts b/sdks/typescript/src/generated/models/operations/patch-agent-api-v1-agents-agent-name-patch.ts new file mode 100644 index 00000000..5bb9f012 --- /dev/null +++ b/sdks/typescript/src/generated/models/operations/patch-agent-api-v1-agents-agent-name-patch.ts @@ -0,0 +1,46 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import * as z from "zod/v4-mini"; +import { remap as remap$ } from "../../lib/primitives.js"; +import * as models from "../index.js"; + +export type PatchAgentApiV1AgentsAgentNamePatchRequest = { + agentName: string; + body: models.PatchAgentRequest; +}; + +/** @internal */ +export type PatchAgentApiV1AgentsAgentNamePatchRequest$Outbound = { + agent_name: string; + body: models.PatchAgentRequest$Outbound; +}; + +/** @internal */ +export const PatchAgentApiV1AgentsAgentNamePatchRequest$outboundSchema: + z.ZodMiniType< + PatchAgentApiV1AgentsAgentNamePatchRequest$Outbound, + PatchAgentApiV1AgentsAgentNamePatchRequest + > = z.pipe( + z.object({ + agentName: z.string(), + body: models.PatchAgentRequest$outboundSchema, + }), + z.transform((v) => { + return remap$(v, { + agentName: "agent_name", + }); + }), + ); + +export function patchAgentApiV1AgentsAgentNamePatchRequestToJSON( + patchAgentApiV1AgentsAgentNamePatchRequest: + PatchAgentApiV1AgentsAgentNamePatchRequest, +): string { + return JSON.stringify( + PatchAgentApiV1AgentsAgentNamePatchRequest$outboundSchema.parse( + patchAgentApiV1AgentsAgentNamePatchRequest, + ), + ); +} diff --git a/sdks/typescript/src/generated/models/operations/set-agent-policy-api-v1-agents-agent-id-policy-policy-id-post.ts b/sdks/typescript/src/generated/models/operations/set-agent-policy-api-v1-agents-agent-id-policy-policy-id-post.ts deleted file mode 100644 index aa9b0121..00000000 --- a/sdks/typescript/src/generated/models/operations/set-agent-policy-api-v1-agents-agent-id-policy-policy-id-post.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - */ - -import * as z from "zod/v4-mini"; -import { remap as remap$ } from "../../lib/primitives.js"; - -export type SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest = { - agentId: string; - policyId: number; -}; - -/** @internal */ -export type SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest$Outbound = - { - agent_id: string; - policy_id: number; - }; - -/** @internal */ -export const SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest$outboundSchema: - z.ZodMiniType< - SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest$Outbound, - SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest - > = z.pipe( - z.object({ - agentId: z.string(), - policyId: z.int(), - }), - z.transform((v) => { - return remap$(v, { - agentId: "agent_id", - policyId: "policy_id", - }); - }), - ); - -export function setAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequestToJSON( - setAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest: - SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest, -): string { - return JSON.stringify( - SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest$outboundSchema - .parse(setAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest), - ); -} diff --git a/sdks/typescript/src/generated/models/operations/set-agent-policy-api-v1-agents-agent-name-policy-policy-id-post.ts b/sdks/typescript/src/generated/models/operations/set-agent-policy-api-v1-agents-agent-name-policy-policy-id-post.ts new file mode 100644 index 00000000..87c5bf6a --- /dev/null +++ b/sdks/typescript/src/generated/models/operations/set-agent-policy-api-v1-agents-agent-name-policy-policy-id-post.ts @@ -0,0 +1,46 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import * as z from "zod/v4-mini"; +import { remap as remap$ } from "../../lib/primitives.js"; + +export type SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest = { + agentName: string; + policyId: number; +}; + +/** @internal */ +export type SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest$Outbound = + { + agent_name: string; + policy_id: number; + }; + +/** @internal */ +export const SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest$outboundSchema: + z.ZodMiniType< + SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest$Outbound, + SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest + > = z.pipe( + z.object({ + agentName: z.string(), + policyId: z.int(), + }), + z.transform((v) => { + return remap$(v, { + agentName: "agent_name", + policyId: "policy_id", + }); + }), + ); + +export function setAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequestToJSON( + setAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest: + SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest, +): string { + return JSON.stringify( + SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest$outboundSchema + .parse(setAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest), + ); +} diff --git a/sdks/typescript/src/generated/models/stats-response.ts b/sdks/typescript/src/generated/models/stats-response.ts index b4c03368..7ac09190 100644 --- a/sdks/typescript/src/generated/models/stats-response.ts +++ b/sdks/typescript/src/generated/models/stats-response.ts @@ -19,16 +19,16 @@ import { StatsTotals, StatsTotals$inboundSchema } from "./stats-totals.js"; * Contains agent-level totals (with optional timeseries) and per-control breakdown. * * Attributes: - * agent_uuid: Agent UUID + * agent_name: Agent identifier * time_range: Time range used * totals: Agent-level aggregate statistics (includes timeseries) * controls: Per-control breakdown for discovery and detail */ export type StatsResponse = { /** - * Agent UUID + * Agent identifier */ - agentUuid: string; + agentName: string; /** * Per-control breakdown */ @@ -64,14 +64,14 @@ export const StatsResponse$inboundSchema: z.ZodMiniType< unknown > = z.pipe( z.object({ - agent_uuid: types.string(), + agent_name: types.string(), controls: z.array(ControlStats$inboundSchema), time_range: types.string(), totals: StatsTotals$inboundSchema, }), z.transform((v) => { return remap$(v, { - "agent_uuid": "agentUuid", + "agent_name": "agentName", "time_range": "timeRange", }); }), diff --git a/sdks/typescript/src/generated/sdk/agents.ts b/sdks/typescript/src/generated/sdk/agents.ts index 8a15dffb..98c0c874 100644 --- a/sdks/typescript/src/generated/sdk/agents.ts +++ b/sdks/typescript/src/generated/sdk/agents.ts @@ -24,11 +24,11 @@ export class Agents extends ClientSDK { * @remarks * List all registered agents with cursor-based pagination. * - * Returns a summary of each agent including ID, name, policy assignment, + * Returns a summary of each agent including identifier, policy assignment, * and counts of registered steps and evaluators. * * Args: - * cursor: Optional cursor for pagination (UUID of last agent from previous page) + * cursor: Optional cursor for pagination (last agent name from previous page) * limit: Pagination limit (default 20, max 100) * name: Optional name filter (case-insensitive partial match) * db: Database session (injected) @@ -55,9 +55,7 @@ export class Agents extends ClientSDK { * * This endpoint is idempotent: * - If the agent name doesn't exist, creates a new agent - * - If the agent name exists with the same UUID, updates registration data - * - If the agent name exists with a different UUID, returns 409 Conflict - * - If the UUID exists with a different name, returns 409 Conflict (no renames) + * - If the agent name exists, updates registration data in place * * conflict_mode controls registration conflict handling: * - strict (default): preserve compatibility checks and conflict errors @@ -69,10 +67,6 @@ export class Agents extends ClientSDK { * * Returns: * InitAgentResponse with created flag and active controls (if policy assigned) - * - * Raises: - * HTTPException 409: Agent name exists with different UUID - * HTTPException 500: Database error during creation/update */ async init( request: models.InitAgentRequest, @@ -94,7 +88,7 @@ export class Agents extends ClientSDK { * Returns the latest version of each step (deduplicated by type+name). * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * db: Database session (injected) * * Returns: @@ -105,7 +99,7 @@ export class Agents extends ClientSDK { * HTTPException 422: Agent data is corrupted */ async get( - request: operations.GetAgentApiV1AgentsAgentIdGetRequest, + request: operations.GetAgentApiV1AgentsAgentNameGetRequest, options?: RequestOptions, ): Promise { return unwrapAsync(agentsGet( @@ -125,7 +119,7 @@ export class Agents extends ClientSDK { * Removals are idempotent - attempting to remove non-existent items is not an error. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * request: Lists of step/evaluator identifiers to remove * db: Database session (injected) * @@ -137,7 +131,7 @@ export class Agents extends ClientSDK { * HTTPException 500: Database error during update */ async update( - request: operations.PatchAgentApiV1AgentsAgentIdPatchRequest, + request: operations.PatchAgentApiV1AgentsAgentNamePatchRequest, options?: RequestOptions, ): Promise { return unwrapAsync(agentsUpdate( @@ -157,7 +151,7 @@ export class Agents extends ClientSDK { * Returns an empty list if the agent has no policy. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * db: Database session (injected) * * Returns: @@ -167,7 +161,7 @@ export class Agents extends ClientSDK { * HTTPException 404: Agent not found */ async listControls( - request: operations.ListAgentControlsApiV1AgentsAgentIdControlsGetRequest, + request: operations.ListAgentControlsApiV1AgentsAgentNameControlsGetRequest, options?: RequestOptions, ): Promise { return unwrapAsync(agentsListControls( @@ -188,7 +182,7 @@ export class Agents extends ClientSDK { * - UI to display available config options * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * cursor: Optional cursor for pagination (name of last evaluator from previous page) * limit: Pagination limit (default 20, max 100) * db: Database session (injected) @@ -201,7 +195,7 @@ export class Agents extends ClientSDK { */ async listEvaluators( request: - operations.ListAgentEvaluatorsApiV1AgentsAgentIdEvaluatorsGetRequest, + operations.ListAgentEvaluatorsApiV1AgentsAgentNameEvaluatorsGetRequest, options?: RequestOptions, ): Promise { return unwrapAsync(agentsListEvaluators( @@ -218,7 +212,7 @@ export class Agents extends ClientSDK { * Get a specific evaluator schema registered with an agent. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * evaluator_name: Name of the evaluator * db: Database session (injected) * @@ -230,7 +224,7 @@ export class Agents extends ClientSDK { */ async getEvaluator( request: - operations.GetAgentEvaluatorApiV1AgentsAgentIdEvaluatorsEvaluatorNameGetRequest, + operations.GetAgentEvaluatorApiV1AgentsAgentNameEvaluatorsEvaluatorNameGetRequest, options?: RequestOptions, ): Promise { return unwrapAsync(agentsGetEvaluator( @@ -249,7 +243,7 @@ export class Agents extends ClientSDK { * The agent will no longer have any protection controls active. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * db: Database session (injected) * * Returns: @@ -260,7 +254,8 @@ export class Agents extends ClientSDK { * HTTPException 500: Database error during removal */ async deletePolicy( - request: operations.DeleteAgentPolicyApiV1AgentsAgentIdPolicyDeleteRequest, + request: + operations.DeleteAgentPolicyApiV1AgentsAgentNamePolicyDeleteRequest, options?: RequestOptions, ): Promise { return unwrapAsync(agentsDeletePolicy( @@ -277,7 +272,7 @@ export class Agents extends ClientSDK { * Retrieve the policy currently assigned to an agent. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * db: Database session (injected) * * Returns: @@ -287,7 +282,7 @@ export class Agents extends ClientSDK { * HTTPException 404: Agent not found or agent has no policy assigned */ async getPolicy( - request: operations.GetAgentPolicyApiV1AgentsAgentIdPolicyGetRequest, + request: operations.GetAgentPolicyApiV1AgentsAgentNamePolicyGetRequest, options?: RequestOptions, ): Promise { return unwrapAsync(agentsGetPolicy( @@ -306,7 +301,7 @@ export class Agents extends ClientSDK { * The agent will immediately inherit all controls from the assigned policy. * * Args: - * agent_id: UUID of the agent + * agent_name: Agent identifier * policy_id: ID of the policy to assign * db: Database session (injected) * @@ -319,7 +314,7 @@ export class Agents extends ClientSDK { */ async updatePolicy( request: - operations.SetAgentPolicyApiV1AgentsAgentIdPolicyPolicyIdPostRequest, + operations.SetAgentPolicyApiV1AgentsAgentNamePolicyPolicyIdPostRequest, options?: RequestOptions, ): Promise { return unwrapAsync(agentsUpdatePolicy( diff --git a/sdks/typescript/src/generated/sdk/evaluators.ts b/sdks/typescript/src/generated/sdk/evaluators.ts index c78145c0..b5bab688 100644 --- a/sdks/typescript/src/generated/sdk/evaluators.ts +++ b/sdks/typescript/src/generated/sdk/evaluators.ts @@ -23,7 +23,7 @@ export class Evaluators extends ClientSDK { * - **sql**: SQL query validation * * Custom evaluators are registered per-agent via initAgent. - * Use GET /agents/{agent_id}/evaluators to list agent-specific schemas. + * Use GET /agents/{agent_name}/evaluators to list agent-specific schemas. */ async list( options?: RequestOptions, diff --git a/sdks/typescript/src/generated/sdk/observability.ts b/sdks/typescript/src/generated/sdk/observability.ts index 6c59fc48..1697b9a8 100644 --- a/sdks/typescript/src/generated/sdk/observability.ts +++ b/sdks/typescript/src/generated/sdk/observability.ts @@ -49,7 +49,7 @@ export class Observability extends ClientSDK { * - trace_id: Get all events for a request * - span_id: Get all events for a function call * - control_execution_id: Get a specific event - * - agent_uuid: Filter by agent + * - agent_name: Filter by agent * - control_ids: Filter by controls * - actions: Filter by actions (allow, deny, warn, log) * - matched: Filter by matched status @@ -87,7 +87,7 @@ export class Observability extends ClientSDK { * Use /stats/controls/{control_id} for single control stats. * * Args: - * agent_uuid: Agent to get stats for + * agent_name: Agent to get stats for * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) * include_timeseries: Include time-series data points for trend visualization * store: Event store (injected) @@ -116,7 +116,7 @@ export class Observability extends ClientSDK { * * Args: * control_id: Control ID to get stats for - * agent_uuid: Agent to get stats for + * agent_name: Agent to get stats for * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) * include_timeseries: Include time-series data points for trend visualization * store: Event store (injected) diff --git a/sdks/typescript/tests/client-api.test.ts b/sdks/typescript/tests/client-api.test.ts index c6c11864..40899b46 100644 --- a/sdks/typescript/tests/client-api.test.ts +++ b/sdks/typescript/tests/client-api.test.ts @@ -106,7 +106,6 @@ describe("AgentControlClient API wiring", () => { await client.agents.init({ agent: { - agentId: "550e8400-e29b-41d4-a716-446655440000", agentName: "test-agent", }, }); diff --git a/sdks/typescript/tests/test_generate_method_names_overlay.py b/sdks/typescript/tests/test_generate_method_names_overlay.py index 311d6e80..c36db98e 100644 --- a/sdks/typescript/tests/test_generate_method_names_overlay.py +++ b/sdks/typescript/tests/test_generate_method_names_overlay.py @@ -66,9 +66,9 @@ def test_plural_collection_get_is_list_but_singular_get_is_get(overlay_gen): "operationId": "list_agents_api_v1_agents_get", }, }, - "/api/v1/agents/{agent_id}": { + "/api/v1/agents/{agent_name}": { "get": { - "operationId": "get_agent_api_v1_agents__agent_id__get", + "operationId": "get_agent_api_v1_agents__agent_name__get", }, }, "/health": { @@ -83,7 +83,7 @@ def test_plural_collection_get_is_list_but_singular_get_is_get(overlay_gen): names = overlay_gen.resolve_names(operations) assert names[("/api/v1/agents", "get")] == ("agents", "list") - assert names[("/api/v1/agents/{agent_id}", "get")] == ("agents", "get") + assert names[("/api/v1/agents/{agent_name}", "get")] == ("agents", "get") assert names[("/health", "get")] == ("system", "healthCheck") diff --git a/server/README.md b/server/README.md index 7af8ec01..57e64f26 100644 --- a/server/README.md +++ b/server/README.md @@ -159,10 +159,10 @@ POST /api/v1/agents/init Body: { "agent": {...}, "tools": [...], "force_replace": false } # Get agent -GET /api/v1/agents/{agent_id} +GET /api/v1/agents/{agent_name} # List controls for agent (based on assigned policy) -GET /api/v1/agents/{agent_id}/controls +GET /api/v1/agents/{agent_name}/controls ``` ### Control Management @@ -197,7 +197,7 @@ Body: { "name": "my-policy", "description": "..." } GET /api/v1/policies # Assign policy to agent -POST /api/v1/policies/{policy_id}/agents/{agent_id} +POST /api/v1/policies/{policy_id}/agents/{agent_name} # Add control to policy POST /api/v1/policies/{policy_id}/controls/{control_id} @@ -209,7 +209,7 @@ POST /api/v1/policies/{policy_id}/controls/{control_id} # Evaluate step against controls POST /api/v1/evaluation Body: { - "agent_uuid": "uuid", + "agent_name": "uuid", "step": { "type": "llm", "name": "chat", "input": "..." }, "stage": "pre" } @@ -231,13 +231,13 @@ Body: { "events": [...] } # Query events POST /api/v1/observability/events/query -Body: { "agent_uuid": "...", "start_time": "...", ... } +Body: { "agent_name": "...", "start_time": "...", ... } # Get agent stats -GET /api/v1/observability/stats?agent_uuid=...&time_range=5m +GET /api/v1/observability/stats?agent_name=...&time_range=5m # Get control stats -GET /api/v1/observability/stats/controls/{control_id}?agent_uuid=...&time_range=5m +GET /api/v1/observability/stats/controls/{control_id}?agent_name=...&time_range=5m ``` See [docs/REFERENCE.md](../docs/REFERENCE.md) for complete API documentation. diff --git a/server/alembic/versions/9f2c5f7a722f_initial.py b/server/alembic/versions/9f2c5f7a722f_initial.py index f7574331..d395074d 100644 --- a/server/alembic/versions/9f2c5f7a722f_initial.py +++ b/server/alembic/versions/9f2c5f7a722f_initial.py @@ -37,14 +37,12 @@ def upgrade() -> None: sa.UniqueConstraint('name') ) op.create_table('agents', - sa.Column('agent_uuid', sa.UUID(), nullable=False), sa.Column('name', sa.String(length=255), nullable=False), sa.Column('data', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False), sa.Column('policy_id', sa.Integer(), nullable=True), sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), sa.ForeignKeyConstraint(['policy_id'], ['policies.id'], ), - sa.PrimaryKeyConstraint('agent_uuid'), - sa.UniqueConstraint('name') + sa.PrimaryKeyConstraint('name') ) op.create_index(op.f('ix_agents_created_at'), 'agents', ['created_at'], unique=False) op.create_index(op.f('ix_agents_policy_id'), 'agents', ['policy_id'], unique=False) diff --git a/server/alembic/versions/simplify_observability_schema.py b/server/alembic/versions/simplify_observability_schema.py index 84612a02..0b58ca0d 100644 --- a/server/alembic/versions/simplify_observability_schema.py +++ b/server/alembic/versions/simplify_observability_schema.py @@ -9,7 +9,7 @@ 2. Recreates control_execution_events with minimal schema: - control_execution_id (PK) - timestamp (indexed) - - agent_uuid (indexed) + - agent_name (indexed) - data (JSONB containing full event) 3. Creates optimized indexes for query-time aggregation @@ -53,7 +53,7 @@ def upgrade() -> None: server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False, ), - sa.Column("agent_uuid", sa.UUID(), nullable=False), + sa.Column("agent_name", sa.String(length=255), nullable=False), sa.Column("data", postgresql.JSONB(astext_type=sa.Text()), nullable=False), ) @@ -61,7 +61,7 @@ def upgrade() -> None: op.create_index( "ix_events_agent_time", "control_execution_events", - ["agent_uuid", sa.text("timestamp DESC")], + ["agent_name", sa.text("timestamp DESC")], ) # 5. Create expression index on control_id from JSONB for grouping diff --git a/server/src/agent_control_server/endpoints/agents.py b/server/src/agent_control_server/endpoints/agents.py index 7e15ef88..8c78bc28 100644 --- a/server/src/agent_control_server/endpoints/agents.py +++ b/server/src/agent_control_server/endpoints/agents.py @@ -1,5 +1,4 @@ from typing import Any -from uuid import UUID from agent_control_engine import list_evaluators from agent_control_models.agent import Agent as APIAgent @@ -182,7 +181,7 @@ async def _build_overwrite_evaluator_removals( try: controls = await list_controls_for_agent( - agent.agent_uuid, + agent.name, db, allow_invalid_step_name_regex=True, ) @@ -239,11 +238,11 @@ async def list_agents( """ List all registered agents with cursor-based pagination. - Returns a summary of each agent including ID, name, policy assignment, + Returns a summary of each agent including identifier, policy assignment, and counts of registered steps and evaluators. Args: - cursor: Optional cursor for pagination (UUID of last agent from previous page) + cursor: Optional cursor for pagination (last agent name from previous page) limit: Pagination limit (default 20, max 100) name: Optional name filter (case-insensitive partial match) db: Database session (injected) @@ -265,8 +264,8 @@ async def list_agents( total = count_result.scalar() or 0 # Build query with cursor-based pagination - # Order by created_at DESC, then by UUID DESC for stable ordering - query = select(Agent).order_by(Agent.created_at.desc(), Agent.agent_uuid.desc()) + # Order by created_at DESC, then by name DESC for stable ordering + query = select(Agent).order_by(Agent.created_at.desc(), Agent.name.desc()) # Apply name filter if provided if name_filter is not None: @@ -274,25 +273,19 @@ async def list_agents( # If cursor provided, filter to get items after the cursor if cursor: - try: - cursor_uuid = UUID(cursor) - # Get the cursor agent to find its created_at timestamp - cursor_agent_result = await db.execute( - select(Agent).where(Agent.agent_uuid == cursor_uuid) - ) - cursor_agent = cursor_agent_result.scalars().first() - if cursor_agent: - # Get agents created before this one (or same timestamp but smaller UUID) - query = query.where( - (Agent.created_at < cursor_agent.created_at) - | ( - (Agent.created_at == cursor_agent.created_at) - & (Agent.agent_uuid < cursor_agent.agent_uuid) - ) + cursor_name = cursor.strip().lower() + cursor_agent_result = await db.execute( + select(Agent).where(Agent.name == cursor_name) + ) + cursor_agent = cursor_agent_result.scalars().first() + if cursor_agent: + query = query.where( + (Agent.created_at < cursor_agent.created_at) + | ( + (Agent.created_at == cursor_agent.created_at) + & (Agent.name < cursor_agent.name) ) - except ValueError: - # Invalid cursor UUID, ignore it and return first page - pass + ) # Fetch limit + 1 to check if there are more pages query = query.limit(limit + 1) @@ -304,28 +297,28 @@ async def list_agents( if has_more: agents = agents[:-1] # Remove the extra item - # Determine next cursor (UUID of last agent in this page) + # Determine next cursor (name of last agent in this page) next_cursor: str | None = None if has_more and agents: - next_cursor = str(agents[-1].agent_uuid) + next_cursor = agents[-1].name # Batch query: Get control counts for all agents at once # Join: Agent -> Policy -> policy_controls (junction table) -> Control # Count distinct enabled control IDs per agent # Performance: Filter NULL controls explicitly to avoid JSONB parsing on NULL rows # This allows the query planner to optimize better - control_counts_map: dict[UUID, int] = {} + control_counts_map: dict[str, int] = {} if agents: control_counts_query = ( select( - Agent.agent_uuid, + Agent.name, func.count(func.distinct(policy_controls.c.control_id)).label("count"), ) .outerjoin(Policy, Agent.policy_id == Policy.id) .outerjoin(policy_controls, Policy.id == policy_controls.c.policy_id) .outerjoin(Control, policy_controls.c.control_id == Control.id) .where( - Agent.agent_uuid.in_([agent.agent_uuid for agent in agents]), + Agent.name.in_([agent.name for agent in agents]), # Only count enabled controls: Control must exist AND be enabled # (enabled=true OR enabled key missing, default is True) Control.id.is_not(None), # Exclude NULL controls (agents without policies) @@ -334,7 +327,7 @@ async def list_agents( ~Control.data.has_key("enabled"), ), ) - .group_by(Agent.agent_uuid) + .group_by(Agent.name) ) control_counts_result = await db.execute(control_counts_query) control_counts_map = {row[0]: row[1] for row in control_counts_result.all()} @@ -355,11 +348,10 @@ async def list_agents( _logger.warning("Agent '%s' has invalid data, using zero counts", agent.name) # Get active controls count from batched query result - active_controls = control_counts_map.get(agent.agent_uuid, 0) + active_controls = control_counts_map.get(agent.name, 0) summaries.append( AgentSummary( - agent_id=str(agent.agent_uuid), agent_name=agent.name, policy_id=agent.policy_id, created_at=agent.created_at.isoformat() if agent.created_at else None, @@ -394,9 +386,7 @@ async def init_agent( This endpoint is idempotent: - If the agent name doesn't exist, creates a new agent - - If the agent name exists with the same UUID, updates registration data - - If the agent name exists with a different UUID, returns 409 Conflict - - If the UUID exists with a different name, returns 409 Conflict (no renames) + - If the agent name exists, updates registration data in place conflict_mode controls registration conflict handling: - strict (default): preserve compatibility checks and conflict errors @@ -408,10 +398,6 @@ async def init_agent( Returns: InitAgentResponse with created flag and active controls (if policy assigned) - - Raises: - HTTPException 409: Agent name exists with different UUID - HTTPException 500: Database error during creation/update """ # Check for evaluator name collisions with built-in evaluators builtin_names = _get_builtin_evaluator_names() @@ -457,42 +443,8 @@ async def init_agent( ) incoming_steps_by_key[step_key] = step - # Look up by UUID first (primary key) - result = await db.execute(select(Agent).where(Agent.agent_uuid == request.agent.agent_id)) - existing_by_uuid: Agent | None = result.scalars().first() - - # Perf optimization: If UUID exists, skip name query on hot path - if existing_by_uuid is not None: - # Validate name hasn't changed (no rename via initAgent) - if existing_by_uuid.name != request.agent.agent_name: - raise ConflictError( - error_code=ErrorCode.AGENT_NAME_CONFLICT, - detail=( - f"Agent ID '{request.agent.agent_id}' is already registered " - f"with name '{existing_by_uuid.name}'" - ), - resource="Agent", - resource_id=str(request.agent.agent_id), - hint="Use the existing agent name for this UUID or register a new UUID.", - errors=[ - ValidationErrorItem( - resource="Agent", - field="agent_name", - code="name_mismatch", - message=( - f"Agent ID '{request.agent.agent_id}' is already associated " - f"with name '{existing_by_uuid.name}'" - ), - value=request.agent.agent_name, - ) - ], - ) - existing: Agent | None = existing_by_uuid - else: - # UUID doesn't exist, check if name is taken by different agent - result = await db.execute(select(Agent).where(Agent.name == request.agent.agent_name)) - existing_by_name: Agent | None = result.scalars().first() - existing = existing_by_name + result = await db.execute(select(Agent).where(Agent.name == request.agent.agent_name)) + existing: Agent | None = result.scalars().first() created = False @@ -507,7 +459,6 @@ async def init_agent( new_agent = Agent( name=request.agent.agent_name, - agent_uuid=request.agent.agent_id, data=data_model.model_dump(mode="json"), ) db.add(new_agent) @@ -520,7 +471,7 @@ async def init_agent( except Exception: await db.rollback() _logger.error( - f"Failed to create agent '{request.agent.agent_name}' ({request.agent.agent_id})", + f"Failed to create agent '{request.agent.agent_name}' ({request.agent.agent_name})", exc_info=True, ) raise DatabaseError( @@ -530,26 +481,6 @@ async def init_agent( ) return InitAgentResponse(created=created, controls=[]) - requested_uuid = request.agent.agent_id - if existing.agent_uuid != requested_uuid: - # UUID mismatch for the same name: return error - raise ConflictError( - error_code=ErrorCode.AGENT_UUID_CONFLICT, - detail=f"Agent name '{request.agent.agent_name}' already exists with different UUID", - resource="Agent", - resource_id=request.agent.agent_name, - hint="Use the existing agent's UUID or choose a different agent name.", - errors=[ - ValidationErrorItem( - resource="Agent", - field="agent_id", - code="uuid_mismatch", - message=f"Agent '{request.agent.agent_name}' exists with a different UUID", - value=str(requested_uuid), - ) - ], - ) - # Parse existing data via AgentData Pydantic model try: data_model = AgentData.model_validate(existing.data) @@ -788,7 +719,7 @@ async def init_agent( except Exception: await db.rollback() _logger.error( - f"Failed to update agent '{request.agent.agent_name}' ({request.agent.agent_id})", + f"Failed to update agent '{request.agent.agent_name}' ({request.agent.agent_name})", exc_info=True, ) raise DatabaseError( @@ -800,7 +731,7 @@ async def init_agent( # If the existing agent has a policy, include its controls; otherwise empty list controls = [] if existing.policy_id is not None: - controls = await list_controls_for_agent(existing.agent_uuid, db) + controls = await list_controls_for_agent(existing.name, db) return InitAgentResponse( created=created, @@ -811,19 +742,19 @@ async def init_agent( @router.get( - "/{agent_id}", + "/{agent_name}", response_model=GetAgentResponse, summary="Get agent details", response_description="Agent metadata and registered steps", ) -async def get_agent(agent_id: UUID, db: AsyncSession = Depends(get_async_db)) -> GetAgentResponse: +async def get_agent(agent_name: str, db: AsyncSession = Depends(get_async_db)) -> GetAgentResponse: """ Retrieve agent metadata and all registered steps. Returns the latest version of each step (deduplicated by type+name). Args: - agent_id: UUID of the agent + agent_name: Agent identifier db: Database session (injected) Returns: @@ -833,22 +764,25 @@ async def get_agent(agent_id: UUID, db: AsyncSession = Depends(get_async_db)) -> HTTPException 404: Agent not found HTTPException 422: Agent data is corrupted """ - result = await db.execute(select(Agent).where(Agent.agent_uuid == agent_id)) + result = await db.execute(select(Agent).where(Agent.name == agent_name)) existing: Agent | None = result.scalars().first() if existing is None: raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent with ID '{agent_id}' not found", + detail=f"Agent with name '{agent_name}' not found", resource="Agent", - resource_id=str(agent_id), - hint="Verify the agent ID is correct and the agent has been registered via initAgent.", + resource_id=str(agent_name), + hint=( + "Verify the agent name is correct and the agent has been " + "registered via initAgent." + ), ) try: data_model = AgentData.model_validate(existing.data) except ValidationError: _logger.error( - f"Failed to parse agent data for agent '{existing.name}' ({agent_id})", + f"Failed to parse agent data for agent '{existing.name}' ({agent_name})", exc_info=True, ) raise APIValidationError( @@ -867,7 +801,7 @@ async def get_agent(agent_id: UUID, db: AsyncSession = Depends(get_async_db)) -> agent_meta = APIAgent.model_validate(data_model.agent_metadata) except ValidationError: _logger.error( - f"Failed to parse agent metadata for agent '{existing.name}' ({agent_id})", + f"Failed to parse agent metadata for agent '{existing.name}' ({agent_name})", exc_info=True, ) raise APIValidationError( @@ -883,13 +817,13 @@ async def get_agent(agent_id: UUID, db: AsyncSession = Depends(get_async_db)) -> @router.post( - "/{agent_id}/policy/{policy_id}", + "/{agent_name}/policy/{policy_id}", response_model=SetPolicyResponse, summary="Assign policy to agent", response_description="Success status with previous policy ID", ) async def set_agent_policy( - agent_id: UUID, policy_id: int, db: AsyncSession = Depends(get_async_db) + agent_name: str, policy_id: int, db: AsyncSession = Depends(get_async_db) ) -> SetPolicyResponse: """ Assign a policy to an agent, replacing any existing policy assignment. @@ -897,7 +831,7 @@ async def set_agent_policy( The agent will immediately inherit all controls from the assigned policy. Args: - agent_id: UUID of the agent + agent_name: Agent identifier policy_id: ID of the policy to assign db: Database session (injected) @@ -909,15 +843,15 @@ async def set_agent_policy( HTTPException 500: Database error during assignment """ # Find agent - result = await db.execute(select(Agent).where(Agent.agent_uuid == agent_id)) + result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent with ID '{agent_id}' not found", + detail=f"Agent with name '{agent_name}' not found", resource="Agent", - resource_id=str(agent_id), - hint="Verify the agent ID is correct and the agent has been registered.", + resource_id=str(agent_name), + hint="Verify the agent name is correct and the agent has been registered.", ) # Find policy by id @@ -962,7 +896,7 @@ async def set_agent_policy( except Exception: await db.rollback() _logger.error( - f"Failed to assign policy '{policy_id}' to agent '{agent.name}' ({agent_id})", + f"Failed to assign policy '{policy_id}' to agent '{agent.name}' ({agent_name})", exc_info=True, ) raise DatabaseError( @@ -975,19 +909,19 @@ async def set_agent_policy( @router.get( - "/{agent_id}/policy", + "/{agent_name}/policy", response_model=GetPolicyResponse, summary="Get agent's assigned policy", response_description="Policy ID", ) async def get_agent_policy( - agent_id: UUID, db: AsyncSession = Depends(get_async_db) + agent_name: str, db: AsyncSession = Depends(get_async_db) ) -> GetPolicyResponse: """ Retrieve the policy currently assigned to an agent. Args: - agent_id: UUID of the agent + agent_name: Agent identifier db: Database session (injected) Returns: @@ -997,15 +931,15 @@ async def get_agent_policy( HTTPException 404: Agent not found or agent has no policy assigned """ # Find agent - result = await db.execute(select(Agent).where(Agent.agent_uuid == agent_id)) + result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent with ID '{agent_id}' not found", + detail=f"Agent with name '{agent_name}' not found", resource="Agent", - resource_id=str(agent_id), - hint="Verify the agent ID is correct and the agent has been registered.", + resource_id=str(agent_name), + hint="Verify the agent name is correct and the agent has been registered.", ) # Check if agent has a policy @@ -1014,7 +948,7 @@ async def get_agent_policy( error_code=ErrorCode.POLICY_NOT_FOUND, detail=f"Agent '{agent.name}' has no policy assigned", resource="Policy", - hint="Assign a policy to the agent using POST /{agent_id}/policy/{policy_id}.", + hint="Assign a policy to the agent using POST /{agent_name}/policy/{policy_id}.", ) # Find policy @@ -1036,13 +970,13 @@ async def get_agent_policy( @router.delete( - "/{agent_id}/policy", + "/{agent_name}/policy", response_model=DeletePolicyResponse, summary="Remove agent's policy assignment", response_description="Success confirmation", ) async def delete_agent_policy( - agent_id: UUID, db: AsyncSession = Depends(get_async_db) + agent_name: str, db: AsyncSession = Depends(get_async_db) ) -> DeletePolicyResponse: """ Remove the policy assignment from an agent. @@ -1050,7 +984,7 @@ async def delete_agent_policy( The agent will no longer have any protection controls active. Args: - agent_id: UUID of the agent + agent_name: Agent identifier db: Database session (injected) Returns: @@ -1061,15 +995,15 @@ async def delete_agent_policy( HTTPException 500: Database error during removal """ # Find agent - result = await db.execute(select(Agent).where(Agent.agent_uuid == agent_id)) + result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent with ID '{agent_id}' not found", + detail=f"Agent with name '{agent_name}' not found", resource="Agent", - resource_id=str(agent_id), - hint="Verify the agent ID is correct and the agent has been registered.", + resource_id=str(agent_name), + hint="Verify the agent name is correct and the agent has been registered.", ) # Check if agent has a policy @@ -1088,7 +1022,7 @@ async def delete_agent_policy( except Exception: await db.rollback() _logger.error( - f"Failed to remove policy from agent '{agent.name}' ({agent_id})", + f"Failed to remove policy from agent '{agent.name}' ({agent_name})", exc_info=True, ) raise DatabaseError( @@ -1101,13 +1035,13 @@ async def delete_agent_policy( @router.get( - "/{agent_id}/controls", + "/{agent_name}/controls", response_model=AgentControlsResponse, summary="List agent's active controls", response_description="List of controls from agent's policy", ) async def list_agent_controls( - agent_id: UUID, db: AsyncSession = Depends(get_async_db) + agent_name: str, db: AsyncSession = Depends(get_async_db) ) -> AgentControlsResponse: """ List all protection controls active for an agent. @@ -1116,7 +1050,7 @@ async def list_agent_controls( Returns an empty list if the agent has no policy. Args: - agent_id: UUID of the agent + agent_name: Agent identifier db: Database session (injected) Returns: @@ -1125,21 +1059,21 @@ async def list_agent_controls( Raises: HTTPException 404: Agent not found """ - result = await db.execute(select(Agent).where(Agent.agent_uuid == agent_id)) + result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent with ID '{agent_id}' not found", + detail=f"Agent with name '{agent_name}' not found", resource="Agent", - resource_id=str(agent_id), - hint="Verify the agent ID is correct and the agent has been registered.", + resource_id=str(agent_name), + hint="Verify the agent name is correct and the agent has been registered.", ) if agent.policy_id is None: return AgentControlsResponse(controls=[]) - controls = await list_controls_for_agent(agent_id, db) + controls = await list_controls_for_agent(agent_name, db) return AgentControlsResponse(controls=controls) @@ -1166,13 +1100,13 @@ class ListEvaluatorsResponse(BaseModel): @router.get( - "/{agent_id}/evaluators", + "/{agent_name}/evaluators", response_model=ListEvaluatorsResponse, summary="List agent's registered evaluator schemas", response_description="Evaluator schemas registered with this agent", ) async def list_agent_evaluators( - agent_id: UUID, + agent_name: str, cursor: str | None = None, limit: int = _DEFAULT_PAGINATION_LIMIT, db: AsyncSession = Depends(get_async_db), @@ -1185,7 +1119,7 @@ async def list_agent_evaluators( - UI to display available config options Args: - agent_id: UUID of the agent + agent_name: Agent identifier cursor: Optional cursor for pagination (name of last evaluator from previous page) limit: Pagination limit (default 20, max 100) db: Database session (injected) @@ -1199,15 +1133,15 @@ async def list_agent_evaluators( # Clamp limit limit = min(max(1, limit), _MAX_PAGINATION_LIMIT) - result = await db.execute(select(Agent).where(Agent.agent_uuid == agent_id)) + result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent with ID '{agent_id}' not found", + detail=f"Agent with name '{agent_name}' not found", resource="Agent", - resource_id=str(agent_id), - hint="Verify the agent ID is correct and the agent has been registered.", + resource_id=str(agent_name), + hint="Verify the agent name is correct and the agent has been registered.", ) try: @@ -1261,13 +1195,13 @@ async def list_agent_evaluators( @router.get( - "/{agent_id}/evaluators/{evaluator_name}", + "/{agent_name}/evaluators/{evaluator_name}", response_model=EvaluatorSchemaItem, summary="Get specific evaluator schema", response_description="Evaluator schema details", ) async def get_agent_evaluator( - agent_id: UUID, + agent_name: str, evaluator_name: str, db: AsyncSession = Depends(get_async_db), ) -> EvaluatorSchemaItem: @@ -1275,7 +1209,7 @@ async def get_agent_evaluator( Get a specific evaluator schema registered with an agent. Args: - agent_id: UUID of the agent + agent_name: Agent identifier evaluator_name: Name of the evaluator db: Database session (injected) @@ -1285,15 +1219,15 @@ async def get_agent_evaluator( Raises: HTTPException 404: Agent or evaluator not found """ - result = await db.execute(select(Agent).where(Agent.agent_uuid == agent_id)) + result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent with ID '{agent_id}' not found", + detail=f"Agent with name '{agent_name}' not found", resource="Agent", - resource_id=str(agent_id), - hint="Verify the agent ID is correct and the agent has been registered.", + resource_id=str(agent_name), + hint="Verify the agent name is correct and the agent has been registered.", ) try: @@ -1325,13 +1259,13 @@ async def get_agent_evaluator( @router.patch( - "/{agent_id}", + "/{agent_name}", response_model=PatchAgentResponse, summary="Modify agent (remove steps/evaluators)", response_description="Lists of removed items", ) async def patch_agent( - agent_id: UUID, + agent_name: str, request: PatchAgentRequest, db: AsyncSession = Depends(get_async_db), ) -> PatchAgentResponse: @@ -1342,7 +1276,7 @@ async def patch_agent( Removals are idempotent - attempting to remove non-existent items is not an error. Args: - agent_id: UUID of the agent + agent_name: Agent identifier request: Lists of step/evaluator identifiers to remove db: Database session (injected) @@ -1353,15 +1287,15 @@ async def patch_agent( HTTPException 404: Agent not found HTTPException 500: Database error during update """ - result = await db.execute(select(Agent).where(Agent.agent_uuid == agent_id)) + result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent with ID '{agent_id}' not found", + detail=f"Agent with name '{agent_name}' not found", resource="Agent", - resource_id=str(agent_id), - hint="Verify the agent ID is correct and the agent has been registered.", + resource_id=str(agent_name), + hint="Verify the agent name is correct and the agent has been registered.", ) try: @@ -1398,7 +1332,7 @@ async def patch_agent( # Check if any controls reference evaluators being removed if agent.policy_id is not None: # Get all controls for this agent's policy - controls = await list_controls_for_agent(agent.agent_uuid, db) + controls = await list_controls_for_agent(agent.name, db) referencing_controls: list[tuple[str, str]] = [] # (control_name, evaluator) for ctrl in controls: @@ -1447,7 +1381,7 @@ async def patch_agent( except Exception: await db.rollback() _logger.error( - f"Failed to patch agent '{agent.name}' ({agent_id})", + f"Failed to patch agent '{agent.name}' ({agent_name})", exc_info=True, ) raise DatabaseError( diff --git a/server/src/agent_control_server/endpoints/controls.py b/server/src/agent_control_server/endpoints/controls.py index e4977120..97a85f17 100644 --- a/server/src/agent_control_server/endpoints/controls.py +++ b/server/src/agent_control_server/endpoints/controls.py @@ -601,7 +601,6 @@ async def list_controls( agents_query = ( select( policy_controls.c.control_id, - Agent.agent_uuid, Agent.name, ) .select_from(policy_controls) @@ -611,12 +610,10 @@ async def list_controls( ) agents_result = await db.execute(agents_query) for row in agents_result.all(): - control_id, agent_uuid, agent_name = row + control_id, agent_name = row # Take the first agent found (1 control = 1 agent) if control_agent_map[control_id] is None: - control_agent_map[control_id] = AgentRef( - agent_id=str(agent_uuid), agent_name=agent_name - ) + control_agent_map[control_id] = AgentRef(agent_name=agent_name) # Build summaries (filtering already done at DB level) summaries: list[ControlSummary] = [] diff --git a/server/src/agent_control_server/endpoints/evaluation.py b/server/src/agent_control_server/endpoints/evaluation.py index 6938bdae..34c28c5d 100644 --- a/server/src/agent_control_server/endpoints/evaluation.py +++ b/server/src/agent_control_server/endpoints/evaluation.py @@ -141,22 +141,22 @@ async def evaluate( # Fetch agent to get the name agent_result = await db.execute( - select(Agent).where(Agent.agent_uuid == request.agent_uuid) + select(Agent).where(Agent.name == request.agent_name) ) agent = agent_result.scalar_one_or_none() if agent is None: raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent '{request.agent_uuid}' not found", + detail=f"Agent '{request.agent_name}' not found", resource="Agent", - resource_id=str(request.agent_uuid), + resource_id=request.agent_name, hint="Register the agent via initAgent before evaluating.", ) agent_name = agent.name # Fetch controls for the agent (already validated as ControlDefinition) api_controls = await list_controls_for_agent( - request.agent_uuid, + request.agent_name, db, allow_invalid_step_name_regex=True, ) @@ -244,7 +244,6 @@ async def _emit_observability_events( control_execution_id=match.control_execution_id, trace_id=trace_id, span_id=span_id, - agent_uuid=request.agent_uuid, agent_name=agent_name, control_id=match.control_id, control_name=match.control_name, @@ -270,7 +269,6 @@ async def _emit_observability_events( control_execution_id=error.control_execution_id, trace_id=trace_id, span_id=span_id, - agent_uuid=request.agent_uuid, agent_name=agent_name, control_id=error.control_id, control_name=error.control_name, @@ -296,7 +294,6 @@ async def _emit_observability_events( control_execution_id=non_match.control_execution_id, trace_id=trace_id, span_id=span_id, - agent_uuid=request.agent_uuid, agent_name=agent_name, control_id=non_match.control_id, control_name=non_match.control_name, diff --git a/server/src/agent_control_server/endpoints/evaluators.py b/server/src/agent_control_server/endpoints/evaluators.py index 99f7be27..a9cdaa2a 100644 --- a/server/src/agent_control_server/endpoints/evaluators.py +++ b/server/src/agent_control_server/endpoints/evaluators.py @@ -38,7 +38,7 @@ async def get_evaluators() -> dict[str, EvaluatorInfo]: - **sql**: SQL query validation Custom evaluators are registered per-agent via initAgent. - Use GET /agents/{agent_id}/evaluators to list agent-specific schemas. + Use GET /agents/{agent_name}/evaluators to list agent-specific schemas. """ evaluators = list_evaluators() diff --git a/server/src/agent_control_server/endpoints/observability.py b/server/src/agent_control_server/endpoints/observability.py index 61f7bd4c..ceb928a3 100644 --- a/server/src/agent_control_server/endpoints/observability.py +++ b/server/src/agent_control_server/endpoints/observability.py @@ -15,7 +15,6 @@ import logging import time from typing import Literal, cast -from uuid import UUID from agent_control_models import ( BatchEventsRequest, @@ -133,7 +132,7 @@ async def query_events( - trace_id: Get all events for a request - span_id: Get all events for a function call - control_execution_id: Get a specific event - - agent_uuid: Filter by agent + - agent_name: Filter by agent - control_ids: Filter by controls - actions: Filter by actions (allow, deny, warn, log) - matched: Filter by matched status @@ -160,7 +159,7 @@ async def query_events( @router.get("/stats", response_model=StatsResponse) async def get_stats( - agent_uuid: UUID, + agent_name: str, time_range: TimeRange = "5m", include_timeseries: bool = False, store: EventStore = Depends(get_event_store), @@ -172,7 +171,7 @@ async def get_stats( Use /stats/controls/{control_id} for single control stats. Args: - agent_uuid: Agent to get stats for + agent_name: Agent to get stats for time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) include_timeseries: Include time-series data points for trend visualization store: Event store (injected) @@ -184,7 +183,7 @@ async def get_stats( bucket_size = get_bucket_size(time_range) if include_timeseries else None result = await store.query_stats( - agent_uuid, + agent_name, interval, control_id=None, include_timeseries=include_timeseries, @@ -192,7 +191,7 @@ async def get_stats( ) return StatsResponse( - agent_uuid=agent_uuid, + agent_name=agent_name, time_range=time_range, totals=StatsTotals( execution_count=result.total_executions, @@ -209,7 +208,7 @@ async def get_stats( @router.get("/stats/controls/{control_id}", response_model=ControlStatsResponse) async def get_control_stats( control_id: int, - agent_uuid: UUID, + agent_name: str, time_range: TimeRange = "5m", include_timeseries: bool = False, store: EventStore = Depends(get_event_store), @@ -221,7 +220,7 @@ async def get_control_stats( Args: control_id: Control ID to get stats for - agent_uuid: Agent to get stats for + agent_name: Agent to get stats for time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) include_timeseries: Include time-series data points for trend visualization store: Event store (injected) @@ -233,7 +232,7 @@ async def get_control_stats( bucket_size = get_bucket_size(time_range) if include_timeseries else None result = await store.query_stats( - agent_uuid, + agent_name, interval, control_id=control_id, include_timeseries=include_timeseries, @@ -244,7 +243,7 @@ async def get_control_stats( control_name = result.stats[0].control_name if result.stats else f"control-{control_id}" return ControlStatsResponse( - agent_uuid=agent_uuid, + agent_name=agent_name, time_range=time_range, control_id=control_id, control_name=control_name, diff --git a/server/src/agent_control_server/errors.py b/server/src/agent_control_server/errors.py index 32d48547..94af5890 100644 --- a/server/src/agent_control_server/errors.py +++ b/server/src/agent_control_server/errors.py @@ -10,9 +10,9 @@ # Raise a not found error raise NotFoundError( error_code=ErrorCode.AGENT_NOT_FOUND, - detail=f"Agent with ID '{agent_id}' not found", + detail=f"Agent with name '{agent_name}' not found", resource="Agent", - resource_id=str(agent_id), + resource_id=agent_name, ) # Raise a validation error with field-level details diff --git a/server/src/agent_control_server/main.py b/server/src/agent_control_server/main.py index e40d171e..3be55738 100644 --- a/server/src/agent_control_server/main.py +++ b/server/src/agent_control_server/main.py @@ -136,7 +136,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 2. Create controls with `/api/v1/controls` and configure them 3. Create a policy and add controls to it 4. Assign the policy to your agent -5. Query agent's active controls with `/api/v1/agents/{agent_id}/controls` +5. Query agent's active controls with `/api/v1/agents/{agent_name}/controls` """, version="0.1.0", lifespan=lifespan, diff --git a/server/src/agent_control_server/models.py b/server/src/agent_control_server/models.py index 093a3b17..bf842b39 100644 --- a/server/src/agent_control_server/models.py +++ b/server/src/agent_control_server/models.py @@ -1,5 +1,4 @@ import datetime as dt -import uuid as _uuid from typing import Any, Optional from agent_control_models.agent import StepSchema @@ -17,7 +16,6 @@ text, ) from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.dialects.postgresql import UUID as PG_UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from .db import Base @@ -90,10 +88,7 @@ class EvaluatorConfigDB(Base): class Agent(Base): __tablename__ = "agents" - agent_uuid: Mapped[_uuid.UUID] = mapped_column( - PG_UUID(as_uuid=True), primary_key=True - ) - name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) + name: Mapped[str] = mapped_column(String(255), primary_key=True) data: Mapped[dict[str, Any]] = mapped_column( JSONB, server_default=text("'{}'::jsonb"), nullable=False ) @@ -116,12 +111,12 @@ class ControlExecutionEventDB(Base): Raw control execution events with minimal indexed columns + JSONB. Schema designed for simplicity and flexibility: - - Only 4 columns: control_execution_id, timestamp, agent_uuid, data + - Only 4 columns: control_execution_id, timestamp, agent_name, data - Full event stored in JSONB 'data' column - Query-time aggregation from JSONB fields - No migrations needed for new event fields - Primary access pattern: (agent_uuid, timestamp DESC) for stats queries. + Primary access pattern: (agent_name, timestamp DESC) for stats queries. Expression index on (data->>'control_id') for grouping. """ @@ -138,9 +133,7 @@ class ControlExecutionEventDB(Base): server_default=text("CURRENT_TIMESTAMP"), nullable=False, ) - agent_uuid: Mapped[_uuid.UUID] = mapped_column( - PG_UUID(as_uuid=True), nullable=False, - ) + agent_name: Mapped[str] = mapped_column(String(255), nullable=False) # Full event data as JSONB data: Mapped[dict[str, Any]] = mapped_column( @@ -149,5 +142,5 @@ class ControlExecutionEventDB(Base): # Composite index for agent + time queries (primary access pattern) __table_args__ = ( - Index("ix_events_agent_time", "agent_uuid", timestamp.desc()), + Index("ix_events_agent_time", "agent_name", timestamp.desc()), ) diff --git a/server/src/agent_control_server/observability/ingest/direct.py b/server/src/agent_control_server/observability/ingest/direct.py index 081cd4be..b2b09e6d 100644 --- a/server/src/agent_control_server/observability/ingest/direct.py +++ b/server/src/agent_control_server/observability/ingest/direct.py @@ -95,7 +95,6 @@ def _log_events(self, events: list[ControlExecutionEvent]) -> None: "event_type": "control_execution", "trace_id": event.trace_id, "span_id": event.span_id, - "agent_uuid": str(event.agent_uuid), "agent_name": event.agent_name, "control_id": event.control_id, "control_name": event.control_name, diff --git a/server/src/agent_control_server/observability/store/base.py b/server/src/agent_control_server/observability/store/base.py index c8bbd712..c73de99c 100644 --- a/server/src/agent_control_server/observability/store/base.py +++ b/server/src/agent_control_server/observability/store/base.py @@ -15,7 +15,6 @@ from abc import ABC, abstractmethod from datetime import timedelta from typing import Literal -from uuid import UUID from agent_control_models.observability import ( ControlExecutionEvent, @@ -134,7 +133,7 @@ async def store(self, events: list[ControlExecutionEvent]) -> int: @abstractmethod async def query_stats( self, - agent_uuid: UUID, + agent_name: str, time_range: timedelta, control_id: int | None = None, include_timeseries: bool = False, @@ -143,7 +142,7 @@ async def query_stats( """Query stats (aggregated at query time from raw events). Args: - agent_uuid: UUID of the agent to query stats for + agent_name: Identifier of the agent to query stats for time_range: Time range to aggregate over (from now) control_id: Optional control ID to filter by include_timeseries: Whether to include time-series data diff --git a/server/src/agent_control_server/observability/store/postgres.py b/server/src/agent_control_server/observability/store/postgres.py index 8838082c..997d8c1b 100644 --- a/server/src/agent_control_server/observability/store/postgres.py +++ b/server/src/agent_control_server/observability/store/postgres.py @@ -12,7 +12,6 @@ import json import logging from datetime import UTC, datetime, timedelta -from uuid import UUID from agent_control_models.observability import ( ControlExecutionEvent, @@ -84,7 +83,7 @@ class PostgresEventStore(EventStore): """PostgreSQL-based event store with JSONB storage and query-time aggregation. This implementation stores raw events with: - - Indexed columns (control_execution_id, timestamp, agent_uuid) for efficient filtering + - Indexed columns (control_execution_id, timestamp, agent_name) for efficient filtering - JSONB 'data' column containing the full event for flexible querying Stats are computed at query time from raw events, which is fast enough @@ -109,7 +108,7 @@ async def store(self, events: list[ControlExecutionEvent]) -> int: The simplified schema stores only 4 columns: - control_execution_id (PK) - timestamp (indexed) - - agent_uuid (indexed) + - agent_name (indexed) - data (JSONB containing full event) Args: @@ -130,7 +129,7 @@ async def store(self, events: list[ControlExecutionEvent]) -> int: values.append({ "control_execution_id": event.control_execution_id, "timestamp": event.timestamp, - "agent_uuid": event.agent_uuid, + "agent_name": event.agent_name, "data": json.dumps(event_data), }) @@ -139,9 +138,9 @@ async def store(self, events: list[ControlExecutionEvent]) -> int: await session.execute( text(""" INSERT INTO control_execution_events ( - control_execution_id, timestamp, agent_uuid, data + control_execution_id, timestamp, agent_name, data ) VALUES ( - :control_execution_id, :timestamp, :agent_uuid, + :control_execution_id, :timestamp, :agent_name, CAST(:data AS JSONB) ) ON CONFLICT (control_execution_id) DO NOTHING @@ -155,7 +154,7 @@ async def store(self, events: list[ControlExecutionEvent]) -> int: async def query_stats( self, - agent_uuid: UUID, + agent_name: str, time_range: timedelta, control_id: int | None = None, include_timeseries: bool = False, @@ -170,7 +169,7 @@ async def query_stats( and LEFT JOINs with aggregated data to include empty buckets. Args: - agent_uuid: UUID of the agent to query stats for + agent_name: identifier of the agent to query stats for time_range: Time range to aggregate over (from now) control_id: Optional control ID to filter by include_timeseries: Whether to include time-series data @@ -183,7 +182,7 @@ async def query_stats( cutoff = now - time_range params: dict = { - "agent_uuid": agent_uuid, + "agent_name": agent_name, "cutoff": cutoff, } @@ -204,7 +203,7 @@ async def query_stats( WITH filtered_events AS ( SELECT timestamp, data FROM control_execution_events - WHERE agent_uuid = :agent_uuid + WHERE agent_name = :agent_name AND timestamp >= :cutoff {control_filter} ), @@ -274,7 +273,7 @@ async def query_stats( NULL::timestamptz as bucket, {SQL_STATS_AGGREGATIONS} FROM control_execution_events - WHERE agent_uuid = :agent_uuid + WHERE agent_name = :agent_name AND timestamp >= :cutoff {control_filter} GROUP BY data->>'control_id', data->>'control_name' @@ -384,11 +383,11 @@ def _timedelta_to_interval(self, td: timedelta) -> str: async def query_events(self, query: EventQueryRequest) -> EventQueryResponse: """Query raw events with filters and pagination. - Supports filtering by trace_id, span_id, agent_uuid, control_ids, + Supports filtering by trace_id, span_id, agent_name, control_ids, actions, matched status, time range, and pagination. Filters use JSONB operators for fields stored in the 'data' column, - except for indexed columns (control_execution_id, timestamp, agent_uuid). + except for indexed columns (control_execution_id, timestamp, agent_name). Args: query: Query parameters (filters, pagination) @@ -405,9 +404,9 @@ async def query_events(self, query: EventQueryRequest) -> EventQueryResponse: where_clauses.append("control_execution_id = :control_execution_id") params["control_execution_id"] = query.control_execution_id - if query.agent_uuid: - where_clauses.append("agent_uuid = :agent_uuid") - params["agent_uuid"] = query.agent_uuid + if query.agent_name: + where_clauses.append("agent_name = :agent_name") + params["agent_name"] = query.agent_name if query.start_time: where_clauses.append("timestamp >= :start_time") diff --git a/server/src/agent_control_server/services/controls.py b/server/src/agent_control_server/services/controls.py index f7867081..46f9987d 100644 --- a/server/src/agent_control_server/services/controls.py +++ b/server/src/agent_control_server/services/controls.py @@ -2,7 +2,6 @@ import logging from collections.abc import Sequence -from uuid import UUID from agent_control_models import ControlDefinition from agent_control_models.errors import ErrorCode, ValidationErrorItem @@ -29,7 +28,7 @@ async def list_controls_for_policy(policy_id: int, db: AsyncSession) -> list[Con async def list_controls_for_agent( - agent_id: UUID, + agent_name: str, db: AsyncSession, *, allow_invalid_step_name_regex: bool = False, @@ -46,7 +45,7 @@ async def list_controls_for_agent( .join(policy_controls, Control.id == policy_controls.c.control_id) .join(Policy, policy_controls.c.policy_id == Policy.id) .join(Agent, Policy.id == Agent.policy_id) - .where(Agent.agent_uuid == agent_id) + .where(Agent.name == agent_name) ) result = await db.execute(stmt) diff --git a/server/src/agent_control_server/services/schema_compat.py b/server/src/agent_control_server/services/schema_compat.py index 796ea41f..99755bc9 100644 --- a/server/src/agent_control_server/services/schema_compat.py +++ b/server/src/agent_control_server/services/schema_compat.py @@ -130,5 +130,5 @@ def format_compatibility_error(evaluator_name: str, errors: list[str]) -> str: return ( f"Evaluator '{evaluator_name}' schema change is not backward compatible. " f"Changes detected: {error_list}. " - f"To make breaking changes, create a new agent with a different UUID/name." + "To make breaking changes, create a new agent with a different name." ) diff --git a/server/tests/test_agents_additional.py b/server/tests/test_agents_additional.py index 13daec4e..2297e9a5 100644 --- a/server/tests/test_agents_additional.py +++ b/server/tests/test_agents_additional.py @@ -19,11 +19,11 @@ def _init_agent( steps: list[dict] | None = None, evaluators: list[dict] | None = None, ) -> tuple[str, str]: - aid = agent_id or str(uuid.uuid4()) - name = agent_name or f"Agent-{uuid.uuid4().hex[:6]}" + name = (agent_name or agent_id or f"agent-{uuid.uuid4().hex[:12]}").lower() + if len(name) < 10: + name = f"{name}-agent".replace("--", "-") payload = { "agent": { - "agent_id": aid, "agent_name": name, "agent_description": "desc", "agent_version": "1.0", @@ -33,7 +33,7 @@ def _init_agent( } resp = client.post("/api/v1/agents/initAgent", json=payload) assert resp.status_code == 200 - return aid, name + return name, name def _create_control_with_data(client: TestClient, data: dict) -> int: @@ -282,7 +282,7 @@ def test_init_agent_rejects_builtin_evaluator_name(client: TestClient) -> None: payload = { "agent": { "agent_id": str(uuid.uuid4()), - "agent_name": f"Agent-{uuid.uuid4().hex[:6]}", + "agent_name": f"agent-{uuid.uuid4().hex[:12]}", "agent_description": "desc", "agent_version": "1.0", }, @@ -300,16 +300,14 @@ def test_init_agent_rejects_builtin_evaluator_name(client: TestClient) -> None: assert resp.json()["error_code"] == "EVALUATOR_NAME_CONFLICT" -def test_init_agent_uuid_conflict_on_same_name(client: TestClient) -> None: +def test_init_agent_same_name_is_idempotent(client: TestClient) -> None: # Given: an existing agent with a specific name - name = f"Agent-{uuid.uuid4().hex[:6]}" - agent_id = str(uuid.uuid4()) - _init_agent(client, agent_id=agent_id, agent_name=name) + name = f"agent-{uuid.uuid4().hex[:12]}" + _init_agent(client, agent_name=name) - # When: re-registering with the same name but a different UUID + # When: re-registering with the same name payload = { "agent": { - "agent_id": str(uuid.uuid4()), "agent_name": name, "agent_description": "desc", "agent_version": "1.0", @@ -318,21 +316,19 @@ def test_init_agent_uuid_conflict_on_same_name(client: TestClient) -> None: } resp = client.post("/api/v1/agents/initAgent", json=payload) - # Then: UUID conflict is returned - assert resp.status_code == 409 - assert resp.json()["error_code"] == "AGENT_UUID_CONFLICT" + # Then: request is idempotent + assert resp.status_code == 200 + assert resp.json()["created"] is False -def test_init_agent_name_conflict_on_same_uuid(client: TestClient) -> None: - # Given: an existing agent with a specific UUID - agent_id = str(uuid.uuid4()) - original_name = f"Agent-{uuid.uuid4().hex[:6]}" - _init_agent(client, agent_id=agent_id, agent_name=original_name) +def test_init_agent_different_name_creates_new_agent(client: TestClient) -> None: + # Given: an existing agent + original_name = f"agent-{uuid.uuid4().hex[:12]}" + _init_agent(client, agent_name=original_name) - # When: re-registering with the same UUID but a different name + # When: registering another agent with a different name payload = { "agent": { - "agent_id": agent_id, "agent_name": f"{original_name}-renamed", "agent_description": "desc", "agent_version": "1.0", @@ -341,9 +337,9 @@ def test_init_agent_name_conflict_on_same_uuid(client: TestClient) -> None: } resp = client.post("/api/v1/agents/initAgent", json=payload) - # Then: name conflict is returned - assert resp.status_code == 409 - assert resp.json()["error_code"] == "AGENT_NAME_CONFLICT" + # Then: a new agent is created + assert resp.status_code == 200 + assert resp.json()["created"] is True def test_list_agent_controls_corrupted_control_data_returns_422( @@ -377,8 +373,8 @@ def test_list_agent_controls_corrupted_control_data_returns_422( def test_list_agents_invalid_cursor_returns_first_page(client: TestClient) -> None: # Given: two agents - _init_agent(client, agent_name=f"Agent-{uuid.uuid4().hex[:6]}") - _init_agent(client, agent_name=f"Agent-{uuid.uuid4().hex[:6]}") + _init_agent(client, agent_name=f"agent-{uuid.uuid4().hex[:12]}") + _init_agent(client, agent_name=f"agent-{uuid.uuid4().hex[:12]}") # When: listing agents without cursor resp = client.get("/api/v1/agents") @@ -400,7 +396,7 @@ def test_list_agent_evaluators_corrupted_data_returns_empty(client: TestClient) agent_id, _ = _init_agent(client, evaluators=[{"name": "eval-a", "config_schema": {}}]) with engine.begin() as conn: conn.execute( - text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE agent_uuid = :id"), + text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE name = :id"), {"data": "{\"bad\": \"data\"}", "id": agent_id}, ) @@ -424,7 +420,7 @@ def test_set_agent_policy_rejects_corrupted_agent_data(client: TestClient) -> No with engine.begin() as conn: conn.execute( - text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE agent_uuid = :id"), + text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE name = :id"), {"data": json.dumps({"bad": "data"}), "id": agent_id}, ) @@ -554,7 +550,7 @@ def test_list_agents_corrupted_data_sets_zero_counts(client: TestClient) -> None ) with engine.begin() as conn: conn.execute( - text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE agent_uuid = :id"), + text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE name = :id"), {"data": json.dumps({"bad": "data"}), "id": agent_id}, ) @@ -563,7 +559,7 @@ def test_list_agents_corrupted_data_sets_zero_counts(client: TestClient) -> None # Then: step/evaluator counts are zeroed for corrupted data assert resp.status_code == 200 - agents = {a["agent_id"]: a for a in resp.json()["agents"]} + agents = {a["agent_name"]: a for a in resp.json()["agents"]} agent = agents[agent_id] assert agent["step_count"] == 0 assert agent["evaluator_count"] == 0 @@ -574,7 +570,7 @@ def test_get_agent_corrupted_data_returns_422(client: TestClient) -> None: agent_id, _ = _init_agent(client) with engine.begin() as conn: conn.execute( - text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE agent_uuid = :id"), + text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE name = :id"), {"data": json.dumps({"bad": "data"}), "id": agent_id}, ) @@ -592,7 +588,7 @@ def test_get_agent_corrupted_metadata_returns_422(client: TestClient) -> None: corrupted = {"agent_metadata": {}, "steps": [], "evaluators": []} with engine.begin() as conn: conn.execute( - text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE agent_uuid = :id"), + text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE name = :id"), {"data": json.dumps(corrupted), "id": agent_id}, ) @@ -623,7 +619,7 @@ def test_get_agent_policy_missing_policy_returns_404(client: TestClient) -> None with Session(engine) as session: agent_row = ( session.execute( - select(AgentModel).where(AgentModel.agent_uuid == agent_id) + select(AgentModel).where(AgentModel.name == agent_id) ) .scalars() .first() @@ -718,8 +714,8 @@ def test_list_agents_includes_active_controls_count(client: TestClient) -> None: def test_list_agents_valid_cursor_not_found_returns_first_page(client: TestClient) -> None: # Given: two agents - _init_agent(client, agent_name=f"Agent-{uuid.uuid4().hex[:6]}") - _init_agent(client, agent_name=f"Agent-{uuid.uuid4().hex[:6]}") + _init_agent(client, agent_name=f"agent-{uuid.uuid4().hex[:12]}") + _init_agent(client, agent_name=f"agent-{uuid.uuid4().hex[:12]}") # When: listing without cursor resp = client.get("/api/v1/agents", params={"limit": 1}) @@ -738,8 +734,8 @@ def test_list_agents_valid_cursor_not_found_returns_first_page(client: TestClien def test_init_agent_adds_new_evaluator(client: TestClient) -> None: # Given: an existing agent with one evaluator - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:6]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name payload = { "agent": { "agent_id": agent_id, @@ -777,8 +773,8 @@ def test_init_agent_adds_new_evaluator(client: TestClient) -> None: def test_init_agent_returns_controls_when_policy_assigned(client: TestClient) -> None: # Given: an agent assigned to a policy with a control - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:6]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name init_resp = client.post( "/api/v1/agents/initAgent", json={ @@ -828,7 +824,7 @@ def test_patch_agent_corrupted_data_returns_422(client: TestClient) -> None: agent_id, _ = _init_agent(client) with engine.begin() as conn: conn.execute( - text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE agent_uuid = :id"), + text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE name = :id"), {"data": json.dumps({"bad": "data"}), "id": agent_id}, ) @@ -848,7 +844,7 @@ def test_get_agent_evaluator_corrupted_data_returns_404(client: TestClient) -> N agent_id, _ = _init_agent(client, evaluators=[{"name": "eval-a", "config_schema": {}}]) with engine.begin() as conn: conn.execute( - text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE agent_uuid = :id"), + text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE name = :id"), {"data": json.dumps({"bad": "data"}), "id": agent_id}, ) @@ -867,7 +863,7 @@ def test_init_agent_rejects_duplicate_step_names_in_single_request( payload = { "agent": { "agent_id": str(uuid.uuid4()), - "agent_name": f"Agent-{uuid.uuid4().hex[:6]}", + "agent_name": f"agent-{uuid.uuid4().hex[:12]}", "agent_description": "desc", "agent_version": "1.0", }, @@ -895,8 +891,8 @@ def test_init_agent_rejects_step_schema_conflict_across_registrations( client: TestClient, ) -> None: # Given: an agent registered with a step - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:6]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name original_payload = { "agent": { "agent_id": agent_id, @@ -949,8 +945,8 @@ def test_init_agent_accepts_identical_step_schema_across_registrations( client: TestClient, ) -> None: # Given: an agent registered with a step - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:6]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name payload = { "agent": { "agent_id": agent_id, diff --git a/server/tests/test_controls_additional.py b/server/tests/test_controls_additional.py index 8d63bf9f..22967f17 100644 --- a/server/tests/test_controls_additional.py +++ b/server/tests/test_controls_additional.py @@ -463,8 +463,8 @@ def test_set_control_data_agent_scoped_agent_not_found(client: TestClient) -> No def test_set_control_data_agent_scoped_evaluator_missing(client: TestClient) -> None: # Given: an agent without the referenced evaluator - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:6]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name resp = client.post( "/api/v1/agents/initAgent", json={ @@ -491,8 +491,8 @@ def test_set_control_data_agent_scoped_evaluator_missing(client: TestClient) -> def test_set_control_data_agent_scoped_invalid_schema(client: TestClient) -> None: # Given: an agent with evaluator schema requiring "pattern" - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:6]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name resp = client.post( "/api/v1/agents/initAgent", json={ @@ -581,8 +581,8 @@ def test_set_control_data_agent_scoped_corrupted_agent_data_returns_422( client: TestClient, ) -> None: # Given: an agent whose stored data is corrupted - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:6]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name resp = client.post( "/api/v1/agents/initAgent", json={ @@ -595,7 +595,7 @@ def test_set_control_data_agent_scoped_corrupted_agent_data_returns_422( with engine.begin() as conn: conn.execute( - text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE agent_uuid = :id"), + text("UPDATE agents SET data = CAST(:data AS JSONB) WHERE name = :id"), {"data": json.dumps({"bad": "data"}), "id": agent_id}, ) diff --git a/server/tests/test_error_handling.py b/server/tests/test_error_handling.py index 5a14d8c3..756827c8 100644 --- a/server/tests/test_error_handling.py +++ b/server/tests/test_error_handling.py @@ -31,11 +31,12 @@ def test_init_agent_rollback_on_create_failure( ) -> None: """Test that init_agent rolls back transaction when commit fails on create.""" # Given: a valid agent init payload - agent_id = str(uuid.uuid4()) + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name payload = { "agent": { "agent_id": agent_id, - "agent_name": f"test-agent-{uuid.uuid4()}", + "agent_name": agent_name, "agent_description": "test", "agent_version": "1.0", "agent_metadata": {}, @@ -72,7 +73,7 @@ def test_delete_agent_policy_rollback_on_failure( } r1 = client.post("/api/v1/agents/initAgent", json=agent_payload) assert r1.status_code == 200 - agent_id = agent_payload["agent"]["agent_id"] + agent_id = agent_payload["agent"]["agent_name"] policy_name = f"test-policy-{uuid.uuid4()}" r2 = client.put("/api/v1/policies", json={"name": policy_name}) @@ -88,7 +89,7 @@ def test_delete_agent_policy_rollback_on_failure( with Session(db_engine) as session: existing_agent = ( - session.query(Agent).filter(Agent.agent_uuid == agent_id).first() + session.query(Agent).filter(Agent.name == agent_id).first() ) assert existing_agent is not None @@ -119,8 +120,8 @@ def test_init_agent_rollback_on_update_failure( ) -> None: """Test that init_agent rolls back transaction when commit fails on update.""" # Given: an existing agent - agent_id = str(uuid.uuid4()) - agent_name = f"test-agent-{uuid.uuid4()}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name payload = { "agent": { "agent_id": agent_id, @@ -219,8 +220,8 @@ def test_patch_agent_rollback_on_failure( ) -> None: """Test that patch_agent rolls back when commit fails.""" # Given: an existing agent with a step to remove - agent_id = str(uuid.uuid4()) - agent_name = f"test-agent-{uuid.uuid4()}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name payload = { "agent": { "agent_id": agent_id, @@ -247,7 +248,7 @@ def test_patch_agent_rollback_on_failure( with Session(db_engine) as session: existing_agent = ( - session.query(Agent).filter(Agent.agent_uuid == agent_id).first() + session.query(Agent).filter(Agent.name == agent_id).first() ) assert existing_agent is not None @@ -358,7 +359,7 @@ def test_set_agent_policy_rollback_on_failure( } r1 = client.post("/api/v1/agents/initAgent", json=agent_payload) assert r1.status_code == 200 - agent_id = agent_payload["agent"]["agent_id"] + agent_id = agent_payload["agent"]["agent_name"] policy_name = f"test-policy-{uuid.uuid4()}" r2 = client.put("/api/v1/policies", json={"name": policy_name}) @@ -372,7 +373,7 @@ def test_set_agent_policy_rollback_on_failure( with Session(db_engine) as session: existing_agent = ( session.query(Agent) - .filter(Agent.agent_uuid == agent_id) + .filter(Agent.name == agent_id) .first() ) existing_policy = ( diff --git a/server/tests/test_evaluation_e2e.py b/server/tests/test_evaluation_e2e.py index b6e414a8..8d3f6ad1 100644 --- a/server/tests/test_evaluation_e2e.py +++ b/server/tests/test_evaluation_e2e.py @@ -19,12 +19,12 @@ def test_evaluation_flow_deny(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_data) + agent_name, control_name = create_and_assign_policy(client, control_data) # When: Sending a request containing "secret" payload = Step(type="llm", name="test-step", input="This contains a secret", output=None) req = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=payload, stage="pre" ) @@ -41,15 +41,15 @@ def test_evaluation_flow_deny(client: TestClient): def test_evaluation_no_policy(client: TestClient): """Test that an agent with no policy assigned is safe.""" # Given: an agent with no policy assigned - agent_uuid = uuid.uuid4() + agent_name = f"agent-{uuid.uuid4().hex[:12]}" client.post("/api/v1/agents/initAgent", json={ - "agent": {"agent_id": str(agent_uuid), "agent_name": "NoPolicyAgent"}, + "agent": {"agent_name": agent_name}, "steps": [] }) # When: evaluating content for that agent req = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="anything", output=None), stage="pre" ) @@ -69,17 +69,17 @@ def test_evaluation_empty_policy(client: TestClient): policy_id = resp.json()["policy_id"] # And: an agent assigned to that policy - agent_uuid = uuid.uuid4() + agent_name = f"agent-{uuid.uuid4().hex[:12]}" client.post("/api/v1/agents/initAgent", json={ - "agent": {"agent_id": str(agent_uuid), "agent_name": "EmptyPolicyAgent"}, + "agent": {"agent_name": agent_name}, "steps": [] }) - client.post(f"/api/v1/agents/{str(agent_uuid)}/policy/{policy_id}") + client.post(f"/api/v1/agents/{agent_name}/policy/{policy_id}") # When: evaluating content for that agent req = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="anything", output=None), stage="pre" ) @@ -106,12 +106,12 @@ def test_evaluation_path_failure(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, _ = create_and_assign_policy(client, control_data, agent_name="PathFailAgent") + agent_name, _ = create_and_assign_policy(client, control_data, agent_name="PathFailAgent") # When: Sending a request payload = Step(type="llm", name="test-step", input="some content", output=None) req = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=payload, stage="pre" ) @@ -135,11 +135,11 @@ def test_evaluation_selector_star_uses_full_step_json(client: TestClient): "evaluator": {"name": "json", "config": {"required_fields": ["type"]}}, "action": {"decision": "deny"}, } - agent_uuid, _ = create_and_assign_policy(client, control_data, agent_name="JsonStarAgent") + agent_name, _ = create_and_assign_policy(client, control_data, agent_name="JsonStarAgent") # When: evaluating a valid step payload payload = Step(type="llm", name="test-step", input="hello", output=None) - req = EvaluationRequest(agent_uuid=agent_uuid, step=payload, stage="pre") + req = EvaluationRequest(agent_name=agent_name, step=payload, stage="pre") resp = client.post("/api/v1/evaluation", json=req.model_dump(mode="json")) # Then: evaluation is safe (JSON evaluator accepts the full payload) @@ -163,7 +163,7 @@ def test_evaluation_tool_step_nested(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_data, agent_name="ToolNestedAgent") + agent_name, control_name = create_and_assign_policy(client, control_data, agent_name="ToolNestedAgent") # Case 1: Safe value # When: Sending safe nested value @@ -173,7 +173,7 @@ def test_evaluation_tool_step_nested(client: TestClient): output=None ) req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=safe_payload, stage="pre" ) @@ -191,7 +191,7 @@ def test_evaluation_tool_step_nested(client: TestClient): output=None ) req_unsafe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=unsafe_payload, stage="pre" ) @@ -217,11 +217,11 @@ def test_evaluation_deny_precedence(client: TestClient): "action": {"decision": "warn"} } # Use helper to setup agent with first control - agent_uuid, warn_control_name = create_and_assign_policy(client, control_warn, agent_name="PrecedenceAgent") + agent_name, warn_control_name = create_and_assign_policy(client, control_warn, agent_name="PrecedenceAgent") # Create and add second (Deny) control to the same policy # Actually, easiest is to fetch the agent's policy ID - resp = client.get(f"/api/v1/agents/{agent_uuid}/policy") + resp = client.get(f"/api/v1/agents/{agent_name}/policy") policy_id = resp.json()["policy_id"] # Create Deny Control @@ -243,7 +243,7 @@ def test_evaluation_deny_precedence(client: TestClient): # When: Sending request matching "keyword" req = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="This has a keyword", output=None), stage="pre" ) @@ -271,11 +271,11 @@ def test_evaluation_stage_filtering(client: TestClient): "evaluator": {"name": "regex", "config": {"pattern": "bad_output"}}, "action": {"decision": "deny"} } - agent_uuid, _ = create_and_assign_policy(client, control_data, agent_name="StageAgent") + agent_name, _ = create_and_assign_policy(client, control_data, agent_name="StageAgent") # When: evaluating at the pre stage req_pre = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, # Even if we provide output, the control shouldn't run in 'pre' stage? # Actually the control says stage='post'. If we send request with stage='pre', it skips. step=Step(type="llm", name="test-step", input="bad_output", output="bad_output"), @@ -288,7 +288,7 @@ def test_evaluation_stage_filtering(client: TestClient): # When: evaluating at the post stage req_post = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="ok", output="bad_output"), stage="post" ) @@ -310,12 +310,12 @@ def test_evaluation_step_type_filtering(client: TestClient): "evaluator": {"name": "regex", "config": {"pattern": "rm_rf"}}, "action": {"decision": "deny"} } - agent_uuid, _ = create_and_assign_policy(client, control_data, agent_name="AppliesToAgent") + agent_name, _ = create_and_assign_policy(client, control_data, agent_name="AppliesToAgent") # When: evaluating an LLM step (control should not apply) # Note: LLM steps don't have tool names, but the engine filters by step type. req_llm = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="rm_rf", output=None), stage="pre" ) @@ -325,7 +325,7 @@ def test_evaluation_step_type_filtering(client: TestClient): # When: evaluating a tool step (control applies) req_tool = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="rm_rf", input={}), stage="pre" ) @@ -349,11 +349,11 @@ def test_evaluation_denylist_step_name(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_data, agent_name="ToolBlockAgent") + agent_name, control_name = create_and_assign_policy(client, control_data, agent_name="ToolBlockAgent") # When: evaluating a safe tool (not in list) req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="safe_tool", input={}), stage="pre" ) @@ -363,7 +363,7 @@ def test_evaluation_denylist_step_name(client: TestClient): # When: evaluating a dangerous tool (in list) req_unsafe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="dangerous_tool", input={}), stage="pre" ) diff --git a/server/tests/test_evaluation_e2e_list_evaluator.py b/server/tests/test_evaluation_e2e_list_evaluator.py index 55c23545..4c73c863 100644 --- a/server/tests/test_evaluation_e2e_list_evaluator.py +++ b/server/tests/test_evaluation_e2e_list_evaluator.py @@ -23,12 +23,12 @@ def test_list_evaluator_denylist_behavior(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_data, agent_name="DenyListAgent") + agent_name, control_name = create_and_assign_policy(client, control_data, agent_name="DenyListAgent") # Case 1: Safe Value # When: Sending a tool step with a safe command "ls" req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="shell", input={"cmd": "ls"}, output=None), stage="pre" ) @@ -40,7 +40,7 @@ def test_list_evaluator_denylist_behavior(client: TestClient): # Case 2: Unsafe Value # When: Sending a tool step with a forbidden command "rm" req_unsafe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="shell", input={"cmd": "rm"}, output=None), stage="pre" ) @@ -71,12 +71,12 @@ def test_list_evaluator_allowlist_behavior(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_data, agent_name="AllowListAgent") + agent_name, control_name = create_and_assign_policy(client, control_data, agent_name="AllowListAgent") # Case 1: Allowed Value # When: Sending a tool step with the allowed tool "safe_tool" req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="safe_tool", input={}, output=None), stage="pre" ) @@ -88,7 +88,7 @@ def test_list_evaluator_allowlist_behavior(client: TestClient): # Case 2: Disallowed Value # When: Sending a tool step with a tool NOT in the list ("unknown_tool") req_unsafe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="unknown_tool", input={}, output=None), stage="pre" ) @@ -118,11 +118,11 @@ def test_list_evaluator_case_insensitive(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_data, agent_name="CaseAgent") + agent_name, control_name = create_and_assign_policy(client, control_data, agent_name="CaseAgent") # When: Sending input "blockme" (lowercase) req = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="blockme", output=None), stage="pre" ) @@ -151,12 +151,12 @@ def test_list_evaluator_list_input_any_match(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, _ = create_and_assign_policy(client, control_data, agent_name="TagAgent") + agent_name, _ = create_and_assign_policy(client, control_data, agent_name="TagAgent") # Case 1: List containing restricted item # When: Sending tags ["public", "restricted"] req_unsafe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="update", input={"tags": ["public", "restricted"]}, output=None), stage="pre" ) @@ -168,7 +168,7 @@ def test_list_evaluator_list_input_any_match(client: TestClient): # Case 2: List containing only safe items # When: Sending tags ["public", "internal"] req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="update", input={"tags": ["public", "internal"]}, output=None), stage="pre" ) @@ -198,12 +198,12 @@ def test_list_evaluator_list_input_all_match(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, _ = create_and_assign_policy(client, control_data, agent_name="SafeTagAgent") + agent_name, _ = create_and_assign_policy(client, control_data, agent_name="SafeTagAgent") # Case 1: All items match # When: Sending only safe tags req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="update", input={"tags": ["safe_tag", "audit_approved"]}, output=None), stage="pre" ) @@ -215,7 +215,7 @@ def test_list_evaluator_list_input_all_match(client: TestClient): # Case 2: Mixed items (one unsafe) # When: Sending tags with one risky item req_unsafe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="update", input={"tags": ["safe_tag", "risky"]}, output=None), stage="pre" ) @@ -244,12 +244,12 @@ def test_list_evaluator_disallow_name(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_data, agent_name="NoDangerousTools") + agent_name, control_name = create_and_assign_policy(client, control_data, agent_name="NoDangerousTools") # Case 1: Allowed Tool # When: Calling a safe tool req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="get_user", input={"id": "123"}, output=None), stage="pre" ) @@ -260,7 +260,7 @@ def test_list_evaluator_disallow_name(client: TestClient): # Case 2: Disallowed Tool # When: Calling a dangerous tool req_unsafe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="delete_user", input={"id": "123"}, output=None), stage="pre" ) @@ -289,12 +289,12 @@ def test_list_evaluator_allow_only_argument_values(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_data, agent_name="RegionPolicy") + agent_name, control_name = create_and_assign_policy(client, control_data, agent_name="RegionPolicy") # Case 1: Allowed Value # When: Using an allowed region req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="deploy", input={"region": "us-east-1"}, output=None), stage="pre" ) @@ -305,7 +305,7 @@ def test_list_evaluator_allow_only_argument_values(client: TestClient): # Case 2: Disallowed Value # When: Using a disallowed region req_unsafe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="deploy", input={"region": "eu-central-1"}, output=None), stage="pre" ) @@ -335,11 +335,11 @@ def test_list_evaluator_edge_cases(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, _ = create_and_assign_policy(client, control_empty, agent_name="EmptyControlAgent") + agent_name, _ = create_and_assign_policy(client, control_empty, agent_name="EmptyControlAgent") # When: Calling any tool req = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="something", input={}, output=None), stage="pre" ) @@ -365,11 +365,11 @@ def test_list_evaluator_edge_cases(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_types, agent_name="TypeAgent") + agent_name, control_name = create_and_assign_policy(client, control_types, agent_name="TypeAgent") # When: Input is integer 10 req_int = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="count", input={"count": 10}, output=None), stage="pre" ) @@ -379,7 +379,7 @@ def test_list_evaluator_edge_cases(client: TestClient): # When: Input is string "20" req_str = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="count", input={"count": "20"}, output=None), stage="pre" ) @@ -405,11 +405,11 @@ def test_list_evaluator_edge_cases(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_special, agent_name="SpecialCharAgent") + agent_name, control_name = create_and_assign_policy(client, control_special, agent_name="SpecialCharAgent") # When: Input exactly matches "(test)" req_special = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="search", input={"query": "(test)"}, output=None), stage="pre" ) @@ -419,7 +419,7 @@ def test_list_evaluator_edge_cases(client: TestClient): # When: Input is "test" (without parens) req_normal = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="search", input={"query": "test"}, output=None), stage="pre" ) @@ -446,11 +446,11 @@ def test_list_evaluator_edge_cases(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, _ = create_and_assign_policy(client, control_null, agent_name="NullAgent") + agent_name, _ = create_and_assign_policy(client, control_null, agent_name="NullAgent") # When: Selector returns None req_null = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="check", input={}, output=None), stage="pre" ) @@ -482,11 +482,11 @@ def test_list_evaluator_re2_corner_cases(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, _ = create_and_assign_policy(client, control_large, agent_name="LargeListAgent") + agent_name, _ = create_and_assign_policy(client, control_large, agent_name="LargeListAgent") # When: Matching the last item req = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="check", input={"item": "target_value"}, output=None), stage="pre" ) @@ -514,11 +514,11 @@ def test_list_evaluator_newline_strictness(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, _ = create_and_assign_policy(client, control_strict, agent_name="StrictAgent") + agent_name, _ = create_and_assign_policy(client, control_strict, agent_name="StrictAgent") # When: Sending "exact\n" (trailing newline) req_newline = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="check", input={"val": "exact\n"}, output=None), stage="pre" ) diff --git a/server/tests/test_evaluation_e2e_sql_evaluator.py b/server/tests/test_evaluation_e2e_sql_evaluator.py index 45e87791..e82bdb7b 100644 --- a/server/tests/test_evaluation_e2e_sql_evaluator.py +++ b/server/tests/test_evaluation_e2e_sql_evaluator.py @@ -30,13 +30,13 @@ def test_sql_read_only_agent(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy( + agent_name, control_name = create_and_assign_policy( client, control_data, agent_name="ReadOnlyAgent" ) # When: evaluating a SELECT with LIMIT 100 req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users LIMIT 100"}, @@ -51,7 +51,7 @@ def test_sql_read_only_agent(client: TestClient): # When: evaluating an INSERT query (not allowed) req_insert = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "INSERT INTO users (name) VALUES ('test')"}, @@ -67,7 +67,7 @@ def test_sql_read_only_agent(client: TestClient): # When: evaluating a SELECT without LIMIT req_no_limit = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users"}, @@ -82,7 +82,7 @@ def test_sql_read_only_agent(client: TestClient): # When: evaluating a SELECT with LIMIT 5000 (exceeds max_limit) req_high_limit = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users LIMIT 5000"}, @@ -114,13 +114,13 @@ def test_sql_multi_tenant_security(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy( + agent_name, control_name = create_and_assign_policy( client, control_data, agent_name="MultiTenantAgent" ) # When: evaluating a query with tenant_id in WHERE req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM orders WHERE tenant_id = 123"}, @@ -135,7 +135,7 @@ def test_sql_multi_tenant_security(client: TestClient): # When: evaluating a query without tenant_id in WHERE req_no_tenant = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM orders WHERE status = 'active'"}, @@ -151,7 +151,7 @@ def test_sql_multi_tenant_security(client: TestClient): # When: evaluating a query with tenant_id only in SELECT req_select_only = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT tenant_id, name FROM orders"}, @@ -182,13 +182,13 @@ def test_sql_block_destructive_operations(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy( + agent_name, control_name = create_and_assign_policy( client, control_data, agent_name="SafeAgent" ) # When: evaluating a safe SELECT query req_select = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users"}, @@ -203,7 +203,7 @@ def test_sql_block_destructive_operations(client: TestClient): # When: evaluating a non-destructive INSERT query req_insert = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "INSERT INTO logs (message) VALUES ('test')"}, @@ -218,7 +218,7 @@ def test_sql_block_destructive_operations(client: TestClient): # When: evaluating a DROP TABLE query req_drop = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "DROP TABLE users"}, @@ -234,7 +234,7 @@ def test_sql_block_destructive_operations(client: TestClient): # When: evaluating a DELETE query req_delete = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "DELETE FROM users WHERE id = 1"}, @@ -249,7 +249,7 @@ def test_sql_block_destructive_operations(client: TestClient): # When: evaluating a TRUNCATE TABLE query req_truncate = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "TRUNCATE TABLE logs"}, @@ -280,13 +280,13 @@ def test_sql_table_restrictions(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy( + agent_name, control_name = create_and_assign_policy( client, control_data, agent_name="AnalyticsAgent" ) # When: evaluating a query against an allowed table (users) req_users = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users"}, @@ -301,7 +301,7 @@ def test_sql_table_restrictions(client: TestClient): # When: evaluating a query against an allowed table (orders) req_orders = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM orders"}, @@ -316,7 +316,7 @@ def test_sql_table_restrictions(client: TestClient): # When: evaluating a query against a disallowed table (admin_data) req_admin = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM admin_data"}, @@ -332,7 +332,7 @@ def test_sql_table_restrictions(client: TestClient): # When: evaluating a query against a disallowed table (sensitive_data) req_sensitive = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM sensitive_data"}, @@ -363,13 +363,13 @@ def test_sql_multi_statement_blocking(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy( + agent_name, control_name = create_and_assign_policy( client, control_data, agent_name="SingleStatementAgent" ) # When: evaluating a single-statement query req_single = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users WHERE id = 1"}, @@ -384,7 +384,7 @@ def test_sql_multi_statement_blocking(client: TestClient): # When: evaluating a multi-statement query req_multi = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users; DROP TABLE users;"}, @@ -417,13 +417,13 @@ def test_sql_limit_enforcement(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy( + agent_name, control_name = create_and_assign_policy( client, control_data, agent_name="LimitAgent" ) # When: evaluating a SELECT with LIMIT 500 req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users LIMIT 500"}, @@ -438,7 +438,7 @@ def test_sql_limit_enforcement(client: TestClient): # When: evaluating a SELECT with LIMIT 1000 (boundary) req_boundary = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users LIMIT 1000"}, @@ -453,7 +453,7 @@ def test_sql_limit_enforcement(client: TestClient): # When: evaluating a SELECT with LIMIT 1001 (exceeds max) req_exceed = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users LIMIT 1001"}, @@ -469,7 +469,7 @@ def test_sql_limit_enforcement(client: TestClient): # When: evaluating a SELECT without LIMIT req_no_limit = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "SELECT * FROM users"}, @@ -484,7 +484,7 @@ def test_sql_limit_enforcement(client: TestClient): # When: evaluating an INSERT without LIMIT (LIMIT only applies to SELECT) req_insert = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="tool", name="execute_sql", input={"query": "INSERT INTO users (name) VALUES ('test')"}, @@ -522,13 +522,13 @@ def test_sql_llm_output_validation_read_only(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy( + agent_name, control_name = create_and_assign_policy( client, control_data, agent_name="LlmReadOnlyAgent" ) # When: LLM outputs SELECT with LIMIT req_safe = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="Generate a query to get all users", output="SELECT * FROM users LIMIT 10" @@ -542,7 +542,7 @@ def test_sql_llm_output_validation_read_only(client: TestClient): # When: LLM outputs DELETE req_delete = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="Delete user with id 1", output="DELETE FROM users WHERE id = 1" @@ -557,7 +557,7 @@ def test_sql_llm_output_validation_read_only(client: TestClient): # When: LLM outputs SELECT without LIMIT req_no_limit = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="Get all users", output="SELECT * FROM users" @@ -587,13 +587,13 @@ def test_sql_llm_output_multi_statement_blocking(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy( + agent_name, control_name = create_and_assign_policy( client, control_data, agent_name="LlmSingleStatementAgent" ) # When: LLM outputs a single statement req_single = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="Get user by id", output="SELECT * FROM users WHERE id = 1" @@ -607,7 +607,7 @@ def test_sql_llm_output_multi_statement_blocking(client: TestClient): # When: LLM outputs a multi-statement query req_multi = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="Get users and drop table", output="SELECT * FROM users; DROP TABLE users;" @@ -638,13 +638,13 @@ def test_sql_llm_output_table_restrictions(client: TestClient): }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy( + agent_name, control_name = create_and_assign_policy( client, control_data, agent_name="LlmAnalyticsAgent" ) # When: LLM outputs a query on an allowed table (analytics) req_analytics = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="Get analytics data", output="SELECT * FROM analytics WHERE date > '2024-01-01'" @@ -658,7 +658,7 @@ def test_sql_llm_output_table_restrictions(client: TestClient): # When: LLM outputs a query on an allowed table (reports) req_reports = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="Get monthly reports", output="SELECT * FROM reports WHERE month = 'January'" @@ -672,7 +672,7 @@ def test_sql_llm_output_table_restrictions(client: TestClient): # When: LLM outputs a query on a disallowed table (users) req_users = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=Step(type="llm", name="test-step", input="Get all users", output="SELECT * FROM users" diff --git a/server/tests/test_evaluation_error_handling.py b/server/tests/test_evaluation_error_handling.py index 1b8e652e..c984dfdc 100644 --- a/server/tests/test_evaluation_error_handling.py +++ b/server/tests/test_evaluation_error_handling.py @@ -17,18 +17,16 @@ def test_evaluation_with_agent_scoped_evaluator_missing(client: TestClient): Then: Returns 400 with clear error message """ # Given: an agent without evaluators - agent_uuid = uuid.uuid4() + agent_name = f"testagent-{uuid.uuid4().hex[:12]}" client.post("/api/v1/agents/initAgent", json={ "agent": { - "agent_id": str(agent_uuid), - "agent_name": f"TestAgent-{uuid.uuid4().hex[:8]}" + "agent_name": agent_name }, "steps": [], "evaluators": [] }) # And: a control referencing a non-existent agent evaluator - agent_name = f"TestAgent-{uuid.uuid4().hex[:8]}" control_data = { "description": "Test control", "enabled": True, @@ -112,7 +110,7 @@ def test_evaluation_errors_field_populated_on_evaluator_failure( }, "action": {"decision": "deny"} } - agent_uuid, control_name = create_and_assign_policy(client, control_data) + agent_name, control_name = create_and_assign_policy(client, control_data) # And: an evaluator instance that throws during evaluation mock_evaluator = MagicMock() @@ -130,7 +128,7 @@ def mock_get_evaluator_instance(config): # When: sending an evaluation request payload = Step(type="llm", name="test-step", input="test content", output=None) req = EvaluationRequest( - agent_uuid=agent_uuid, + agent_name=agent_name, step=payload, stage="pre" ) @@ -173,7 +171,7 @@ def test_evaluation_engine_value_error_returns_422(client: TestClient, monkeypat "evaluator": {"name": "regex", "config": {"pattern": "test"}}, "action": {"decision": "deny"}, } - agent_uuid, _ = create_and_assign_policy(client, control_data) + agent_name, _ = create_and_assign_policy(client, control_data) # And: the engine raises a ValueError during processing import agent_control_engine.core as core_module @@ -185,7 +183,7 @@ async def raise_value_error(*_args, **_kwargs): # When: sending an evaluation request payload = Step(type="llm", name="test-step", input="test content", output=None) - req = EvaluationRequest(agent_uuid=agent_uuid, step=payload, stage="pre") + req = EvaluationRequest(agent_name=agent_name, step=payload, stage="pre") resp = client.post("/api/v1/evaluation", json=req.model_dump(mode="json")) # Then: a validation error is returned @@ -200,7 +198,7 @@ def test_evaluation_warns_when_observability_drops_events( client: TestClient, app, caplog ) -> None: # Given: an agent with a control that will match - agent_uuid, _ = create_and_assign_policy(client) + agent_name, _ = create_and_assign_policy(client) class DroppingIngestor: async def ingest(self, events): # type: ignore[no-untyped-def] @@ -214,7 +212,7 @@ async def ingest(self, events): # type: ignore[no-untyped-def] # When: sending an evaluation request payload = Step(type="llm", name="test-step", input="x", output=None) - req = EvaluationRequest(agent_uuid=agent_uuid, step=payload, stage="pre") + req = EvaluationRequest(agent_name=agent_name, step=payload, stage="pre") resp = client.post("/api/v1/evaluation", json=req.model_dump(mode="json")) # Then: the evaluation succeeds but logs a dropped-events warning diff --git a/server/tests/test_evaluator_schemas.py b/server/tests/test_evaluator_schemas.py index a63e563b..785f41bf 100644 --- a/server/tests/test_evaluator_schemas.py +++ b/server/tests/test_evaluator_schemas.py @@ -14,14 +14,17 @@ def make_agent_payload( evaluators: list | None = None, ): """Helper to create agent payload with evaluators.""" - if agent_id is None: - agent_id = str(uuid.uuid4()) - if name is None: - name = f"Test Agent {uuid.uuid4().hex[:8]}" + if agent_id is not None: + name = agent_id + elif name is None: + name = f"agent-{uuid.uuid4().hex[:12]}" + canonical_name = name.lower().replace(" ", "-") + if len(canonical_name) < 10: + canonical_name = f"{canonical_name}-agent".replace("--", "-") return { "agent": { - "agent_id": agent_id, - "agent_name": name, + "agent_id": canonical_name, + "agent_name": canonical_name, "agent_description": "desc", "agent_version": "1.0", }, diff --git a/server/tests/test_init_agent.py b/server/tests/test_init_agent.py index b63e1acb..55f442b0 100644 --- a/server/tests/test_init_agent.py +++ b/server/tests/test_init_agent.py @@ -18,11 +18,13 @@ def make_agent_payload( agent_id: str | None = None, - name: str = "Test Agent", + name: str = "testagent0001", steps: list[dict[str, Any]] | None = None, ) -> dict[str, Any]: - if agent_id is None: - agent_id = str(uuid.uuid4()) + resolved_name = name if name != "testagent0001" else (agent_id or f"agent-{uuid.uuid4().hex[:12]}") + canonical_name = resolved_name.lower().replace(" ", "-") + if len(canonical_name) < 10: + canonical_name = f"{canonical_name}-agent".replace("--", "-") if steps is None: steps = [ { @@ -34,8 +36,8 @@ def make_agent_payload( ] return { "agent": { - "agent_id": agent_id, - "agent_name": name, + "agent_id": canonical_name, + "agent_name": canonical_name, "agent_description": "desc", "agent_version": "1.0", "agent_metadata": {"env": "test"}, @@ -51,7 +53,7 @@ def test_init_agent_route_exists(app: FastAPI) -> None: # (computation done above to gather all paths) # Then: initAgent and agent retrieval endpoints are present assert "/api/v1/agents/initAgent" in paths - assert "/api/v1/agents/{agent_id}" in paths + assert "/api/v1/agents/{agent_name}" in paths def test_init_agent_creates_and_gets_agent(client: TestClient) -> None: @@ -65,13 +67,12 @@ def test_init_agent_creates_and_gets_agent(client: TestClient) -> None: assert body["created"] is True assert body["controls"] == [] - agent_id = payload["agent"]["agent_id"] + agent_name = payload["agent"]["agent_name"] # When: retrieving the agent by id - resp2 = client.get(f"/api/v1/agents/{agent_id}") + resp2 = client.get(f"/api/v1/agents/{agent_name}") assert resp2.status_code == 200 data = resp2.json() # Then: stored agent fields match the request - assert data["agent"]["agent_id"] == agent_id assert data["agent"]["agent_name"] == payload["agent"]["agent_name"] assert {s["name"] for s in data["steps"]} == {payload["steps"][0]["name"]} @@ -100,11 +101,10 @@ def test_init_agent_updates_metadata_on_reinit(client: TestClient) -> None: Then: The new metadata is persisted """ # Given: create initial agent - agent_id = str(uuid.uuid4()) + agent_name = "metadatatestagent" initial_payload = { "agent": { - "agent_id": agent_id, - "agent_name": "MetadataTestAgent", + "agent_name": agent_name, "agent_description": "Original description", "agent_version": "1.0.0", "agent_metadata": {"env": "dev"}, @@ -118,8 +118,7 @@ def test_init_agent_updates_metadata_on_reinit(client: TestClient) -> None: # When: re-init with updated metadata updated_payload = { "agent": { - "agent_id": agent_id, - "agent_name": "MetadataTestAgent", + "agent_name": agent_name, "agent_description": "Updated description", "agent_version": "2.0.0", "agent_metadata": {"env": "prod", "new_field": "value"}, @@ -131,7 +130,7 @@ def test_init_agent_updates_metadata_on_reinit(client: TestClient) -> None: assert r2.json()["created"] is False # Then: verify metadata is updated - get_resp = client.get(f"/api/v1/agents/{agent_id}") + get_resp = client.get(f"/api/v1/agents/{agent_name}") assert get_resp.status_code == 200 agent_data = get_resp.json()["agent"] assert agent_data["agent_description"] == "Updated description" @@ -429,12 +428,11 @@ def test_list_agent_controls_agent_not_found_404(client: TestClient) -> None: assert r.status_code == 404 -def test_init_agent_rejects_non_uuid_agent_id(client: TestClient) -> None: - # Given: a payload with an invalid (non-UUID) agent_id +def test_init_agent_rejects_invalid_agent_name(client: TestClient) -> None: + # Given: a payload with an invalid agent_name payload = { "agent": { - "agent_id": "not-a-valid-uuid", - "agent_name": "Test Agent", + "agent_name": "short", "agent_description": "desc", "agent_version": "1.0", }, @@ -469,16 +467,14 @@ def test_list_agents_empty(client: TestClient) -> None: def test_list_agents_returns_created_agents(client: TestClient) -> None: """Test listing agents returns created agents with correct summaries.""" # Given: two agents with different steps/evaluators - agent1_id = str(uuid.uuid4()) - payload1 = make_agent_payload(agent_id=agent1_id, name="Agent One") + payload1 = make_agent_payload(name="agent-one-01") payload1["evaluators"] = [ {"name": "eval-1", "description": "Test", "config_schema": {}}, ] r1 = client.post("/api/v1/agents/initAgent", json=payload1) assert r1.status_code == 200 - agent2_id = str(uuid.uuid4()) - payload2 = make_agent_payload(agent_id=agent2_id, name="Agent Two") + payload2 = make_agent_payload(name="agent-two-02") payload2["steps"] = [ {"type": "tool", "name": "tool_x", "input_schema": {}, "output_schema": {}}, {"type": "tool", "name": "tool_y", "input_schema": {}, "output_schema": {}}, @@ -495,18 +491,18 @@ def test_list_agents_returns_created_agents(client: TestClient) -> None: assert len(body["agents"]) == 2 # Verify agent summaries contain correct data - agent_map = {a["agent_id"]: a for a in body["agents"]} + agent_map = {a["agent_name"]: a for a in body["agents"]} - assert agent1_id in agent_map - agent1 = agent_map[agent1_id] - assert agent1["agent_name"] == "Agent One" + assert "agent-one-01" in agent_map + agent1 = agent_map["agent-one-01"] + assert agent1["agent_name"] == "agent-one-01" assert agent1["step_count"] == 1 # from make_agent_payload assert agent1["evaluator_count"] == 1 assert agent1["policy_id"] is None - assert agent2_id in agent_map - agent2 = agent_map[agent2_id] - assert agent2["agent_name"] == "Agent Two" + assert "agent-two-02" in agent_map + agent2 = agent_map["agent-two-02"] + assert agent2["agent_name"] == "agent-two-02" assert agent2["step_count"] == 2 assert agent2["evaluator_count"] == 0 assert agent2["policy_id"] is None diff --git a/server/tests/test_init_agent_conflict_mode.py b/server/tests/test_init_agent_conflict_mode.py index f8efa3eb..0cd1b714 100644 --- a/server/tests/test_init_agent_conflict_mode.py +++ b/server/tests/test_init_agent_conflict_mode.py @@ -21,10 +21,11 @@ def _init_payload( evaluators: list[dict[str, Any]] | None = None, conflict_mode: str | None = None, ) -> dict[str, Any]: + canonical_name = agent_name.lower() payload: dict[str, Any] = { "agent": { - "agent_id": agent_id, - "agent_name": agent_name, + "agent_id": canonical_name, + "agent_name": canonical_name, "agent_description": agent_description, "agent_version": agent_version, }, @@ -71,8 +72,8 @@ def _create_policy_with_agent_evaluator_control( def test_init_agent_overwrite_replaces_steps_and_evaluators(client: TestClient) -> None: # Given: an existing agent registration with baseline steps and evaluators. - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:8]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name create_payload = _init_payload( agent_id=agent_id, @@ -163,8 +164,8 @@ def test_init_agent_overwrite_replaces_steps_and_evaluators(client: TestClient) def test_init_agent_overwrite_warns_on_removed_referenced_evaluator(client: TestClient) -> None: # Given: an agent whose assigned policy contains a control referencing an agent evaluator. - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:8]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name evaluator_name = "custom-eval" init_resp = client.post( @@ -215,8 +216,8 @@ def test_init_agent_overwrite_warns_on_removed_referenced_evaluator(client: Test def test_init_agent_overwrite_noop_reports_not_applied(client: TestClient) -> None: # Given: an existing agent registration and an equivalent overwrite payload. - agent_id = str(uuid.uuid4()) - agent_name = f"Agent-{uuid.uuid4().hex[:8]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name payload = _init_payload( agent_id=agent_id, agent_name=agent_name, diff --git a/server/tests/test_init_agent_force_replace.py b/server/tests/test_init_agent_force_replace.py index 76358a17..f324a641 100644 --- a/server/tests/test_init_agent_force_replace.py +++ b/server/tests/test_init_agent_force_replace.py @@ -18,8 +18,8 @@ def test_init_agent_force_replace_default_false_works_normally(client: TestClien Then: Creates agent normally (force_replace defaults to False) """ # Given: New agent - agent_id = str(uuid.uuid4()) - agent_name = f"TestAgent-{uuid.uuid4().hex[:8]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name # When: Create without force_replace (default) resp = client.post("/api/v1/agents/initAgent", json={ @@ -45,8 +45,8 @@ def test_init_agent_force_replace_false_explicit_works_normally(client: TestClie Then: Creates agent normally """ # Given: New agent - agent_id = str(uuid.uuid4()) - agent_name = f"TestAgent-{uuid.uuid4().hex[:8]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name # When: Create with force_replace=false resp = client.post("/api/v1/agents/initAgent", json={ @@ -73,8 +73,8 @@ def test_init_agent_force_replace_true_on_valid_data_works_normally(client: Test Then: Updates normally without data loss """ # Given: Create agent with steps - agent_id = str(uuid.uuid4()) - agent_name = f"TestAgent-{uuid.uuid4().hex[:8]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name resp = client.post("/api/v1/agents/initAgent", json={ "agent": { @@ -130,8 +130,8 @@ def test_init_agent_force_replace_true_on_valid_data_works_normally(client: Test def test_init_agent_force_replace_recovers_from_corrupted_data(client: TestClient) -> None: """Test that force_replace=true replaces corrupted stored data.""" # Given: an existing agent with corrupted data in the DB - agent_id = str(uuid.uuid4()) - agent_name = f"TestAgent-{uuid.uuid4().hex[:8]}" + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + agent_id = agent_name resp = client.post( "/api/v1/agents/initAgent", json={ @@ -147,7 +147,7 @@ def test_init_agent_force_replace_recovers_from_corrupted_data(client: TestClien assert resp.status_code == 200 with Session(engine) as session: - agent = session.execute(select(Agent).where(Agent.agent_uuid == agent_id)).scalar_one() + agent = session.execute(select(Agent).where(Agent.name == agent_name)).scalar_one() agent.data = {"steps": "not-a-list"} session.commit() diff --git a/server/tests/test_new_features.py b/server/tests/test_new_features.py index 9d618ede..01bc96ec 100644 --- a/server/tests/test_new_features.py +++ b/server/tests/test_new_features.py @@ -12,14 +12,17 @@ def make_agent_payload( evaluators: list | None = None, ): """Helper to create agent payload.""" - if agent_id is None: - agent_id = str(uuid.uuid4()) - if name is None: - name = f"Test Agent {uuid.uuid4().hex[:8]}" + if agent_id is not None: + name = agent_id + elif name is None: + name = f"agent-{uuid.uuid4().hex[:12]}" + canonical_name = name.lower().replace(" ", "-") + if len(canonical_name) < 10: + canonical_name = f"{canonical_name}-agent".replace("--", "-") return { "agent": { - "agent_id": agent_id, - "agent_name": name, + "agent_id": canonical_name, + "agent_name": canonical_name, "agent_description": "desc", "agent_version": "1.0", }, @@ -307,8 +310,8 @@ def test_policy_assignment_with_builtin_evaluator(client: TestClient) -> None: def test_policy_assignment_with_registered_agent_evaluator(client: TestClient) -> None: """Given an agent with custom evaluator and matching policy, when assigning policy, then succeeds.""" # Given: - agent_id = str(uuid.uuid4()) - agent_name = f"Test Agent {uuid.uuid4().hex[:8]}" + agent_id = f"agent-{uuid.uuid4().hex[:12]}" + agent_name = agent_id payload = make_agent_payload( agent_id=agent_id, name=agent_name, @@ -339,8 +342,8 @@ def test_policy_assignment_with_registered_agent_evaluator(client: TestClient) - def test_control_creation_with_unregistered_evaluator_fails(client: TestClient) -> None: """Given an agent without evaluator, when setting control to use that evaluator, then fails.""" # Given: - agent_id = str(uuid.uuid4()) - agent_name = f"Test Agent {uuid.uuid4().hex[:8]}" + agent_id = f"agent-{uuid.uuid4().hex[:12]}" + agent_name = agent_id payload = make_agent_payload(agent_id=agent_id, name=agent_name) client.post("/api/v1/agents/initAgent", json=payload) @@ -371,8 +374,8 @@ def test_control_creation_with_unregistered_evaluator_fails(client: TestClient) def test_policy_assignment_cross_agent_evaluator_fails(client: TestClient) -> None: """Given policy with Agent A's evaluator, when assigning to Agent B, then fails.""" # Given: Agent A has evaluator, Agent B does not - agent_a_id = str(uuid.uuid4()) - agent_a_name = f"Agent-A-{uuid.uuid4().hex[:8]}" + agent_a_id = f"agent-a-{uuid.uuid4().hex[:12]}" + agent_a_name = agent_a_id payload_a = make_agent_payload( agent_id=agent_a_id, name=agent_a_name, @@ -380,8 +383,8 @@ def test_policy_assignment_cross_agent_evaluator_fails(client: TestClient) -> No ) client.post("/api/v1/agents/initAgent", json=payload_a) - agent_b_id = str(uuid.uuid4()) - agent_b_name = f"Agent-B-{uuid.uuid4().hex[:8]}" + agent_b_id = f"agent-b-{uuid.uuid4().hex[:12]}" + agent_b_name = agent_b_id payload_b = make_agent_payload(agent_id=agent_b_id, name=agent_b_name) client.post("/api/v1/agents/initAgent", json=payload_b) @@ -537,8 +540,8 @@ def test_patch_agent_remove_evaluator_blocked_by_control(client: TestClient) -> Then: Returns 409 with error message about referencing control """ # Given: Agent with custom evaluator - agent_id = str(uuid.uuid4()) - agent_name = f"Test Agent {uuid.uuid4().hex[:8]}" + agent_id = f"agent-{uuid.uuid4().hex[:12]}" + agent_name = agent_id payload = make_agent_payload( agent_id=agent_id, name=agent_name, @@ -588,8 +591,8 @@ def test_patch_agent_remove_evaluator_allowed_without_policy(client: TestClient) Then: Succeeds since no controls can reference it """ # Given: Agent with custom evaluator but no policy - agent_id = str(uuid.uuid4()) - agent_name = f"Test Agent {uuid.uuid4().hex[:8]}" + agent_id = f"agent-{uuid.uuid4().hex[:12]}" + agent_name = agent_id payload = make_agent_payload( agent_id=agent_id, name=agent_name, diff --git a/server/tests/test_observability_direct_ingest.py b/server/tests/test_observability_direct_ingest.py index 23d3e045..f0d3b964 100644 --- a/server/tests/test_observability_direct_ingest.py +++ b/server/tests/test_observability_direct_ingest.py @@ -15,7 +15,7 @@ class FailingStore(EventStore): async def store(self, events: list[ControlExecutionEvent]) -> int: raise RuntimeError("boom") - async def query_stats(self, agent_uuid, time_range, control_id=None): # pragma: no cover - not used + async def query_stats(self, agent_name, time_range, control_id=None): # pragma: no cover - not used raise NotImplementedError async def query_events(self, query): # pragma: no cover - not used @@ -30,7 +30,7 @@ async def store(self, events: list[ControlExecutionEvent]) -> int: self.calls.append(events) return len(events) - async def query_stats(self, agent_uuid, time_range, control_id=None): # pragma: no cover - not used + async def query_stats(self, agent_name, time_range, control_id=None): # pragma: no cover - not used raise NotImplementedError async def query_events(self, query): # pragma: no cover - not used @@ -45,8 +45,7 @@ async def test_direct_ingestor_drops_on_store_error() -> None: ControlExecutionEvent( trace_id="a" * 32, span_id="b" * 16, - agent_uuid=uuid4(), - agent_name="agent", + agent_name="agent-test-01", control_id=1, control_name="c", check_stage="pre", @@ -74,8 +73,7 @@ async def test_direct_ingestor_logs_when_enabled(caplog: pytest.LogCaptureFixtur event = ControlExecutionEvent( trace_id="a" * 32, span_id="b" * 16, - agent_uuid=uuid4(), - agent_name="agent", + agent_name="agent-test-01", control_id=1, control_name="c", check_stage="pre", diff --git a/server/tests/test_observability_endpoints.py b/server/tests/test_observability_endpoints.py index ec96400f..aaa9274e 100644 --- a/server/tests/test_observability_endpoints.py +++ b/server/tests/test_observability_endpoints.py @@ -17,7 +17,7 @@ def create_test_event( control_id: int = 1, - agent_uuid: str | UUID | None = None, + agent_name: str | UUID | None = None, action: str = "allow", matched: bool = False, timestamp: datetime | None = None, @@ -27,8 +27,7 @@ def create_test_event( return ControlExecutionEvent( trace_id="a" * 32, # 128-bit hex (32 chars) span_id="b" * 16, # 64-bit hex (16 chars) - agent_uuid=agent_uuid or uuid4(), - agent_name="test-agent", + agent_name=agent_name or f"agent-{uuid4().hex[:12]}", control_id=control_id, control_name=f"control-{control_id}", check_stage="pre", @@ -107,7 +106,7 @@ def test_query_request_with_filters(self): """Test query request with various filters.""" request = EventQueryRequest( trace_id="a" * 32, - agent_uuid=uuid4(), + agent_name=f"agent-{uuid4().hex[:12]}", control_ids=[1, 2, 3], actions=["allow", "deny"], matched=True, @@ -153,8 +152,7 @@ def test_event_with_all_fields(self): event = ControlExecutionEvent( trace_id="a" * 32, span_id="b" * 16, - agent_uuid=uuid4(), - agent_name="test-agent", + agent_name="test-agent", control_id=1, control_name="test-control", check_stage="post", @@ -226,15 +224,15 @@ class TestStatsTimeseries: async def test_stats_without_timeseries(self, client: TestClient, setup_observability): """Default response has no timeseries.""" store = setup_observability - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" # Create and store an event - event = create_test_event(agent_uuid=agent_uuid, matched=True) + event = create_test_event(agent_name=agent_name, matched=True) await store.store([event]) response = client.get( "/api/v1/observability/stats", - params={"agent_uuid": str(agent_uuid), "time_range": "1h"}, + params={"agent_name": str(agent_name), "time_range": "1h"}, ) assert response.status_code == 200 @@ -246,27 +244,27 @@ async def test_stats_without_timeseries(self, client: TestClient, setup_observab async def test_stats_with_timeseries(self, client: TestClient, setup_observability): """With include_timeseries=true, returns buckets.""" store = setup_observability - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" now = datetime.now(timezone.utc) # Create events spread across time events = [ create_test_event( - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, action="allow", timestamp=now - timedelta(minutes=30), execution_duration_ms=10.0, ), create_test_event( - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, action="deny", timestamp=now - timedelta(minutes=15), execution_duration_ms=20.0, ), create_test_event( - agent_uuid=agent_uuid, + agent_name=agent_name, matched=False, timestamp=now - timedelta(minutes=5), ), @@ -277,7 +275,7 @@ async def test_stats_with_timeseries(self, client: TestClient, setup_observabili response = client.get( "/api/v1/observability/stats", params={ - "agent_uuid": str(agent_uuid), + "agent_name": str(agent_name), "time_range": "1h", "include_timeseries": "true", }, @@ -303,12 +301,12 @@ async def test_stats_with_timeseries(self, client: TestClient, setup_observabili async def test_timeseries_bucket_count_1h(self, client: TestClient, setup_observability): """Verify reasonable number of buckets for 1h time range (5m buckets).""" store = setup_observability - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" now = datetime.now(timezone.utc) # Create a single event event = create_test_event( - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, timestamp=now - timedelta(minutes=30), ) @@ -318,7 +316,7 @@ async def test_timeseries_bucket_count_1h(self, client: TestClient, setup_observ response = client.get( "/api/v1/observability/stats", params={ - "agent_uuid": str(agent_uuid), + "agent_name": str(agent_name), "time_range": "1h", "include_timeseries": "true", }, @@ -334,12 +332,12 @@ async def test_timeseries_bucket_count_1h(self, client: TestClient, setup_observ async def test_timeseries_bucket_count_5m(self, client: TestClient, setup_observability): """Verify reasonable number of buckets for 5m time range (30s buckets).""" store = setup_observability - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" now = datetime.now(timezone.utc) # Create a single event event = create_test_event( - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, timestamp=now - timedelta(minutes=2), ) @@ -349,7 +347,7 @@ async def test_timeseries_bucket_count_5m(self, client: TestClient, setup_observ response = client.get( "/api/v1/observability/stats", params={ - "agent_uuid": str(agent_uuid), + "agent_name": str(agent_name), "time_range": "5m", "include_timeseries": "true", }, @@ -367,28 +365,28 @@ async def test_timeseries_aggregates_events_per_bucket( ): """Events in the same bucket are aggregated.""" store = setup_observability - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" now = datetime.now(timezone.utc) # Create multiple events in the same 5-minute bucket base_time = now - timedelta(minutes=10) events = [ create_test_event( - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, action="allow", timestamp=base_time + timedelta(seconds=30), execution_duration_ms=10.0, ), create_test_event( - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, action="deny", timestamp=base_time + timedelta(seconds=60), execution_duration_ms=20.0, ), create_test_event( - agent_uuid=agent_uuid, + agent_name=agent_name, matched=False, timestamp=base_time + timedelta(seconds=90), execution_duration_ms=30.0, @@ -400,7 +398,7 @@ async def test_timeseries_aggregates_events_per_bucket( response = client.get( "/api/v1/observability/stats", params={ - "agent_uuid": str(agent_uuid), + "agent_name": str(agent_name), "time_range": "1h", "include_timeseries": "true", }, @@ -434,12 +432,12 @@ async def test_timeseries_empty_buckets_included( ): """Empty buckets are included with zero counts.""" store = setup_observability - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" now = datetime.now(timezone.utc) # Create events only at the start of the time range event = create_test_event( - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, timestamp=now - timedelta(minutes=55), ) @@ -449,7 +447,7 @@ async def test_timeseries_empty_buckets_included( response = client.get( "/api/v1/observability/stats", params={ - "agent_uuid": str(agent_uuid), + "agent_name": str(agent_name), "time_range": "1h", "include_timeseries": "true", }, @@ -484,27 +482,27 @@ class TestControlStats: async def test_control_stats_basic(self, client: TestClient, setup_observability): """Test getting stats for a single control.""" store = setup_observability - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" # Create events for multiple controls events = [ - create_test_event(control_id=1, agent_uuid=agent_uuid, matched=True, action="allow"), - create_test_event(control_id=1, agent_uuid=agent_uuid, matched=True, action="deny"), - create_test_event(control_id=2, agent_uuid=agent_uuid, matched=True, action="warn"), + create_test_event(control_id=1, agent_name=agent_name, matched=True, action="allow"), + create_test_event(control_id=1, agent_name=agent_name, matched=True, action="deny"), + create_test_event(control_id=2, agent_name=agent_name, matched=True, action="warn"), ] await store.store(events) # Get stats for control 1 only response = client.get( "/api/v1/observability/stats/controls/1", - params={"agent_uuid": str(agent_uuid), "time_range": "1h"}, + params={"agent_name": str(agent_name), "time_range": "1h"}, ) assert response.status_code == 200 data = response.json() # Verify response structure - assert data["agent_uuid"] == str(agent_uuid) + assert data["agent_name"] == str(agent_name) assert data["control_id"] == 1 assert data["control_name"] == "control-1" assert "stats" in data @@ -520,21 +518,21 @@ async def test_control_stats_basic(self, client: TestClient, setup_observability async def test_control_stats_with_timeseries(self, client: TestClient, setup_observability): """Test control stats with timeseries.""" store = setup_observability - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" now = datetime.now(timezone.utc) # Create events for control 1 at different times events = [ create_test_event( control_id=1, - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, action="allow", timestamp=now - timedelta(minutes=30), ), create_test_event( control_id=1, - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, action="deny", timestamp=now - timedelta(minutes=10), @@ -542,7 +540,7 @@ async def test_control_stats_with_timeseries(self, client: TestClient, setup_obs # Control 2 event (should not appear) create_test_event( control_id=2, - agent_uuid=agent_uuid, + agent_name=agent_name, matched=True, action="warn", timestamp=now - timedelta(minutes=20), @@ -553,7 +551,7 @@ async def test_control_stats_with_timeseries(self, client: TestClient, setup_obs response = client.get( "/api/v1/observability/stats/controls/1", params={ - "agent_uuid": str(agent_uuid), + "agent_name": str(agent_name), "time_range": "1h", "include_timeseries": "true", }, @@ -579,16 +577,16 @@ async def test_control_stats_with_timeseries(self, client: TestClient, setup_obs async def test_control_stats_no_data(self, client: TestClient, setup_observability): """Test control stats when control has no events.""" store = setup_observability - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" # Create event for control 1 only - event = create_test_event(control_id=1, agent_uuid=agent_uuid, matched=True) + event = create_test_event(control_id=1, agent_name=agent_name, matched=True) await store.store([event]) # Query for control 2 (no events) response = client.get( "/api/v1/observability/stats/controls/2", - params={"agent_uuid": str(agent_uuid), "time_range": "1h"}, + params={"agent_name": str(agent_name), "time_range": "1h"}, ) assert response.status_code == 200 @@ -647,12 +645,12 @@ def test_query_events_filters_and_pagination(self, client: TestClient, setup_obs def test_get_stats_aggregates_events(self, client: TestClient, setup_observability): """Test GET /stats aggregates events for an agent.""" # Given: events for a specific agent and one other agent - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" event1 = create_test_event(control_id=1, action="allow", matched=True).model_copy( - update={"agent_uuid": agent_uuid} + update={"agent_name": agent_name} ) event2 = create_test_event(control_id=2, action="deny", matched=True).model_copy( - update={"agent_uuid": agent_uuid, "trace_id": "c" * 32} + update={"agent_name": agent_name, "trace_id": "c" * 32} ) # Event from different agent (should not be counted) event3 = create_test_event(control_id=1, action="warn", matched=True).model_copy( @@ -669,7 +667,7 @@ def test_get_stats_aggregates_events(self, client: TestClient, setup_observabili # When: getting stats for the agent resp = client.get( "/api/v1/observability/stats", - params={"agent_uuid": str(agent_uuid), "time_range": "1h"}, + params={"agent_name": str(agent_name), "time_range": "1h"}, ) assert resp.status_code == 200 diff --git a/server/tests/test_observability_models.py b/server/tests/test_observability_models.py index c34f172d..18335c83 100644 --- a/server/tests/test_observability_models.py +++ b/server/tests/test_observability_models.py @@ -26,8 +26,7 @@ def test_valid_event(self): event = ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="00f067aa0ba902b7", - agent_uuid=uuid4(), - agent_name="test-agent", + agent_name="test-agent", control_id=123, control_name="sql-injection-check", check_stage="pre", @@ -49,7 +48,6 @@ def test_trace_id_validation_empty(self): ControlExecutionEvent( trace_id="", span_id="00f067aa0ba902b7", - agent_uuid=uuid4(), agent_name="test-agent", control_id=123, control_name="test", @@ -67,8 +65,7 @@ def test_trace_id_accepts_various_formats(self): event = ControlExecutionEvent( trace_id="my-custom-trace-id", # non-hex, non-32-char span_id="my-span", - agent_uuid=uuid4(), - agent_name="test-agent", + agent_name="test-agent", control_id=123, control_name="test", check_stage="pre", @@ -86,7 +83,6 @@ def test_span_id_validation_empty(self): ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="", - agent_uuid=uuid4(), agent_name="test-agent", control_id=123, control_name="test", @@ -104,7 +100,6 @@ def test_confidence_bounds(self): ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="00f067aa0ba902b7", - agent_uuid=uuid4(), agent_name="test-agent", control_id=123, control_name="test", @@ -121,7 +116,6 @@ def test_check_stage_values(self): ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="00f067aa0ba902b7", - agent_uuid=uuid4(), agent_name="test-agent", control_id=123, control_name="test", @@ -138,7 +132,6 @@ def test_action_values(self): event = ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="00f067aa0ba902b7", - agent_uuid=uuid4(), agent_name="test-agent", control_id=123, control_name="test", @@ -155,8 +148,7 @@ def test_timestamp_default(self): event = ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="00f067aa0ba902b7", - agent_uuid=uuid4(), - agent_name="test-agent", + agent_name="test-agent", control_id=123, control_name="test", check_stage="pre", @@ -174,8 +166,7 @@ def test_optional_fields(self): event = ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="00f067aa0ba902b7", - agent_uuid=uuid4(), - agent_name="test-agent", + agent_name="test-agent", control_id=123, control_name="test", check_stage="pre", @@ -196,12 +187,11 @@ def test_optional_fields(self): def test_to_dict(self): """Test serialization to dict.""" - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" event = ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="00f067aa0ba902b7", - agent_uuid=agent_uuid, - agent_name="test-agent", + agent_name=agent_name, control_id=123, control_name="test", check_stage="pre", @@ -212,7 +202,7 @@ def test_to_dict(self): ) data = event.to_dict() assert data["trace_id"] == "4bf92f3577b34da6a3ce929d0e0e4736" - assert data["agent_uuid"] == agent_uuid + assert data["agent_name"] == agent_name assert data["matched"] is False @@ -225,7 +215,6 @@ def test_valid_batch(self): ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="00f067aa0ba902b7", - agent_uuid=uuid4(), agent_name="test-agent", control_id=i, control_name=f"control-{i}", @@ -251,7 +240,6 @@ def test_max_batch_size(self): ControlExecutionEvent( trace_id="4bf92f3577b34da6a3ce929d0e0e4736", span_id="00f067aa0ba902b7", - agent_uuid=uuid4(), agent_name="test-agent", control_id=i, control_name=f"control-{i}", @@ -353,7 +341,7 @@ class TestStatsRequest: def test_valid_request(self): """Test creating valid stats request.""" request = StatsRequest( - agent_uuid=uuid4(), + agent_name=f"agent-{uuid4().hex[:12]}", time_range="5m", ) assert request.time_range == "5m" @@ -362,11 +350,10 @@ def test_time_range_values(self): """Test valid time range values.""" for time_range in ["1m", "5m", "15m", "1h", "24h", "7d", "30d", "180d", "365d"]: request = StatsRequest( - agent_uuid=uuid4(), + agent_name=f"agent-{uuid4().hex[:12]}", time_range=time_range, ) assert request.time_range == time_range - diff --git a/server/tests/test_observability_store_postgres.py b/server/tests/test_observability_store_postgres.py index 13d50c89..2b53385e 100644 --- a/server/tests/test_observability_store_postgres.py +++ b/server/tests/test_observability_store_postgres.py @@ -21,7 +21,7 @@ def clear_event_table() -> None: def _event( *, - agent_uuid, + agent_name, control_id: int, action: str, matched: bool, @@ -35,8 +35,7 @@ def _event( return ControlExecutionEvent( trace_id=trace_id, span_id=span_id, - agent_uuid=agent_uuid, - agent_name="agent", + agent_name=agent_name, control_id=control_id, control_name=f"control-{control_id}", check_stage=check_stage, @@ -59,12 +58,12 @@ async def test_postgres_event_store_query_events_and_stats() -> None: ) store = PostgresEventStore(session_maker) - agent_uuid = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" now = datetime.now(UTC) events = [ _event( - agent_uuid=agent_uuid, + agent_name=agent_name, control_id=1, action="allow", matched=True, @@ -72,7 +71,7 @@ async def test_postgres_event_store_query_events_and_stats() -> None: trace_id="a" * 32, ), _event( - agent_uuid=agent_uuid, + agent_name=agent_name, control_id=2, action="deny", matched=False, @@ -80,7 +79,7 @@ async def test_postgres_event_store_query_events_and_stats() -> None: trace_id="b" * 32, ), _event( - agent_uuid=agent_uuid, + agent_name=agent_name, control_id=1, action="allow", matched=True, @@ -93,7 +92,7 @@ async def test_postgres_event_store_query_events_and_stats() -> None: await store.store(events) # When: querying events filtered by control_id - query = EventQueryRequest(agent_uuid=agent_uuid, control_ids=[1], limit=10, offset=0) + query = EventQueryRequest(agent_name=agent_name, control_ids=[1], limit=10, offset=0) resp = await store.query_events(query) # Then: only matching events are returned assert resp.total == 2 @@ -107,7 +106,7 @@ async def test_postgres_event_store_query_events_and_stats() -> None: assert all(e.trace_id == "a" * 32 for e in resp.events) # When: querying stats - stats = await store.query_stats(agent_uuid, timedelta(hours=1)) + stats = await store.query_stats(agent_name, timedelta(hours=1)) # Then: totals and action counts are aggregated correctly assert stats.total_executions == 3 assert stats.total_matches == 2 @@ -116,7 +115,7 @@ async def test_postgres_event_store_query_events_and_stats() -> None: assert stats.action_counts == {"allow": 2} # When: querying stats with a control filter - filtered_stats = await store.query_stats(agent_uuid, timedelta(hours=1), control_id=1) + filtered_stats = await store.query_stats(agent_name, timedelta(hours=1), control_id=1) # Then: only the requested control is returned assert len(filtered_stats.stats) == 1 assert filtered_stats.stats[0].control_id == 1 @@ -149,8 +148,8 @@ async def test_postgres_event_store_query_events_all_filters() -> None: ) store = PostgresEventStore(session_maker) - agent_uuid = uuid4() - other_agent = uuid4() + agent_name = f"agent-{uuid4().hex[:12]}" + other_agent = f"agent-{uuid4().hex[:12]}" now = datetime.now(UTC) target_exec_id = "exec-1" @@ -159,7 +158,7 @@ async def test_postgres_event_store_query_events_all_filters() -> None: events = [ _event( - agent_uuid=agent_uuid, + agent_name=agent_name, control_id=1, action="allow", matched=True, @@ -171,7 +170,7 @@ async def test_postgres_event_store_query_events_all_filters() -> None: applies_to="llm_call", ), _event( - agent_uuid=other_agent, + agent_name=other_agent, control_id=2, action="deny", matched=False, @@ -189,7 +188,7 @@ async def test_postgres_event_store_query_events_all_filters() -> None: # When: querying with all supported filters query = EventQueryRequest( control_execution_id=target_exec_id, - agent_uuid=agent_uuid, + agent_name=agent_name, start_time=now - timedelta(seconds=2), end_time=now, trace_id=target_trace_id, @@ -215,8 +214,7 @@ async def test_postgres_event_store_parses_string_json_rows() -> None: event = ControlExecutionEvent( trace_id="a" * 32, span_id="b" * 16, - agent_uuid=uuid4(), - agent_name="agent", + agent_name="agent-test-01", control_id=1, control_name="control-1", check_stage="pre", diff --git a/server/tests/test_policy_integration.py b/server/tests/test_policy_integration.py index b5789421..446c520a 100644 --- a/server/tests/test_policy_integration.py +++ b/server/tests/test_policy_integration.py @@ -6,12 +6,12 @@ def _create_agent(client: TestClient, name: str | None = None) -> tuple[str, str]: - """Helper: Create an agent and return (agent_id, agent_name).""" - agent_id = str(uuid.uuid4()) - agent_name = name or f"agent-{uuid.uuid4()}" + """Helper: Create an agent and return (agent_name, agent_name).""" + agent_name = (name or f"agent-{uuid.uuid4().hex[:12]}").lower() + if len(agent_name) < 10: + agent_name = f"{agent_name}-agent".replace("--", "-") payload = { "agent": { - "agent_id": agent_id, "agent_name": agent_name, "agent_description": "test", "agent_version": "1.0", @@ -21,7 +21,7 @@ def _create_agent(client: TestClient, name: str | None = None) -> tuple[str, str } resp = client.post("/api/v1/agents/initAgent", json=payload) assert resp.status_code == 200 - return agent_id, agent_name + return agent_name, agent_name def _create_policy(client: TestClient, name: str | None = None) -> int: @@ -281,8 +281,8 @@ def test_control_shared_between_policies(client: TestClient) -> None: assert control_id in resp_b.json()["control_ids"] # And: Agents with either policy see the control - agent_a_id, _ = _create_agent(client, "agent-a") - agent_b_id, _ = _create_agent(client, "agent-b") + agent_a_id, _ = _create_agent(client, "agent-alpha-01") + agent_b_id, _ = _create_agent(client, "agent-beta-02") client.post(f"/api/v1/agents/{agent_a_id}/policy/{policy_a_id}") client.post(f"/api/v1/agents/{agent_b_id}/policy/{policy_b_id}") diff --git a/server/tests/test_services_controls.py b/server/tests/test_services_controls.py index e4d26670..797835d2 100644 --- a/server/tests/test_services_controls.py +++ b/server/tests/test_services_controls.py @@ -46,7 +46,6 @@ async def test_list_controls_for_agent_returns_controls(async_db) -> None: policy = Policy(name=f"policy-{uuid.uuid4()}") control = Control(name=f"control-{uuid.uuid4()}", data=VALID_CONTROL_PAYLOAD) agent = Agent( - agent_uuid=uuid.uuid4(), name=f"agent-{uuid.uuid4()}", data={}, policy=policy, @@ -60,7 +59,7 @@ async def test_list_controls_for_agent_returns_controls(async_db) -> None: await async_db.commit() # When: listing controls for the agent - controls = await list_controls_for_agent(agent.agent_uuid, async_db) + controls = await list_controls_for_agent(agent.name, async_db) # Then: the API control is returned with expected fields assert len(controls) == 1 @@ -74,7 +73,6 @@ async def test_list_controls_for_agent_corrupted_data_raises(async_db) -> None: policy = Policy(name=f"policy-{uuid.uuid4()}") control = Control(name=f"control-{uuid.uuid4()}", data={"bad": "data"}) agent = Agent( - agent_uuid=uuid.uuid4(), name=f"agent-{uuid.uuid4()}", data={}, policy=policy, @@ -89,7 +87,7 @@ async def test_list_controls_for_agent_corrupted_data_raises(async_db) -> None: # When: listing controls for the agent with pytest.raises(APIValidationError) as exc_info: - await list_controls_for_agent(agent.agent_uuid, async_db) + await list_controls_for_agent(agent.name, async_db) # Then: corrupted data error is raised assert exc_info.value.error_code == ErrorCode.CORRUPTED_DATA diff --git a/server/tests/utils.py b/server/tests/utils.py index 73efa678..a2a32098 100644 --- a/server/tests/utils.py +++ b/server/tests/utils.py @@ -18,8 +18,8 @@ def create_and_assign_policy( client: TestClient, control_config: dict[str, Any] | None = None, - agent_name: str = "MyTestAgent", -) -> tuple[uuid.UUID, str]: + agent_name: str = "mytestagent01", +) -> tuple[str, str]: """Helper to setup Agent -> Policy -> Control hierarchy. Args: @@ -28,7 +28,7 @@ def create_and_assign_policy( agent_name: Name for the test agent Returns: - tuple: (agent_uuid, control_name) + tuple: (agent_name, control_name) """ if control_config is None: control_config = VALID_CONTROL_PAYLOAD.copy() @@ -54,18 +54,19 @@ def create_and_assign_policy( assert resp.status_code == 200 # 5. Register Agent - agent_uuid = uuid.uuid4() + normalized_agent_name = agent_name.lower() + if len(normalized_agent_name) < 10: + normalized_agent_name = f"{normalized_agent_name}-agent".replace("--", "-") resp = client.post("/api/v1/agents/initAgent", json={ "agent": { - "agent_id": str(agent_uuid), - "agent_name": agent_name + "agent_name": normalized_agent_name }, "steps": [] }) assert resp.status_code == 200 # 6. Assign Policy to Agent - resp = client.post(f"/api/v1/agents/{str(agent_uuid)}/policy/{policy_id}") + resp = client.post(f"/api/v1/agents/{normalized_agent_name}/policy/{policy_id}") assert resp.status_code == 200 - return agent_uuid, control_name + return normalized_agent_name, control_name diff --git a/ui/src/core/api/client.ts b/ui/src/core/api/client.ts index c72911a9..3facbcb0 100644 --- a/ui/src/core/api/client.ts +++ b/ui/src/core/api/client.ts @@ -55,27 +55,30 @@ export const api = { apiClient.GET('/api/v1/agents', { params: { query: params }, }), - get: (agentId: GetAgentPathParams['agent_id']) => - apiClient.GET('/api/v1/agents/{agent_id}', { - params: { path: { agent_id: agentId } }, + get: (agentName: GetAgentPathParams['agent_name']) => + apiClient.GET('/api/v1/agents/{agent_name}', { + params: { path: { agent_name: agentName } }, }), initAgent: (data: InitAgentRequestBody) => apiClient.POST('/api/v1/agents/initAgent', { body: data }), - getControls: (agentId: GetAgentControlsPathParams['agent_id']) => - apiClient.GET('/api/v1/agents/{agent_id}/controls', { - params: { path: { agent_id: agentId } }, + getControls: (agentName: GetAgentControlsPathParams['agent_name']) => + apiClient.GET('/api/v1/agents/{agent_name}/controls', { + params: { path: { agent_name: agentName } }, }), - setPolicy: (agentId: GetAgentPathParams['agent_id'], policyId: number) => - apiClient.POST('/api/v1/agents/{agent_id}/policy/{policy_id}', { - params: { path: { agent_id: agentId, policy_id: policyId } }, + setPolicy: ( + agentName: GetAgentPathParams['agent_name'], + policyId: number + ) => + apiClient.POST('/api/v1/agents/{agent_name}/policy/{policy_id}', { + params: { path: { agent_name: agentName, policy_id: policyId } }, }), - getPolicy: (agentId: GetAgentPathParams['agent_id']) => - apiClient.GET('/api/v1/agents/{agent_id}/policy', { - params: { path: { agent_id: agentId } }, + getPolicy: (agentName: GetAgentPathParams['agent_name']) => + apiClient.GET('/api/v1/agents/{agent_name}/policy', { + params: { path: { agent_name: agentName } }, }), - deletePolicy: (agentId: GetAgentPathParams['agent_id']) => - apiClient.DELETE('/api/v1/agents/{agent_id}/policy', { - params: { path: { agent_id: agentId } }, + deletePolicy: (agentName: GetAgentPathParams['agent_name']) => + apiClient.DELETE('/api/v1/agents/{agent_name}/policy', { + params: { path: { agent_name: agentName } }, }), }, evaluators: { @@ -143,7 +146,7 @@ export const api = { }, observability: { getStats: (params: { - agent_uuid: string; + agent_name: string; time_range?: | '1m' | '5m' diff --git a/ui/src/core/api/generated/api-types.ts b/ui/src/core/api/generated/api-types.ts index c1e36591..f90c3286 100644 --- a/ui/src/core/api/generated/api-types.ts +++ b/ui/src/core/api/generated/api-types.ts @@ -4,3933 +4,4056 @@ */ export interface paths { - '/api/v1/agents': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/api/v1/agents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List all agents + * @description List all registered agents with cursor-based pagination. + * + * Returns a summary of each agent including identifier, policy assignment, + * and counts of registered steps and evaluators. + * + * Args: + * cursor: Optional cursor for pagination (last agent name from previous page) + * limit: Pagination limit (default 20, max 100) + * name: Optional name filter (case-insensitive partial match) + * db: Database session (injected) + * + * Returns: + * ListAgentsResponse with agent summaries and pagination info + */ + get: operations["list_agents_api_v1_agents_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/agents/initAgent": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Initialize or update an agent + * @description Register a new agent or update an existing agent's steps and metadata. + * + * This endpoint is idempotent: + * - If the agent name doesn't exist, creates a new agent + * - If the agent name exists, updates registration data in place + * + * conflict_mode controls registration conflict handling: + * - strict (default): preserve compatibility checks and conflict errors + * - overwrite: latest init payload replaces steps/evaluators and returns change summary + * + * Args: + * request: Agent metadata and step schemas + * db: Database session (injected) + * + * Returns: + * InitAgentResponse with created flag and active controls (if policy assigned) + */ + post: operations["init_agent_api_v1_agents_initAgent_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/agents/{agent_name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get agent details + * @description Retrieve agent metadata and all registered steps. + * + * Returns the latest version of each step (deduplicated by type+name). + * + * Args: + * agent_name: Agent identifier + * db: Database session (injected) + * + * Returns: + * GetAgentResponse with agent metadata and step list + * + * Raises: + * HTTPException 404: Agent not found + * HTTPException 422: Agent data is corrupted + */ + get: operations["get_agent_api_v1_agents__agent_name__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * Modify agent (remove steps/evaluators) + * @description Remove steps and/or evaluators from an agent. + * + * This is the complement to initAgent which only adds items. + * Removals are idempotent - attempting to remove non-existent items is not an error. + * + * Args: + * agent_name: Agent identifier + * request: Lists of step/evaluator identifiers to remove + * db: Database session (injected) + * + * Returns: + * PatchAgentResponse with lists of actually removed items + * + * Raises: + * HTTPException 404: Agent not found + * HTTPException 500: Database error during update + */ + patch: operations["patch_agent_api_v1_agents__agent_name__patch"]; + trace?: never; + }; + "/api/v1/agents/{agent_name}/controls": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List agent's active controls + * @description List all protection controls active for an agent. + * + * Controls are inherited from the agent's assigned policy. + * Returns an empty list if the agent has no policy. + * + * Args: + * agent_name: Agent identifier + * db: Database session (injected) + * + * Returns: + * AgentControlsResponse with list of controls (empty if no policy) + * + * Raises: + * HTTPException 404: Agent not found + */ + get: operations["list_agent_controls_api_v1_agents__agent_name__controls_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/agents/{agent_name}/evaluators": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List agent's registered evaluator schemas + * @description List all evaluator schemas registered with an agent. + * + * Evaluator schemas are registered via initAgent and used for: + * - Config validation when creating Controls + * - UI to display available config options + * + * Args: + * agent_name: Agent identifier + * cursor: Optional cursor for pagination (name of last evaluator from previous page) + * limit: Pagination limit (default 20, max 100) + * db: Database session (injected) + * + * Returns: + * ListEvaluatorsResponse with evaluator schemas and pagination + * + * Raises: + * HTTPException 404: Agent not found + */ + get: operations["list_agent_evaluators_api_v1_agents__agent_name__evaluators_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/agents/{agent_name}/evaluators/{evaluator_name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get specific evaluator schema + * @description Get a specific evaluator schema registered with an agent. + * + * Args: + * agent_name: Agent identifier + * evaluator_name: Name of the evaluator + * db: Database session (injected) + * + * Returns: + * EvaluatorSchemaItem with schema details + * + * Raises: + * HTTPException 404: Agent or evaluator not found + */ + get: operations["get_agent_evaluator_api_v1_agents__agent_name__evaluators__evaluator_name__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/agents/{agent_name}/policy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get agent's assigned policy + * @description Retrieve the policy currently assigned to an agent. + * + * Args: + * agent_name: Agent identifier + * db: Database session (injected) + * + * Returns: + * GetPolicyResponse with policy ID + * + * Raises: + * HTTPException 404: Agent not found or agent has no policy assigned + */ + get: operations["get_agent_policy_api_v1_agents__agent_name__policy_get"]; + put?: never; + post?: never; + /** + * Remove agent's policy assignment + * @description Remove the policy assignment from an agent. + * + * The agent will no longer have any protection controls active. + * + * Args: + * agent_name: Agent identifier + * db: Database session (injected) + * + * Returns: + * DeletePolicyResponse with success flag + * + * Raises: + * HTTPException 404: Agent not found or agent has no policy assigned + * HTTPException 500: Database error during removal + */ + delete: operations["delete_agent_policy_api_v1_agents__agent_name__policy_delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/agents/{agent_name}/policy/{policy_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Assign policy to agent + * @description Assign a policy to an agent, replacing any existing policy assignment. + * + * The agent will immediately inherit all controls from the assigned policy. + * + * Args: + * agent_name: Agent identifier + * policy_id: ID of the policy to assign + * db: Database session (injected) + * + * Returns: + * SetPolicyResponse with success flag and previous policy ID (if any) + * + * Raises: + * HTTPException 404: Agent or policy not found + * HTTPException 500: Database error during assignment + */ + post: operations["set_agent_policy_api_v1_agents__agent_name__policy__policy_id__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/controls": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List all controls + * @description List all controls with optional filtering and cursor-based pagination. + * + * Controls are returned ordered by ID descending (newest first). + * + * Args: + * cursor: ID of the last control from the previous page (for pagination) + * limit: Maximum number of controls to return (default 20, max 100) + * name: Optional filter by name (partial, case-insensitive match) + * enabled: Optional filter by enabled status + * step_type: Optional filter by step type (built-ins: 'tool', 'llm') + * stage: Optional filter by stage ('pre' or 'post') + * execution: Optional filter by execution ('server' or 'sdk') + * tag: Optional filter by tag + * db: Database session (injected) + * + * Returns: + * ListControlsResponse with control summaries and pagination info + * + * Example: + * GET /controls?limit=10&enabled=true&step_type=tool + */ + get: operations["list_controls_api_v1_controls_get"]; + /** + * Create a new control + * @description Create a new control with a unique name and empty data. + * + * Controls define protection logic and can be added to policies. + * Use the PUT /{control_id}/data endpoint to set control configuration. + * + * Args: + * request: Control creation request with unique name + * db: Database session (injected) + * + * Returns: + * CreateControlResponse with the new control's ID + * + * Raises: + * HTTPException 409: Control with this name already exists + * HTTPException 500: Database error during creation + */ + put: operations["create_control_api_v1_controls_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/controls/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Validate control configuration + * @description Validate control configuration data without saving it. + * + * Args: + * request: Control configuration data to validate + * db: Database session (injected) + * + * Returns: + * ValidateControlDataResponse with success=True if valid + */ + post: operations["validate_control_data_api_v1_controls_validate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/controls/{control_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get control details + * @description Retrieve a control by ID including its name and configuration data. + * + * Args: + * control_id: ID of the control + * db: Database session (injected) + * + * Returns: + * GetControlResponse with control id, name, and data + * + * Raises: + * HTTPException 404: Control not found + */ + get: operations["get_control_api_v1_controls__control_id__get"]; + put?: never; + post?: never; + /** + * Delete a control + * @description Delete a control by ID. + * + * By default, deletion fails if the control is associated with any policy. + * Use force=true to automatically dissociate and delete. + * + * Args: + * control_id: ID of the control to delete + * force: If true, remove associations before deleting + * db: Database session (injected) + * + * Returns: + * DeleteControlResponse with success flag and list of dissociated policies + * + * Raises: + * HTTPException 404: Control not found + * HTTPException 409: Control is in use (and force=false) + * HTTPException 500: Database error during deletion + */ + delete: operations["delete_control_api_v1_controls__control_id__delete"]; + options?: never; + head?: never; + /** + * Update control metadata + * @description Update control metadata (name and/or enabled status). + * + * This endpoint allows partial updates: + * - To rename: provide 'name' field + * - To enable/disable: provide 'enabled' field (updates the control's data) + * + * Args: + * control_id: ID of the control to update + * request: Fields to update (name, enabled) + * db: Database session (injected) + * + * Returns: + * PatchControlResponse with current control state + * + * Raises: + * HTTPException 404: Control not found + * HTTPException 409: New name conflicts with existing control + * HTTPException 422: Cannot update enabled status (control has no data configured) + * HTTPException 500: Database error during update + */ + patch: operations["patch_control_api_v1_controls__control_id__patch"]; + trace?: never; + }; + "/api/v1/controls/{control_id}/data": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get control configuration data + * @description Retrieve the configuration data for a control. + * + * Control data is a JSONB field that must follow the ControlDefinition schema. + * + * Args: + * control_id: ID of the control + * db: Database session (injected) + * + * Returns: + * GetControlDataResponse with validated ControlDefinition + * + * Raises: + * HTTPException 404: Control not found + * HTTPException 422: Control data is corrupted + */ + get: operations["get_control_data_api_v1_controls__control_id__data_get"]; + /** + * Update control configuration data + * @description Update the configuration data for a control. + * + * This replaces the entire data payload. The data is validated against + * the ControlDefinition schema. + * + * Args: + * control_id: ID of the control + * request: New control data (replaces existing) + * db: Database session (injected) + * + * Returns: + * SetControlDataResponse with success flag + * + * Raises: + * HTTPException 404: Control not found + * HTTPException 500: Database error during update + */ + put: operations["set_control_data_api_v1_controls__control_id__data_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/evaluation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Analyze content safety + * @description Analyze content for safety and control violations. + * + * Runs all controls assigned to the agent via policy through the + * evaluation engine. Controls are evaluated in parallel with + * cancel-on-deny for efficiency. + * + * Custom evaluators must be deployed as Evaluator classes + * with the engine. Their schemas are registered via initAgent. + * + * Optionally accepts X-Trace-Id and X-Span-Id headers for + * OpenTelemetry-compatible distributed tracing. + */ + post: operations["evaluate_api_v1_evaluation_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/evaluator-configs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List evaluator configs */ + get: operations["list_evaluator_configs_api_v1_evaluator_configs_get"]; + put?: never; + /** Create evaluator config */ + post: operations["create_evaluator_config_api_v1_evaluator_configs_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/evaluator-configs/{config_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get evaluator config */ + get: operations["get_evaluator_config_api_v1_evaluator_configs__config_id__get"]; + /** Update evaluator config */ + put: operations["update_evaluator_config_api_v1_evaluator_configs__config_id__put"]; + post?: never; + /** Delete evaluator config */ + delete: operations["delete_evaluator_config_api_v1_evaluator_configs__config_id__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/evaluators": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List available evaluators + * @description List all available evaluators. + * + * Returns metadata and JSON Schema for each built-in evaluator. + * + * Built-in evaluators: + * - **regex**: Regular expression pattern matching + * - **list**: List-based value matching with flexible logic + * - **json**: JSON validation with schema, types, constraints + * - **sql**: SQL query validation + * + * Custom evaluators are registered per-agent via initAgent. + * Use GET /agents/{agent_name}/evaluators to list agent-specific schemas. + */ + get: operations["get_evaluators_api_v1_evaluators_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/observability/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Ingest Events + * @description Ingest batched control execution events. + * + * Events are stored directly to the database with ~5-20ms latency. + * + * Args: + * request: Batch of events to ingest + * ingestor: Event ingestor (injected) + * + * Returns: + * BatchEventsResponse with counts of received/processed/dropped + */ + post: operations["ingest_events_api_v1_observability_events_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/observability/events/query": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Query Events + * @description Query raw control execution events. + * + * Supports filtering by: + * - trace_id: Get all events for a request + * - span_id: Get all events for a function call + * - control_execution_id: Get a specific event + * - agent_name: Filter by agent + * - control_ids: Filter by controls + * - actions: Filter by actions (allow, deny, warn, log) + * - matched: Filter by matched status + * - check_stages: Filter by check stage (pre, post) + * - applies_to: Filter by call type (llm_call, tool_call) + * - start_time/end_time: Filter by time range + * + * Results are paginated with limit/offset. + * + * Args: + * request: Query parameters + * store: Event store (injected) + * + * Returns: + * EventQueryResponse with matching events and pagination info + */ + post: operations["query_events_api_v1_observability_events_query_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/observability/stats": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Stats + * @description Get agent-level aggregated statistics. + * + * Returns totals across all controls plus per-control breakdown. + * Use /stats/controls/{control_id} for single control stats. + * + * Args: + * agent_name: Agent to get stats for + * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) + * include_timeseries: Include time-series data points for trend visualization + * store: Event store (injected) + * + * Returns: + * StatsResponse with agent-level totals and per-control breakdown + */ + get: operations["get_stats_api_v1_observability_stats_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/observability/stats/controls/{control_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Control Stats + * @description Get statistics for a single control. + * + * Returns stats for the specified control with optional time-series. + * + * Args: + * control_id: Control ID to get stats for + * agent_name: Agent to get stats for + * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) + * include_timeseries: Include time-series data points for trend visualization + * store: Event store (injected) + * + * Returns: + * ControlStatsResponse with control stats and optional timeseries + */ + get: operations["get_control_stats_api_v1_observability_stats_controls__control_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/observability/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Status + * @description Get observability system status. + * + * Returns basic health information. + */ + get: operations["get_status_api_v1_observability_status_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/policies": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Create a new policy + * @description Create a new empty policy with a unique name. + * + * Policies contain controls and can be assigned to agents. + * A newly created policy has no controls until they are explicitly added. + * + * Args: + * request: Policy creation request with unique name + * db: Database session (injected) + * + * Returns: + * CreatePolicyResponse with the new policy's ID + * + * Raises: + * HTTPException 409: Policy with this name already exists + * HTTPException 500: Database error during creation + */ + put: operations["create_policy_api_v1_policies_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/policies/{policy_id}/controls": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List policy's controls + * @description List all controls associated with a policy. + * + * Args: + * policy_id: ID of the policy + * db: Database session (injected) + * + * Returns: + * GetPolicyControlsResponse with list of control IDs + * + * Raises: + * HTTPException 404: Policy not found + */ + get: operations["list_policy_controls_api_v1_policies__policy_id__controls_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/policies/{policy_id}/controls/{control_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Add control to policy + * @description Associate a control with a policy. + * + * This operation is idempotent - adding the same control multiple times has no effect. + * Agents with this policy will immediately see the added control. + * + * Args: + * policy_id: ID of the policy + * control_id: ID of the control to add + * db: Database session (injected) + * + * Returns: + * AssocResponse with success flag + * + * Raises: + * HTTPException 404: Policy or control not found + * HTTPException 500: Database error + */ + post: operations["add_control_to_policy_api_v1_policies__policy_id__controls__control_id__post"]; + /** + * Remove control from policy + * @description Remove a control from a policy. + * + * This operation is idempotent - removing a non-associated control has no effect. + * Agents with this policy will immediately lose the removed control. + * + * Args: + * policy_id: ID of the policy + * control_id: ID of the control to remove + * db: Database session (injected) + * + * Returns: + * AssocResponse with success flag + * + * Raises: + * HTTPException 404: Policy or control not found + * HTTPException 500: Database error + */ + delete: operations["remove_control_from_policy_api_v1_policies__policy_id__controls__control_id__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Health check + * @description Check if the server is running and responsive. + * + * This endpoint does not check database connectivity. + * + * Returns: + * HealthResponse with status and version + */ + get: operations["health_check_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - /** - * List all agents - * @description List all registered agents with cursor-based pagination. - * - * Returns a summary of each agent including ID, name, policy assignment, - * and counts of registered steps and evaluators. - * - * Args: - * cursor: Optional cursor for pagination (UUID of last agent from previous page) - * limit: Pagination limit (default 20, max 100) - * name: Optional name filter (case-insensitive partial match) - * db: Database session (injected) - * - * Returns: - * ListAgentsResponse with agent summaries and pagination info - */ - get: operations['list_agents_api_v1_agents_get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/agents/initAgent': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Initialize or update an agent - * @description Register a new agent or update an existing agent's steps and metadata. - * - * This endpoint is idempotent: - * - If the agent name doesn't exist, creates a new agent - * - If the agent name exists with the same UUID, updates step schemas - * - If the agent name exists with a different UUID, returns 409 Conflict - * - * Step versioning: When step schemas change (input_schema or output_schema), - * a new version is created automatically. - * - * Args: - * request: Agent metadata and step schemas - * db: Database session (injected) - * - * Returns: - * InitAgentResponse with created flag and active controls (if policy assigned) - * - * Raises: - * HTTPException 409: Agent name exists with different UUID - * HTTPException 500: Database error during creation/update - */ - post: operations['init_agent_api_v1_agents_initAgent_post']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/agents/{agent_id}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get agent details - * @description Retrieve agent metadata and all registered steps. - * - * Returns the latest version of each step (deduplicated by type+name). - * - * Args: - * agent_id: UUID of the agent - * db: Database session (injected) - * - * Returns: - * GetAgentResponse with agent metadata and step list - * - * Raises: - * HTTPException 404: Agent not found - * HTTPException 422: Agent data is corrupted - */ - get: operations['get_agent_api_v1_agents__agent_id__get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - /** - * Modify agent (remove steps/evaluators) - * @description Remove steps and/or evaluators from an agent. - * - * This is the complement to initAgent which only adds items. - * Removals are idempotent - attempting to remove non-existent items is not an error. - * - * Args: - * agent_id: UUID of the agent - * request: Lists of step/evaluator identifiers to remove - * db: Database session (injected) - * - * Returns: - * PatchAgentResponse with lists of actually removed items - * - * Raises: - * HTTPException 404: Agent not found - * HTTPException 500: Database error during update - */ - patch: operations['patch_agent_api_v1_agents__agent_id__patch']; - trace?: never; - }; - '/api/v1/agents/{agent_id}/policy/{policy_id}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Assign policy to agent - * @description Assign a policy to an agent, replacing any existing policy assignment. - * - * The agent will immediately inherit all controls from the assigned policy. - * - * Args: - * agent_id: UUID of the agent - * policy_id: ID of the policy to assign - * db: Database session (injected) - * - * Returns: - * SetPolicyResponse with success flag and previous policy ID (if any) - * - * Raises: - * HTTPException 404: Agent or policy not found - * HTTPException 500: Database error during assignment - */ - post: operations['set_agent_policy_api_v1_agents__agent_id__policy__policy_id__post']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/agents/{agent_id}/policy': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get agent's assigned policy - * @description Retrieve the policy currently assigned to an agent. - * - * Args: - * agent_id: UUID of the agent - * db: Database session (injected) - * - * Returns: - * GetPolicyResponse with policy ID - * - * Raises: - * HTTPException 404: Agent not found or agent has no policy assigned - */ - get: operations['get_agent_policy_api_v1_agents__agent_id__policy_get']; - put?: never; - post?: never; - /** - * Remove agent's policy assignment - * @description Remove the policy assignment from an agent. - * - * The agent will no longer have any protection controls active. - * - * Args: - * agent_id: UUID of the agent - * db: Database session (injected) - * - * Returns: - * DeletePolicyResponse with success flag - * - * Raises: - * HTTPException 404: Agent not found or agent has no policy assigned - * HTTPException 500: Database error during removal - */ - delete: operations['delete_agent_policy_api_v1_agents__agent_id__policy_delete']; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/agents/{agent_id}/controls': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List agent's active controls - * @description List all protection controls active for an agent. - * - * Controls are inherited from the agent's assigned policy. - * Returns an empty list if the agent has no policy. - * - * Args: - * agent_id: UUID of the agent - * db: Database session (injected) - * - * Returns: - * AgentControlsResponse with list of controls (empty if no policy) - * - * Raises: - * HTTPException 404: Agent not found - */ - get: operations['list_agent_controls_api_v1_agents__agent_id__controls_get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/agents/{agent_id}/evaluators': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List agent's registered evaluator schemas - * @description List all evaluator schemas registered with an agent. - * - * Evaluator schemas are registered via initAgent and used for: - * - Config validation when creating Controls - * - UI to display available config options - * - * Args: - * agent_id: UUID of the agent - * cursor: Optional cursor for pagination (name of last evaluator from previous page) - * limit: Pagination limit (default 20, max 100) - * db: Database session (injected) - * - * Returns: - * ListEvaluatorsResponse with evaluator schemas and pagination - * - * Raises: - * HTTPException 404: Agent not found - */ - get: operations['list_agent_evaluators_api_v1_agents__agent_id__evaluators_get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/agents/{agent_id}/evaluators/{evaluator_name}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get specific evaluator schema - * @description Get a specific evaluator schema registered with an agent. - * - * Args: - * agent_id: UUID of the agent - * evaluator_name: Name of the evaluator - * db: Database session (injected) - * - * Returns: - * EvaluatorSchemaItem with schema details - * - * Raises: - * HTTPException 404: Agent or evaluator not found - */ - get: operations['get_agent_evaluator_api_v1_agents__agent_id__evaluators__evaluator_name__get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/policies': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - /** - * Create a new policy - * @description Create a new empty policy with a unique name. - * - * Policies contain controls and can be assigned to agents. - * A newly created policy has no controls until they are explicitly added. - * - * Args: - * request: Policy creation request with unique name - * db: Database session (injected) - * - * Returns: - * CreatePolicyResponse with the new policy's ID - * - * Raises: - * HTTPException 409: Policy with this name already exists - * HTTPException 500: Database error during creation - */ - put: operations['create_policy_api_v1_policies_put']; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/policies/{policy_id}/controls/{control_id}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Add control to policy - * @description Associate a control with a policy. - * - * This operation is idempotent - adding the same control multiple times has no effect. - * Agents with this policy will immediately see the added control. - * - * Args: - * policy_id: ID of the policy - * control_id: ID of the control to add - * db: Database session (injected) - * - * Returns: - * AssocResponse with success flag - * - * Raises: - * HTTPException 404: Policy or control not found - * HTTPException 500: Database error - */ - post: operations['add_control_to_policy_api_v1_policies__policy_id__controls__control_id__post']; - /** - * Remove control from policy - * @description Remove a control from a policy. - * - * This operation is idempotent - removing a non-associated control has no effect. - * Agents with this policy will immediately lose the removed control. - * - * Args: - * policy_id: ID of the policy - * control_id: ID of the control to remove - * db: Database session (injected) - * - * Returns: - * AssocResponse with success flag - * - * Raises: - * HTTPException 404: Policy or control not found - * HTTPException 500: Database error - */ - delete: operations['remove_control_from_policy_api_v1_policies__policy_id__controls__control_id__delete']; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/policies/{policy_id}/controls': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List policy's controls - * @description List all controls associated with a policy. - * - * Args: - * policy_id: ID of the policy - * db: Database session (injected) - * - * Returns: - * GetPolicyControlsResponse with list of control IDs - * - * Raises: - * HTTPException 404: Policy not found - */ - get: operations['list_policy_controls_api_v1_policies__policy_id__controls_get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/controls': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List all controls - * @description List all controls with optional filtering and cursor-based pagination. - * - * Controls are returned ordered by ID descending (newest first). - * - * Args: - * cursor: ID of the last control from the previous page (for pagination) - * limit: Maximum number of controls to return (default 20, max 100) - * name: Optional filter by name (partial, case-insensitive match) - * enabled: Optional filter by enabled status - * step_type: Optional filter by step type (built-ins: 'tool', 'llm') - * stage: Optional filter by stage ('pre' or 'post') - * execution: Optional filter by execution ('server' or 'sdk') - * tag: Optional filter by tag - * db: Database session (injected) - * - * Returns: - * ListControlsResponse with control summaries and pagination info - * - * Example: - * GET /controls?limit=10&enabled=true&step_type=tool - */ - get: operations['list_controls_api_v1_controls_get']; - /** - * Create a new control - * @description Create a new control with a unique name and empty data. - * - * Controls define protection logic and can be added to policies. - * Use the PUT /{control_id}/data endpoint to set control configuration. - * - * Args: - * request: Control creation request with unique name - * db: Database session (injected) - * - * Returns: - * CreateControlResponse with the new control's ID - * - * Raises: - * HTTPException 409: Control with this name already exists - * HTTPException 500: Database error during creation - */ - put: operations['create_control_api_v1_controls_put']; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/controls/{control_id}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get control details - * @description Retrieve a control by ID including its name and configuration data. - * - * Args: - * control_id: ID of the control - * db: Database session (injected) - * - * Returns: - * GetControlResponse with control id, name, and data - * - * Raises: - * HTTPException 404: Control not found - */ - get: operations['get_control_api_v1_controls__control_id__get']; - put?: never; - post?: never; - /** - * Delete a control - * @description Delete a control by ID. - * - * By default, deletion fails if the control is associated with any policy. - * Use force=true to automatically dissociate and delete. - * - * Args: - * control_id: ID of the control to delete - * force: If true, remove associations before deleting - * db: Database session (injected) - * - * Returns: - * DeleteControlResponse with success flag and list of dissociated policies - * - * Raises: - * HTTPException 404: Control not found - * HTTPException 409: Control is in use (and force=false) - * HTTPException 500: Database error during deletion - */ - delete: operations['delete_control_api_v1_controls__control_id__delete']; - options?: never; - head?: never; - /** - * Update control metadata - * @description Update control metadata (name and/or enabled status). - * - * This endpoint allows partial updates: - * - To rename: provide 'name' field - * - To enable/disable: provide 'enabled' field (updates the control's data) - * - * Args: - * control_id: ID of the control to update - * request: Fields to update (name, enabled) - * db: Database session (injected) - * - * Returns: - * PatchControlResponse with current control state - * - * Raises: - * HTTPException 404: Control not found - * HTTPException 409: New name conflicts with existing control - * HTTPException 422: Cannot update enabled status (control has no data configured) - * HTTPException 500: Database error during update - */ - patch: operations['patch_control_api_v1_controls__control_id__patch']; - trace?: never; - }; - '/api/v1/controls/{control_id}/data': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get control configuration data - * @description Retrieve the configuration data for a control. - * - * Control data is a JSONB field that must follow the ControlDefinition schema. - * - * Args: - * control_id: ID of the control - * db: Database session (injected) - * - * Returns: - * GetControlDataResponse with validated ControlDefinition - * - * Raises: - * HTTPException 404: Control not found - * HTTPException 422: Control data is corrupted - */ - get: operations['get_control_data_api_v1_controls__control_id__data_get']; - /** - * Update control configuration data - * @description Update the configuration data for a control. - * - * This replaces the entire data payload. The data is validated against - * the ControlDefinition schema. - * - * Args: - * control_id: ID of the control - * request: New control data (replaces existing) - * db: Database session (injected) - * - * Returns: - * SetControlDataResponse with success flag - * - * Raises: - * HTTPException 404: Control not found - * HTTPException 500: Database error during update - */ - put: operations['set_control_data_api_v1_controls__control_id__data_put']; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/evaluator-configs': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** List evaluator configs */ - get: operations['list_evaluator_configs_api_v1_evaluator_configs_get']; - put?: never; - /** Create evaluator config */ - post: operations['create_evaluator_config_api_v1_evaluator_configs_post']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/evaluator-configs/{config_id}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get evaluator config */ - get: operations['get_evaluator_config_api_v1_evaluator_configs__config_id__get']; - /** Update evaluator config */ - put: operations['update_evaluator_config_api_v1_evaluator_configs__config_id__put']; - post?: never; - /** Delete evaluator config */ - delete: operations['delete_evaluator_config_api_v1_evaluator_configs__config_id__delete']; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/evaluation': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Analyze content safety - * @description Analyze content for safety and control violations. - * - * Runs all controls assigned to the agent via policy through the - * evaluation engine. Controls are evaluated in parallel with - * cancel-on-deny for efficiency. - * - * Custom evaluators must be deployed as Evaluator classes - * with the engine. Their schemas are registered via initAgent. - * - * Optionally accepts X-Trace-Id and X-Span-Id headers for - * OpenTelemetry-compatible distributed tracing. - */ - post: operations['evaluate_api_v1_evaluation_post']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/evaluators': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List available evaluators - * @description List all available evaluators. - * - * Returns metadata and JSON Schema for each built-in evaluator. - * - * Built-in evaluators: - * - **regex**: Regular expression pattern matching - * - **list**: List-based value matching with flexible logic - * - **json**: JSON validation with schema, types, constraints - * - **sql**: SQL query validation - * - * Custom evaluators are registered per-agent via initAgent. - * Use GET /agents/{agent_id}/evaluators to list agent-specific schemas. - */ - get: operations['get_evaluators_api_v1_evaluators_get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/observability/events': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Ingest Events - * @description Ingest batched control execution events. - * - * Events are stored directly to the database with ~5-20ms latency. - * - * Args: - * request: Batch of events to ingest - * ingestor: Event ingestor (injected) - * - * Returns: - * BatchEventsResponse with counts of received/processed/dropped - */ - post: operations['ingest_events_api_v1_observability_events_post']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/observability/events/query': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Query Events - * @description Query raw control execution events. - * - * Supports filtering by: - * - trace_id: Get all events for a request - * - span_id: Get all events for a function call - * - control_execution_id: Get a specific event - * - agent_uuid: Filter by agent - * - control_ids: Filter by controls - * - actions: Filter by actions (allow, deny, warn, log) - * - matched: Filter by matched status - * - check_stages: Filter by check stage (pre, post) - * - applies_to: Filter by call type (llm_call, tool_call) - * - start_time/end_time: Filter by time range - * - * Results are paginated with limit/offset. - * - * Args: - * request: Query parameters - * store: Event store (injected) - * - * Returns: - * EventQueryResponse with matching events and pagination info - */ - post: operations['query_events_api_v1_observability_events_query_post']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/observability/stats': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Stats - * @description Get agent-level aggregated statistics. - * - * Returns totals across all controls plus per-control breakdown. - * Use /stats/controls/{control_id} for single control stats. - * - * Args: - * agent_uuid: Agent to get stats for - * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) - * include_timeseries: Include time-series data points for trend visualization - * store: Event store (injected) - * - * Returns: - * StatsResponse with agent-level totals and per-control breakdown - */ - get: operations['get_stats_api_v1_observability_stats_get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/observability/stats/controls/{control_id}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Control Stats - * @description Get statistics for a single control. - * - * Returns stats for the specified control with optional time-series. - * - * Args: - * control_id: Control ID to get stats for - * agent_uuid: Agent to get stats for - * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) - * include_timeseries: Include time-series data points for trend visualization - * store: Event store (injected) - * - * Returns: - * ControlStatsResponse with control stats and optional timeseries - */ - get: operations['get_control_stats_api_v1_observability_stats_controls__control_id__get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/v1/observability/status': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Status - * @description Get observability system status. - * - * Returns basic health information. - */ - get: operations['get_status_api_v1_observability_status_get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/health': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Health check - * @description Check if the server is running and responsive. - * - * This endpoint does not check database connectivity. - * - * Returns: - * HealthResponse with status and version - */ - get: operations['health_check_health_get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; } export type webhooks = Record; export interface components { - schemas: { - /** - * Agent - * @description Agent metadata for registration and tracking. - * - * An agent represents an AI system that can be protected and monitored. - * Each agent has a unique ID and can have multiple steps registered with it. - * @example { - * "agent_description": "Handles customer inquiries and support tickets", - * "agent_id": "550e8400-e29b-41d4-a716-446655440000", - * "agent_metadata": { - * "environment": "production", - * "team": "support" - * }, - * "agent_name": "customer-service-bot", - * "agent_version": "1.0.0" - * } - */ - Agent: { - /** - * Agent Id - * Format: uuid - * @description Unique identifier for the agent (UUID format) - */ - agent_id: string; - /** - * Agent Name - * @description Human-readable name for the agent - */ - agent_name: string; - /** - * Agent Description - * @description Optional description of the agent's purpose - */ - agent_description?: string | null; - /** - * Agent Created At - * @description ISO 8601 timestamp when agent was created - */ - agent_created_at?: string | null; - /** - * Agent Updated At - * @description ISO 8601 timestamp when agent was last updated - */ - agent_updated_at?: string | null; - /** - * Agent Version - * @description Semantic version string (e.g. '1.0.0') - */ - agent_version?: string | null; - /** - * Agent Metadata - * @description Free-form metadata dictionary for custom properties - */ - agent_metadata?: { - [key: string]: unknown; - } | null; - }; - /** AgentControlsResponse */ - AgentControlsResponse: { - /** - * Controls - * @description List of controls associated with the agent via its policy - */ - controls: components['schemas']['Control'][]; - }; - /** - * AgentRef - * @description Reference to an agent (for listing which agents use a control). - */ - AgentRef: { - /** - * Agent Id - * @description Agent UUID - */ - agent_id: string; - /** - * Agent Name - * @description Agent name - */ - agent_name: string; - }; - /** - * AgentSummary - * @description Summary of an agent for list responses. - */ - AgentSummary: { - /** - * Agent Id - * @description UUID of the agent - */ - agent_id: string; - /** - * Agent Name - * @description Human-readable name of the agent - */ - agent_name: string; - /** - * Policy Id - * @description ID of assigned policy, if any - */ - policy_id?: number | null; - /** - * Created At - * @description ISO 8601 timestamp when agent was created - */ - created_at?: string | null; - /** - * Step Count - * @description Number of steps registered with the agent - * @default 0 - */ - step_count: number; - /** - * Evaluator Count - * @description Number of evaluators registered with the agent - * @default 0 - */ - evaluator_count: number; - /** - * Active Controls Count - * @description Number of active controls from agent's policy - * @default 0 - */ - active_controls_count: number; - }; - /** AssocResponse */ - AssocResponse: { - /** - * Success - * @description Whether the association change succeeded - */ - success: boolean; - }; - /** - * BatchEventsRequest - * @description Request model for batch event ingestion. - * - * SDKs batch events and send them to the server periodically. - * This reduces HTTP overhead significantly (100x reduction). - * - * Attributes: - * events: List of control execution events to ingest - * @example { - * "events": [ - * { - * "action": "deny", - * "agent_name": "my-agent", - * "agent_uuid": "550e8400-e29b-41d4-a716-446655440001", - * "applies_to": "llm_call", - * "check_stage": "pre", - * "confidence": 0.95, - * "control_id": 123, - * "control_name": "sql-injection-check", - * "matched": true, - * "span_id": "00f067aa0ba902b7", - * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" - * } - * ] - * } - */ - BatchEventsRequest: { - /** - * Events - * @description List of events to ingest - */ - events: components['schemas']['ControlExecutionEvent'][]; - }; - /** - * BatchEventsResponse - * @description Response model for batch event ingestion. - * - * Attributes: - * received: Number of events received - * enqueued: Number of events successfully enqueued - * dropped: Number of events dropped (queue full) - * status: Overall status ('queued', 'partial', 'failed') - */ - BatchEventsResponse: { - /** - * Received - * @description Number of events received - */ - received: number; - /** - * Enqueued - * @description Number of events enqueued - */ - enqueued: number; - /** - * Dropped - * @description Number of events dropped - */ - dropped: number; - /** - * Status - * @description Overall ingestion status - * @enum {string} - */ - status: 'queued' | 'partial' | 'failed'; - }; - /** - * Control - * @description A control with identity and configuration. - * - * Note: Only fully-configured controls (with valid ControlDefinition) - * are returned from API endpoints. Unconfigured controls are filtered out. - */ - Control: { - /** Id */ - id: number; - /** Name */ - name: string; - control: components['schemas']['ControlDefinition']; - }; - /** - * ControlAction - * @description What to do when control matches. - */ - ControlAction: { - /** - * Decision - * @description Action to take when control is triggered - * @enum {string} - */ - decision: 'allow' | 'deny' | 'warn' | 'log'; - }; - /** - * ControlDefinition - * @description A control definition to evaluate agent interactions. - * - * This model contains only the logic and configuration. - * Identity fields (id, name) are managed by the database. - * @example { - * "action": { - * "decision": "deny" - * }, - * "description": "Block outputs containing US Social Security Numbers", - * "enabled": true, - * "evaluator": { - * "config": { - * "pattern": "\\b\\d{3}-\\d{2}-\\d{4}\\b" - * }, - * "name": "regex" - * }, - * "execution": "server", - * "scope": { - * "stages": [ - * "post" - * ], - * "step_types": [ - * "llm" - * ] - * }, - * "selector": { - * "path": "output" - * }, - * "tags": [ - * "pii", - * "compliance" - * ] - * } - */ - ControlDefinition: { - /** - * Description - * @description Detailed description of the control - */ - description?: string | null; - /** - * Enabled - * @description Whether this control is active - * @default true - */ - enabled: boolean; - /** - * Execution - * @description Where this control executes - * @enum {string} - */ - execution: 'server' | 'sdk'; - /** @description Which steps and stages this control applies to */ - scope?: components['schemas']['ControlScope']; - /** @description What data to select from the payload */ - selector: components['schemas']['ControlSelector']; - /** @description How to evaluate the selected data */ - evaluator: components['schemas']['EvaluatorConfig']; - /** @description What action to take when control matches */ - action: components['schemas']['ControlAction']; - /** - * Tags - * @description Tags for categorization - */ - tags?: string[]; - }; - /** - * ControlExecutionEvent - * @description Represents a single control execution event. - * - * This is the core observability data model, capturing: - * - Identity: control_execution_id, trace_id, span_id (OpenTelemetry-compatible) - * - Context: agent, control, check stage, applies to - * - Result: action taken, whether matched, confidence score - * - Timing: when it happened, how long it took - * - Optional details: evaluator name, selector path, errors, metadata - * - * Attributes: - * control_execution_id: Unique ID for this specific control execution - * trace_id: OpenTelemetry-compatible trace ID (128-bit hex, 32 chars) - * span_id: OpenTelemetry-compatible span ID (64-bit hex, 16 chars) - * agent_uuid: UUID of the agent that executed the control - * agent_name: Name of the agent (denormalized for queries) - * control_id: Database ID of the control - * control_name: Name of the control (denormalized for queries) - * control_set_id: Optional ID of the control set - * control_set_name: Optional name of the control set - * check_stage: "pre" (before execution) or "post" (after execution) - * applies_to: "llm_call" or "tool_call" - * action: The action taken (allow, deny, warn, log) - * matched: Whether the control evaluator matched - * confidence: Confidence score from the evaluator (0.0-1.0) - * timestamp: When the control was executed (UTC) - * execution_duration_ms: How long the control evaluation took - * evaluator_name: Name of the evaluator used - * selector_path: The selector path used to extract data - * error_message: Error message if evaluation failed - * metadata: Additional metadata for extensibility - * @example { - * "action": "deny", - * "agent_name": "my-agent", - * "agent_uuid": "550e8400-e29b-41d4-a716-446655440001", - * "applies_to": "llm_call", - * "check_stage": "pre", - * "confidence": 0.95, - * "control_execution_id": "550e8400-e29b-41d4-a716-446655440000", - * "control_id": 123, - * "control_name": "sql-injection-check", - * "evaluator_name": "regex", - * "execution_duration_ms": 15.3, - * "matched": true, - * "selector_path": "input", - * "span_id": "00f067aa0ba902b7", - * "timestamp": "2025-01-09T10:30:00Z", - * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" - * } - */ - ControlExecutionEvent: { - /** - * Control Execution Id - * @description Unique ID for this control execution - */ - control_execution_id?: string; - /** - * Trace Id - * @description Trace ID for distributed tracing (SDK generates OTEL-compatible 32-char hex) - */ - trace_id: string; - /** - * Span Id - * @description Span ID for distributed tracing (SDK generates OTEL-compatible 16-char hex) - */ - span_id: string; - /** - * Agent Uuid - * Format: uuid - * @description UUID of the agent - */ - agent_uuid: string; - /** - * Agent Name - * @description Name of the agent (denormalized) - */ - agent_name: string; - /** - * Control Id - * @description Database ID of the control - */ - control_id: number; - /** - * Control Name - * @description Name of the control (denormalized) - */ - control_name: string; - /** - * Check Stage - * @description Check stage: 'pre' or 'post' - * @enum {string} - */ - check_stage: 'pre' | 'post'; - /** - * Applies To - * @description Type of call: 'llm_call' or 'tool_call' - * @enum {string} - */ - applies_to: 'llm_call' | 'tool_call'; - /** - * Action - * @description Action taken by the control - * @enum {string} - */ - action: 'allow' | 'deny' | 'warn' | 'log'; - /** - * Matched - * @description Whether the evaluator matched (True) or not (False) - */ - matched: boolean; - /** - * Confidence - * @description Confidence score (0.0 to 1.0) - */ - confidence: number; - /** - * Timestamp - * Format: date-time - * @description When the control was executed (UTC) - */ - timestamp?: string; - /** - * Execution Duration Ms - * @description Execution duration in milliseconds - */ - execution_duration_ms?: number | null; - /** - * Evaluator Name - * @description Name of the evaluator used - */ - evaluator_name?: string | null; - /** - * Selector Path - * @description Selector path used to extract data - */ - selector_path?: string | null; - /** - * Error Message - * @description Error message if evaluation failed - */ - error_message?: string | null; - /** - * Metadata - * @description Additional metadata - */ - metadata?: { - [key: string]: unknown; - }; - }; - /** - * ControlMatch - * @description Represents a control evaluation result (match, non-match, or error). - */ - ControlMatch: { - /** - * Control Execution Id - * @description Unique ID for this control execution (generated by engine) - */ - control_execution_id?: string; - /** - * Control Id - * @description Database ID of the control - */ - control_id: number; - /** - * Control Name - * @description Name of the control - */ - control_name: string; - /** - * Action - * @description Action configured for this control - * @enum {string} - */ - action: 'allow' | 'deny' | 'warn' | 'log'; - /** @description Evaluator result (confidence, message, metadata) */ - result: components['schemas']['EvaluatorResult']; - }; - /** - * ControlScope - * @description Defines when a control applies to a Step. - * @example { - * "stages": [ - * "pre" - * ], - * "step_types": [ - * "tool" - * ] - * } - * @example { - * "step_names": [ - * "search_db", - * "fetch_user" - * ] - * } - * @example { - * "step_name_regex": "^db_.*" - * } - * @example { - * "stages": [ - * "post" - * ], - * "step_types": [ - * "llm" - * ] - * } - */ - ControlScope: { - /** - * Step Types - * @description Step types this control applies to (omit to apply to all types). Built-in types are 'tool' and 'llm'. - */ - step_types?: string[] | null; - /** - * Step Names - * @description Exact step names this control applies to - */ - step_names?: string[] | null; - /** - * Step Name Regex - * @description RE2 pattern matched with search() against step name - */ - step_name_regex?: string | null; - /** - * Stages - * @description Evaluation stages this control applies to - */ - stages?: ('pre' | 'post')[] | null; - }; - /** - * ControlSelector - * @description Selects data from a Step payload. - * - * - path: which slice of the Step to feed into the evaluator. Optional, defaults to "*" - * meaning the entire Step object. - * @example { - * "path": "output" - * } - * @example { - * "path": "context.user_id" - * } - * @example { - * "path": "input" - * } - * @example { - * "path": "*" - * } - * @example { - * "path": "name" - * } - * @example { - * "path": "output" - * } - */ - ControlSelector: { - /** - * Path - * @description Path to data using dot notation. Examples: 'input', 'output', 'context.user_id', 'name', 'type', '*' - * @default * - */ - path: string | null; - }; - /** - * ControlStats - * @description Aggregated statistics for a single control. - * - * Attributes: - * control_id: Database ID of the control - * control_name: Name of the control - * execution_count: Total number of executions - * match_count: Number of times the control matched - * non_match_count: Number of times the control did not match - * allow_count: Number of allow actions - * deny_count: Number of deny actions - * warn_count: Number of warn actions - * log_count: Number of log actions - * error_count: Number of errors during evaluation - * avg_confidence: Average confidence score - * avg_duration_ms: Average execution duration in milliseconds - */ - ControlStats: { - /** - * Control Id - * @description Control ID - */ - control_id: number; - /** - * Control Name - * @description Control name - */ - control_name: string; - /** - * Execution Count - * @description Total executions - */ - execution_count: number; - /** - * Match Count - * @description Total matches - */ - match_count: number; - /** - * Non Match Count - * @description Total non-matches - */ - non_match_count: number; - /** - * Allow Count - * @description Allow actions - */ - allow_count: number; - /** - * Deny Count - * @description Deny actions - */ - deny_count: number; - /** - * Warn Count - * @description Warn actions - */ - warn_count: number; - /** - * Log Count - * @description Log actions - */ - log_count: number; - /** - * Error Count - * @description Evaluation errors - */ - error_count: number; - /** - * Avg Confidence - * @description Average confidence - */ - avg_confidence: number; - /** - * Avg Duration Ms - * @description Average duration (ms) - */ - avg_duration_ms?: number | null; - }; - /** - * ControlStatsResponse - * @description Response model for control-level statistics. - * - * Contains stats for a single control (with optional timeseries). - * - * Attributes: - * agent_uuid: Agent UUID - * time_range: Time range used - * control_id: Control ID - * control_name: Control name - * stats: Control statistics (includes timeseries when requested) - */ - ControlStatsResponse: { - /** - * Agent Uuid - * Format: uuid - * @description Agent UUID - */ - agent_uuid: string; - /** - * Time Range - * @description Time range used - */ - time_range: string; - /** - * Control Id - * @description Control ID - */ - control_id: number; - /** - * Control Name - * @description Control name - */ - control_name: string; - /** @description Control statistics */ - stats: components['schemas']['StatsTotals']; - }; - /** - * ControlSummary - * @description Summary of a control for list responses. - */ - ControlSummary: { - /** - * Id - * @description Control ID - */ - id: number; - /** - * Name - * @description Control name - */ - name: string; - /** - * Description - * @description Control description - */ - description?: string | null; - /** - * Enabled - * @description Whether control is enabled - * @default true - */ - enabled: boolean; - /** - * Execution - * @description 'server' or 'sdk' - */ - execution?: string | null; - /** - * Step Types - * @description Step types in scope - */ - step_types?: string[] | null; - /** - * Stages - * @description Evaluation stages in scope - */ - stages?: string[] | null; - /** - * Tags - * @description Control tags - */ - tags?: string[]; - /** @description Agent using this control */ - used_by_agent?: components['schemas']['AgentRef'] | null; - }; - /** CreateControlRequest */ - CreateControlRequest: { - /** - * Name - * @description Unique control name (letters, numbers, hyphens, underscores) - */ - name: string; - }; - /** CreateControlResponse */ - CreateControlResponse: { - /** - * Control Id - * @description Identifier of the created control - */ - control_id: number; - }; - /** - * CreateEvaluatorConfigRequest - * @description Request to create an evaluator config template. - */ - CreateEvaluatorConfigRequest: { - /** - * Name - * @description Unique evaluator config name (letters, numbers, hyphens, underscores) - */ - name: string; - /** - * Description - * @description Optional description - */ - description?: string | null; - /** - * Evaluator - * @description Evaluator name (built-in or custom) - */ - evaluator: string; - /** - * Config - * @description Evaluator-specific configuration - */ - config: { - [key: string]: unknown; - }; - }; - /** CreatePolicyRequest */ - CreatePolicyRequest: { - /** - * Name - * @description Unique policy name (letters, numbers, hyphens, underscores) - */ - name: string; - }; - /** CreatePolicyResponse */ - CreatePolicyResponse: { - /** - * Policy Id - * @description Identifier of the created policy - */ - policy_id: number; - }; - /** - * DeleteControlResponse - * @description Response for deleting a control. - */ - DeleteControlResponse: { - /** - * Success - * @description Whether the control was deleted - */ - success: boolean; - /** - * Dissociated From - * @description Policy IDs the control was removed from before deletion - */ - dissociated_from?: number[]; - }; - /** - * DeleteEvaluatorConfigResponse - * @description Response for deleting an evaluator config. - */ - DeleteEvaluatorConfigResponse: { - /** - * Success - * @description Whether the evaluator config was deleted - */ - success: boolean; - }; - /** DeletePolicyResponse */ - DeletePolicyResponse: { - /** - * Success - * @description Whether the policy was successfully removed - */ - success: boolean; - }; - /** - * EvaluationRequest - * @description Request model for evaluation analysis. - * - * Used to analyze agent interactions for safety violations, - * policy compliance, and control rules. - * - * Attributes: - * agent_uuid: UUID of the agent making the request - * step: Step payload for evaluation - * stage: 'pre' (before execution) or 'post' (after execution) - * @example { - * "agent_uuid": "550e8400-e29b-41d4-a716-446655440000", - * "stage": "pre", - * "step": { - * "context": { - * "session_id": "abc123", - * "user_id": "user123" - * }, - * "input": "What is the customer's credit card number?", - * "name": "support-answer", - * "type": "llm" - * } - * } - * @example { - * "agent_uuid": "550e8400-e29b-41d4-a716-446655440000", - * "stage": "post", - * "step": { - * "context": { - * "session_id": "abc123", - * "user_id": "user123" - * }, - * "input": "What is the customer's credit card number?", - * "name": "support-answer", - * "output": "I cannot share sensitive payment information.", - * "type": "llm" - * } - * } - * @example { - * "agent_uuid": "550e8400-e29b-41d4-a716-446655440000", - * "stage": "pre", - * "step": { - * "context": { - * "user_id": "user123" - * }, - * "input": { - * "query": "SELECT * FROM users" - * }, - * "name": "search_database", - * "type": "tool" - * } - * } - * @example { - * "agent_uuid": "550e8400-e29b-41d4-a716-446655440000", - * "stage": "post", - * "step": { - * "context": { - * "user_id": "user123" - * }, - * "input": { - * "query": "SELECT * FROM users" - * }, - * "name": "search_database", - * "output": { - * "results": [] - * }, - * "type": "tool" - * } - * } - */ - EvaluationRequest: { - /** - * Agent Uuid - * Format: uuid - * @description UUID of the agent making the evaluation request - */ - agent_uuid: string; - /** @description Agent step payload to evaluate */ - step: components['schemas']['Step']; - /** - * Stage - * @description Evaluation stage: 'pre' or 'post' - * @enum {string} - */ - stage: 'pre' | 'post'; - }; - /** - * EvaluationResponse - * @description Response model from evaluation analysis (server-side). - * - * This is what the server returns. The SDK may transform this - * into an EvaluationResult for client convenience. - * - * Attributes: - * is_safe: Whether the content is considered safe - * confidence: Confidence score between 0.0 and 1.0 - * reason: Optional explanation for the decision - * matches: List of controls that matched/triggered (if any) - * errors: List of controls that failed during evaluation (if any) - * non_matches: List of controls that were evaluated but did not match (if any) - */ - EvaluationResponse: { - /** - * Is Safe - * @description Whether content is safe - */ - is_safe: boolean; - /** - * Confidence - * @description Confidence score (0.0 to 1.0) - */ - confidence: number; - /** - * Reason - * @description Explanation for the decision - */ - reason?: string | null; - /** - * Matches - * @description List of controls that matched/triggered (if any) - */ - matches?: components['schemas']['ControlMatch'][] | null; - /** - * Errors - * @description List of controls that failed during evaluation (if any) - */ - errors?: components['schemas']['ControlMatch'][] | null; - /** - * Non Matches - * @description List of controls that were evaluated but did not match (if any) - */ - non_matches?: components['schemas']['ControlMatch'][] | null; - }; - /** - * EvaluatorConfig - * @description Evaluator configuration. See GET /evaluators for available evaluators and schemas. - * - * Evaluator reference formats: - * - Built-in: "regex", "list" - * - Agent-scoped: "my-agent:my-evaluator" (validated in endpoint, not here) - */ - EvaluatorConfig: { - /** - * Name - * @description Evaluator name or agent-scoped reference (agent:evaluator) - * @example regex - * @example list - * @example my-agent:pii-detector - */ - name: string; - /** - * Config - * @description Evaluator-specific configuration - * @example { - * "pattern": "\\d{3}-\\d{2}-\\d{4}" - * } - * @example { - * "logic": "any", - * "values": [ - * "admin" - * ] - * } - */ - config: { - [key: string]: unknown; - }; - }; - /** - * EvaluatorConfigItem - * @description Evaluator config template stored in the server. - */ - EvaluatorConfigItem: { - /** - * Id - * @description Evaluator config ID - */ - id: number; - /** - * Name - * @description Unique evaluator config name (letters, numbers, hyphens, underscores) - */ - name: string; - /** - * Description - * @description Optional description - */ - description?: string | null; - /** - * Evaluator - * @description Evaluator name (built-in or custom) - */ - evaluator: string; - /** - * Config - * @description Evaluator-specific configuration - */ - config: { - [key: string]: unknown; - }; - /** - * Created At - * @description ISO 8601 created timestamp - */ - created_at?: string | null; - /** - * Updated At - * @description ISO 8601 updated timestamp - */ - updated_at?: string | null; - }; - /** - * EvaluatorInfo - * @description Information about a registered evaluator. - */ - EvaluatorInfo: { - /** - * Name - * @description Evaluator name - */ - name: string; - /** - * Version - * @description Evaluator version - */ - version: string; - /** - * Description - * @description Evaluator description - */ - description: string; - /** - * Requires Api Key - * @description Whether evaluator requires API key - */ - requires_api_key: boolean; - /** - * Timeout Ms - * @description Default timeout in milliseconds - */ - timeout_ms: number; - /** - * Config Schema - * @description JSON Schema for config - */ - config_schema: { - [key: string]: unknown; - }; - }; - /** - * EvaluatorResult - * @description Result from a control evaluator. - * - * The `error` field indicates evaluator failures, NOT validation failures: - * - Set `error` for: evaluator crashes, timeouts, missing dependencies, external service errors - * - Do NOT set `error` for: invalid input, syntax errors, schema violations, constraint failures - * - * When `error` is set, `matched` must be False (fail-open on evaluator errors). - * When `error` is None, `matched` reflects the actual validation result. - * - * This distinction allows: - * - Clients to distinguish "data violated rules" from "evaluator is broken" - * - Observability systems to monitor evaluator health separately from validation outcomes - */ - EvaluatorResult: { - /** - * Matched - * @description Whether the pattern matched - */ - matched: boolean; - /** - * Confidence - * @description Confidence in the evaluation - */ - confidence: number; - /** - * Message - * @description Explanation of the result - */ - message?: string | null; - /** - * Metadata - * @description Additional result metadata - */ - metadata?: { - [key: string]: unknown; - } | null; - /** - * Error - * @description Error message if evaluation failed internally. When set, matched=False is due to error, not actual evaluation. - */ - error?: string | null; - }; - /** - * EvaluatorSchema - * @description Schema for a custom evaluator registered with an agent. - * - * Custom evaluators are Evaluator classes deployed with the engine. - * This schema is registered via initAgent for validation and UI purposes. - */ - EvaluatorSchema: { - /** - * Name - * @description Unique evaluator name - */ - name: string; - /** - * Config Schema - * @description JSON Schema for evaluator config validation - */ - config_schema?: { - [key: string]: unknown; - }; - /** - * Description - * @description Optional description - */ - description?: string | null; - }; - /** - * EvaluatorSchemaItem - * @description Evaluator schema summary for list response. - */ - EvaluatorSchemaItem: { - /** Name */ - name: string; - /** Description */ - description: string | null; - /** Config Schema */ - config_schema: { - [key: string]: unknown; - }; - }; - /** - * EventQueryRequest - * @description Request model for querying raw events. - * - * Supports filtering by various criteria and pagination. - * - * Attributes: - * trace_id: Filter by trace ID (get all events for a request) - * span_id: Filter by span ID (get all events for a function call) - * control_execution_id: Filter by specific event ID - * agent_uuid: Filter by agent UUID - * control_ids: Filter by control IDs - * actions: Filter by actions (allow, deny, warn, log) - * matched: Filter by matched status - * check_stages: Filter by check stages (pre, post) - * applies_to: Filter by call type (llm_call, tool_call) - * start_time: Filter events after this time - * end_time: Filter events before this time - * limit: Maximum number of events to return - * offset: Offset for pagination - * @example { - * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" - * } - * @example { - * "actions": [ - * "deny", - * "warn" - * ], - * "agent_uuid": "550e8400-e29b-41d4-a716-446655440001", - * "limit": 50, - * "start_time": "2025-01-09T00:00:00Z" - * } - */ - EventQueryRequest: { - /** - * Trace Id - * @description Filter by trace ID (all events for a request) - */ - trace_id?: string | null; - /** - * Span Id - * @description Filter by span ID (all events for a function) - */ - span_id?: string | null; - /** - * Control Execution Id - * @description Filter by specific event ID - */ - control_execution_id?: string | null; - /** - * Agent Uuid - * @description Filter by agent UUID - */ - agent_uuid?: string | null; - /** - * Control Ids - * @description Filter by control IDs - */ - control_ids?: number[] | null; - /** - * Actions - * @description Filter by actions - */ - actions?: ('allow' | 'deny' | 'warn' | 'log')[] | null; - /** - * Matched - * @description Filter by matched status - */ - matched?: boolean | null; - /** - * Check Stages - * @description Filter by check stages - */ - check_stages?: ('pre' | 'post')[] | null; - /** - * Applies To - * @description Filter by call types - */ - applies_to?: ('llm_call' | 'tool_call')[] | null; - /** - * Start Time - * @description Filter events after this time - */ - start_time?: string | null; - /** - * End Time - * @description Filter events before this time - */ - end_time?: string | null; - /** - * Limit - * @description Maximum events - * @default 100 - */ - limit: number; - /** - * Offset - * @description Pagination offset - * @default 0 - */ - offset: number; - }; - /** - * EventQueryResponse - * @description Response model for event queries. - * - * Attributes: - * events: List of matching events - * total: Total number of matching events (for pagination) - * limit: Limit used in query - * offset: Offset used in query - */ - EventQueryResponse: { - /** - * Events - * @description Matching events - */ - events: components['schemas']['ControlExecutionEvent'][]; - /** - * Total - * @description Total matching events - */ - total: number; - /** - * Limit - * @description Limit used in query - */ - limit: number; - /** - * Offset - * @description Offset used in query - */ - offset: number; - }; - /** - * GetAgentResponse - * @description Response containing agent details and registered steps. - */ - GetAgentResponse: { - /** @description Agent metadata */ - agent: components['schemas']['Agent']; - /** - * Steps - * @description Steps registered with this agent - */ - steps: components['schemas']['StepSchema'][]; - /** - * Evaluators - * @description Custom evaluators registered with this agent - */ - evaluators?: components['schemas']['EvaluatorSchema'][]; - }; - /** GetControlDataResponse */ - GetControlDataResponse: { - /** @description Control data payload */ - data: components['schemas']['ControlDefinition']; - }; - /** - * GetControlResponse - * @description Response containing control details. - */ - GetControlResponse: { - /** - * Id - * @description Control ID - */ - id: number; - /** - * Name - * @description Control name - */ - name: string; - /** @description Control configuration data (None if not yet configured) */ - data?: components['schemas']['ControlDefinition'] | null; - }; - /** - * GetPolicyControlsResponse - * @description Response containing control IDs associated with a policy. - */ - GetPolicyControlsResponse: { - /** - * Control Ids - * @description List of control IDs associated with the policy - */ - control_ids: number[]; - }; - /** GetPolicyResponse */ - GetPolicyResponse: { - /** - * Policy Id - * @description Identifier of the policy assigned to the agent - */ - policy_id: number; - }; - /** HTTPValidationError */ - HTTPValidationError: { - /** Detail */ - detail?: components['schemas']['ValidationError'][]; - }; - /** - * HealthResponse - * @description Health check response model. - * - * Attributes: - * status: Current health status (e.g., "healthy", "degraded", "unhealthy") - * version: Application version - */ - HealthResponse: { - /** Status */ - status: string; - /** Version */ - version: string; - }; - /** - * InitAgentRequest - * @description Request to initialize or update an agent registration. - * @example { - * "agent": { - * "agent_description": "Handles customer inquiries", - * "agent_id": "550e8400-e29b-41d4-a716-446655440000", - * "agent_name": "customer-service-bot", - * "agent_version": "1.0.0" - * }, - * "evaluators": [ - * { - * "config_schema": { - * "properties": { - * "sensitivity": { - * "type": "string" - * } - * }, - * "type": "object" - * }, - * "description": "Detects PII in text", - * "name": "pii-detector" - * } - * ], - * "steps": [ - * { - * "input_schema": { - * "query": { - * "type": "string" - * } - * }, - * "name": "search_kb", - * "output_schema": { - * "results": { - * "type": "array" - * } - * }, - * "type": "tool" - * } - * ] - * } - */ - InitAgentRequest: { - /** @description Agent metadata including ID, name, and version */ - agent: components['schemas']['Agent']; - /** - * Steps - * @description List of steps available to the agent - */ - steps?: components['schemas']['StepSchema'][]; - /** - * Evaluators - * @description Custom evaluator schemas for config validation - */ - evaluators?: components['schemas']['EvaluatorSchema'][]; - /** - * Force Replace - * @description If true, replace corrupted agent data instead of failing. Use only when agent data is corrupted and cannot be parsed. - * @default false - */ - force_replace: boolean; - }; - /** - * InitAgentResponse - * @description Response from agent initialization. - */ - InitAgentResponse: { - /** - * Created - * @description True if agent was newly created, False if updated - */ - created: boolean; - /** - * Controls - * @description Active protection controls for the agent (if policy assigned) - */ - controls?: components['schemas']['Control'][]; - }; - JSONObject: { - [key: string]: components['schemas']['JSONValue']; - }; - /** @description Any JSON value */ - JSONValue: unknown; - /** - * ListAgentsResponse - * @description Response for listing agents. - */ - ListAgentsResponse: { - /** - * Agents - * @description List of agent summaries - */ - agents: components['schemas']['AgentSummary'][]; - /** @description Pagination metadata */ - pagination: components['schemas']['PaginationInfo']; - }; - /** - * ListControlsResponse - * @description Response for listing controls. - */ - ListControlsResponse: { - /** - * Controls - * @description List of control summaries - */ - controls: components['schemas']['ControlSummary'][]; - /** @description Pagination metadata */ - pagination: components['schemas']['PaginationInfo']; - }; - /** - * ListEvaluatorConfigsResponse - * @description Response for listing evaluator configs. - */ - ListEvaluatorConfigsResponse: { - /** - * Evaluator Configs - * @description List of evaluator configs - */ - evaluator_configs: components['schemas']['EvaluatorConfigItem'][]; - /** @description Pagination metadata */ - pagination: components['schemas']['PaginationInfo']; - }; - /** - * ListEvaluatorsResponse - * @description Response for listing agent's evaluator schemas. - */ - ListEvaluatorsResponse: { - /** Evaluators */ - evaluators: components['schemas']['EvaluatorSchemaItem'][]; - pagination: components['schemas']['PaginationInfo']; - }; - /** - * PaginationInfo - * @description Pagination metadata for cursor-based pagination. - */ - PaginationInfo: { - /** - * Limit - * @description Number of items per page - */ - limit: number; - /** - * Total - * @description Total number of items - */ - total: number; - /** - * Next Cursor - * @description Cursor for fetching the next page (null if no more pages) - */ - next_cursor?: string | null; - /** - * Has More - * @description Whether there are more pages available - */ - has_more: boolean; - }; - /** - * PatchAgentRequest - * @description Request to modify an agent (remove steps/evaluators). - */ - PatchAgentRequest: { - /** - * Remove Steps - * @description Step identifiers to remove from the agent - */ - remove_steps?: components['schemas']['StepKey'][]; - /** - * Remove Evaluators - * @description Evaluator names to remove from the agent - */ - remove_evaluators?: string[]; - }; - /** - * PatchAgentResponse - * @description Response from agent modification. - */ - PatchAgentResponse: { - /** - * Steps Removed - * @description Step identifiers that were removed - */ - steps_removed?: components['schemas']['StepKey'][]; - /** - * Evaluators Removed - * @description Evaluator names that were removed - */ - evaluators_removed?: string[]; - }; - /** - * PatchControlRequest - * @description Request to update control metadata (name, enabled status). - */ - PatchControlRequest: { - /** - * Name - * @description New name for the control - */ - name?: string | null; - /** - * Enabled - * @description Enable or disable the control - */ - enabled?: boolean | null; - }; - /** - * PatchControlResponse - * @description Response from control metadata update. - */ - PatchControlResponse: { - /** - * Success - * @description Whether the update succeeded - */ - success: boolean; - /** - * Name - * @description Current control name (may have changed) - */ - name: string; - /** - * Enabled - * @description Current enabled status (if control has data configured) - */ - enabled?: boolean | null; - }; - /** - * SetControlDataRequest - * @description Request to update control configuration data. - */ - SetControlDataRequest: { - /** @description Control configuration data (replaces existing) */ - data: components['schemas']['ControlDefinition']; - }; - /** SetControlDataResponse */ - SetControlDataResponse: { - /** - * Success - * @description Whether the control data was updated - */ - success: boolean; - }; - /** SetPolicyResponse */ - SetPolicyResponse: { - /** - * Success - * @description Whether the policy was successfully assigned - */ - success: boolean; - /** - * Old Policy Id - * @description Previous policy id if one was replaced - */ - old_policy_id?: number | null; - }; - /** - * StatsResponse - * @description Response model for agent-level aggregated statistics. - * - * Contains agent-level totals (with optional timeseries) and per-control breakdown. - * - * Attributes: - * agent_uuid: Agent UUID - * time_range: Time range used - * totals: Agent-level aggregate statistics (includes timeseries) - * controls: Per-control breakdown for discovery and detail - */ - StatsResponse: { - /** - * Agent Uuid - * Format: uuid - * @description Agent UUID - */ - agent_uuid: string; - /** - * Time Range - * @description Time range used - */ - time_range: string; - /** @description Agent-level aggregate statistics */ - totals: components['schemas']['StatsTotals']; - /** - * Controls - * @description Per-control breakdown - */ - controls: components['schemas']['ControlStats'][]; - }; - /** - * StatsTotals - * @description Agent-level aggregate statistics. - * - * Invariant: execution_count = match_count + non_match_count + error_count - * - * Matches have actions (allow, deny, warn, log) tracked in action_counts. - * sum(action_counts.values()) == match_count - * - * Attributes: - * execution_count: Total executions across all controls - * match_count: Total matches across all controls (evaluator matched) - * non_match_count: Total non-matches across all controls (evaluator didn't match) - * error_count: Total errors across all controls (evaluation failed) - * action_counts: Breakdown of actions for matched executions - * timeseries: Time-series data points (only when include_timeseries=true) - */ - StatsTotals: { - /** - * Execution Count - * @description Total executions - */ - execution_count: number; - /** - * Match Count - * @description Total matches - * @default 0 - */ - match_count: number; - /** - * Non Match Count - * @description Total non-matches - * @default 0 - */ - non_match_count: number; - /** - * Error Count - * @description Total errors - * @default 0 - */ - error_count: number; - /** - * Action Counts - * @description Action breakdown for matches: {allow, deny, warn, log} - */ - action_counts?: { - [key: string]: number; - }; - /** - * Timeseries - * @description Time-series data points (only when include_timeseries=true) - */ - timeseries?: components['schemas']['TimeseriesBucket'][] | null; - }; - /** - * Step - * @description Runtime payload for an agent step invocation. - */ - Step: { - /** - * Type - * @description Step type (e.g., 'tool', 'llm') - */ - type: string; - /** - * Name - * @description Step name (tool name or model/chain id) - */ - name: string; - /** @description Input content for this step */ - input: components['schemas']['JSONValue']; - /** @description Output content for this step (None for pre-checks) */ - output?: components['schemas']['JSONValue'] | null; - /** @description Optional context (conversation history, metadata, etc.) */ - context?: components['schemas']['JSONObject'] | null; - }; - /** - * StepKey - * @description Identifies a registered step schema by type and name. - */ - StepKey: { - /** - * Type - * @description Step type - */ - type: string; - /** - * Name - * @description Registered step name - */ - name: string; - }; - /** - * StepSchema - * @description Schema for a registered agent step. - * @example { - * "description": "Search the internal knowledge base", - * "input_schema": { - * "query": { - * "description": "Search query", - * "type": "string" - * } - * }, - * "name": "search_knowledge_base", - * "output_schema": { - * "results": { - * "items": { - * "type": "object" - * }, - * "type": "array" - * } - * }, - * "type": "tool" - * } - * @example { - * "description": "Customer support response generation", - * "input_schema": { - * "messages": { - * "items": { - * "type": "object" - * }, - * "type": "array" - * } - * }, - * "name": "support-answer", - * "output_schema": { - * "text": { - * "type": "string" - * } - * }, - * "type": "llm" - * } - */ - StepSchema: { - /** - * Type - * @description Step type for this schema (e.g., 'tool', 'llm') - */ - type: string; - /** - * Name - * @description Unique name for the step - */ - name: string; - /** - * Description - * @description Optional description of the step - */ - description?: string | null; - /** - * Input Schema - * @description JSON schema describing step input - */ - input_schema?: { - [key: string]: unknown; - } | null; - /** - * Output Schema - * @description JSON schema describing step output - */ - output_schema?: { - [key: string]: unknown; - } | null; - /** - * Metadata - * @description Additional metadata for the step - */ - metadata?: { - [key: string]: unknown; - } | null; - }; - /** - * TimeseriesBucket - * @description Single data point in a time-series. - * - * Represents aggregated metrics for a single time bucket. - * - * Attributes: - * timestamp: Start time of the bucket (UTC, always timezone-aware) - * execution_count: Total executions in this bucket - * match_count: Number of matches in this bucket - * non_match_count: Number of non-matches in this bucket - * error_count: Number of errors in this bucket - * action_counts: Breakdown of actions for matched executions - * avg_confidence: Average confidence score (None if no executions) - * avg_duration_ms: Average execution duration in milliseconds (None if no data) - */ - TimeseriesBucket: { - /** - * Timestamp - * Format: date-time - * @description Start time of the bucket (UTC) - */ - timestamp: string; - /** - * Execution Count - * @description Total executions in bucket - */ - execution_count: number; - /** - * Match Count - * @description Matches in bucket - */ - match_count: number; - /** - * Non Match Count - * @description Non-matches in bucket - */ - non_match_count: number; - /** - * Error Count - * @description Errors in bucket - */ - error_count: number; - /** - * Action Counts - * @description Action breakdown: {allow, deny, warn, log} - */ - action_counts?: { - [key: string]: number; - }; - /** - * Avg Confidence - * @description Average confidence score - */ - avg_confidence?: number | null; - /** - * Avg Duration Ms - * @description Average duration (ms) - */ - avg_duration_ms?: number | null; - }; - /** - * UpdateEvaluatorConfigRequest - * @description Request to replace an evaluator config template. - */ - UpdateEvaluatorConfigRequest: { - /** - * Name - * @description Unique evaluator config name (letters, numbers, hyphens, underscores) - */ - name: string; - /** - * Description - * @description Optional description - */ - description?: string | null; - /** - * Evaluator - * @description Evaluator name (built-in or custom) - */ - evaluator: string; - /** - * Config - * @description Evaluator-specific configuration - */ - config: { - [key: string]: unknown; - }; - }; - /** ValidationError */ - ValidationError: { - /** Location */ - loc: (string | number)[]; - /** Message */ - msg: string; - /** Error Type */ - type: string; - }; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + schemas: { + /** + * Agent + * @description Agent metadata for registration and tracking. + * + * An agent represents an AI system that can be protected and monitored. + * Each agent has a unique immutable name and can have multiple steps registered with it. + * @example { + * "agent_description": "Handles customer inquiries and support tickets", + * "agent_metadata": { + * "environment": "production", + * "team": "support" + * }, + * "agent_name": "customer-service-bot", + * "agent_version": "1.0.0" + * } + */ + Agent: { + /** + * Agent Created At + * @description ISO 8601 timestamp when agent was created + */ + agent_created_at?: string | null; + /** + * Agent Description + * @description Optional description of the agent's purpose + */ + agent_description?: string | null; + /** + * Agent Metadata + * @description Free-form metadata dictionary for custom properties + */ + agent_metadata?: { + [key: string]: unknown; + } | null; + /** + * Agent Name + * @description Unique immutable identifier for the agent + */ + agent_name: string; + /** + * Agent Updated At + * @description ISO 8601 timestamp when agent was last updated + */ + agent_updated_at?: string | null; + /** + * Agent Version + * @description Semantic version string (e.g. '1.0.0') + */ + agent_version?: string | null; + }; + /** AgentControlsResponse */ + AgentControlsResponse: { + /** + * Controls + * @description List of controls associated with the agent via its policy + */ + controls: components["schemas"]["Control"][]; + }; + /** + * AgentRef + * @description Reference to an agent (for listing which agents use a control). + */ + AgentRef: { + /** + * Agent Name + * @description Agent identifier + */ + agent_name: string; + }; + /** + * AgentSummary + * @description Summary of an agent for list responses. + */ + AgentSummary: { + /** + * Active Controls Count + * @description Number of active controls from agent's policy + * @default 0 + */ + active_controls_count: number; + /** + * Agent Name + * @description Unique identifier of the agent + */ + agent_name: string; + /** + * Created At + * @description ISO 8601 timestamp when agent was created + */ + created_at?: string | null; + /** + * Evaluator Count + * @description Number of evaluators registered with the agent + * @default 0 + */ + evaluator_count: number; + /** + * Policy Id + * @description ID of assigned policy, if any + */ + policy_id?: number | null; + /** + * Step Count + * @description Number of steps registered with the agent + * @default 0 + */ + step_count: number; + }; + /** AssocResponse */ + AssocResponse: { + /** + * Success + * @description Whether the association change succeeded + */ + success: boolean; + }; + /** + * BatchEventsRequest + * @description Request model for batch event ingestion. + * + * SDKs batch events and send them to the server periodically. + * This reduces HTTP overhead significantly (100x reduction). + * + * Attributes: + * events: List of control execution events to ingest + * @example { + * "events": [ + * { + * "action": "deny", + * "agent_name": "my-agent", + * "applies_to": "llm_call", + * "check_stage": "pre", + * "confidence": 0.95, + * "control_id": 123, + * "control_name": "sql-injection-check", + * "matched": true, + * "span_id": "00f067aa0ba902b7", + * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" + * } + * ] + * } + */ + BatchEventsRequest: { + /** + * Events + * @description List of events to ingest + */ + events: components["schemas"]["ControlExecutionEvent"][]; + }; + /** + * BatchEventsResponse + * @description Response model for batch event ingestion. + * + * Attributes: + * received: Number of events received + * enqueued: Number of events successfully enqueued + * dropped: Number of events dropped (queue full) + * status: Overall status ('queued', 'partial', 'failed') + */ + BatchEventsResponse: { + /** + * Dropped + * @description Number of events dropped + */ + dropped: number; + /** + * Enqueued + * @description Number of events enqueued + */ + enqueued: number; + /** + * Received + * @description Number of events received + */ + received: number; + /** + * Status + * @description Overall ingestion status + * @enum {string} + */ + status: "queued" | "partial" | "failed"; + }; + /** + * ConflictMode + * @description Conflict handling mode for initAgent registration updates. + * + * STRICT preserves compatibility checks and raises conflicts on incompatible changes. + * OVERWRITE applies latest-init-wins replacement for steps and evaluators. + * @enum {string} + */ + ConflictMode: "strict" | "overwrite"; + /** + * Control + * @description A control with identity and configuration. + * + * Note: Only fully-configured controls (with valid ControlDefinition) + * are returned from API endpoints. Unconfigured controls are filtered out. + */ + Control: { + control: components["schemas"]["ControlDefinition"]; + /** Id */ + id: number; + /** Name */ + name: string; + }; + /** + * ControlAction + * @description What to do when control matches. + */ + ControlAction: { + /** + * Decision + * @description Action to take when control is triggered + * @enum {string} + */ + decision: "allow" | "deny" | "warn" | "log"; + }; + /** + * ControlDefinition + * @description A control definition to evaluate agent interactions. + * + * This model contains only the logic and configuration. + * Identity fields (id, name) are managed by the database. + * @example { + * "action": { + * "decision": "deny" + * }, + * "description": "Block outputs containing US Social Security Numbers", + * "enabled": true, + * "evaluator": { + * "config": { + * "pattern": "\\b\\d{3}-\\d{2}-\\d{4}\\b" + * }, + * "name": "regex" + * }, + * "execution": "server", + * "scope": { + * "stages": [ + * "post" + * ], + * "step_types": [ + * "llm" + * ] + * }, + * "selector": { + * "path": "output" + * }, + * "tags": [ + * "pii", + * "compliance" + * ] + * } + */ + ControlDefinition: { + /** @description What action to take when control matches */ + action: components["schemas"]["ControlAction"]; + /** + * Description + * @description Detailed description of the control + */ + description?: string | null; + /** + * Enabled + * @description Whether this control is active + * @default true + */ + enabled: boolean; + /** @description How to evaluate the selected data */ + evaluator: components["schemas"]["EvaluatorSpec"]; + /** + * Execution + * @description Where this control executes + * @enum {string} + */ + execution: "server" | "sdk"; + /** @description Which steps and stages this control applies to */ + scope?: components["schemas"]["ControlScope"]; + /** @description What data to select from the payload */ + selector: components["schemas"]["ControlSelector"]; + /** + * Tags + * @description Tags for categorization + */ + tags?: string[]; + }; + /** + * ControlExecutionEvent + * @description Represents a single control execution event. + * + * This is the core observability data model, capturing: + * - Identity: control_execution_id, trace_id, span_id (OpenTelemetry-compatible) + * - Context: agent, control, check stage, applies to + * - Result: action taken, whether matched, confidence score + * - Timing: when it happened, how long it took + * - Optional details: evaluator name, selector path, errors, metadata + * + * Attributes: + * control_execution_id: Unique ID for this specific control execution + * trace_id: OpenTelemetry-compatible trace ID (128-bit hex, 32 chars) + * span_id: OpenTelemetry-compatible span ID (64-bit hex, 16 chars) + * agent_name: Identifier of the agent that executed the control + * control_id: Database ID of the control + * control_name: Name of the control (denormalized for queries) + * check_stage: "pre" (before execution) or "post" (after execution) + * applies_to: "llm_call" or "tool_call" + * action: The action taken (allow, deny, warn, log) + * matched: Whether the control evaluator matched + * confidence: Confidence score from the evaluator (0.0-1.0) + * timestamp: When the control was executed (UTC) + * execution_duration_ms: How long the control evaluation took + * evaluator_name: Name of the evaluator used + * selector_path: The selector path used to extract data + * error_message: Error message if evaluation failed + * metadata: Additional metadata for extensibility + * @example { + * "action": "deny", + * "agent_name": "my-agent", + * "applies_to": "llm_call", + * "check_stage": "pre", + * "confidence": 0.95, + * "control_execution_id": "550e8400-e29b-41d4-a716-446655440000", + * "control_id": 123, + * "control_name": "sql-injection-check", + * "evaluator_name": "regex", + * "execution_duration_ms": 15.3, + * "matched": true, + * "selector_path": "input", + * "span_id": "00f067aa0ba902b7", + * "timestamp": "2025-01-09T10:30:00Z", + * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" + * } + */ + ControlExecutionEvent: { + /** + * Action + * @description Action taken by the control + * @enum {string} + */ + action: "allow" | "deny" | "warn" | "log"; + /** + * Agent Name + * @description Identifier of the agent + */ + agent_name: string; + /** + * Applies To + * @description Type of call: 'llm_call' or 'tool_call' + * @enum {string} + */ + applies_to: "llm_call" | "tool_call"; + /** + * Check Stage + * @description Check stage: 'pre' or 'post' + * @enum {string} + */ + check_stage: "pre" | "post"; + /** + * Confidence + * @description Confidence score (0.0 to 1.0) + */ + confidence: number; + /** + * Control Execution Id + * @description Unique ID for this control execution + */ + control_execution_id?: string; + /** + * Control Id + * @description Database ID of the control + */ + control_id: number; + /** + * Control Name + * @description Name of the control (denormalized) + */ + control_name: string; + /** + * Error Message + * @description Error message if evaluation failed + */ + error_message?: string | null; + /** + * Evaluator Name + * @description Name of the evaluator used + */ + evaluator_name?: string | null; + /** + * Execution Duration Ms + * @description Execution duration in milliseconds + */ + execution_duration_ms?: number | null; + /** + * Matched + * @description Whether the evaluator matched (True) or not (False) + */ + matched: boolean; + /** + * Metadata + * @description Additional metadata + */ + metadata?: { + [key: string]: unknown; + }; + /** + * Selector Path + * @description Selector path used to extract data + */ + selector_path?: string | null; + /** + * Span Id + * @description Span ID for distributed tracing (SDK generates OTEL-compatible 16-char hex) + */ + span_id: string; + /** + * Timestamp + * Format: date-time + * @description When the control was executed (UTC) + */ + timestamp?: string; + /** + * Trace Id + * @description Trace ID for distributed tracing (SDK generates OTEL-compatible 32-char hex) + */ + trace_id: string; + }; + /** + * ControlMatch + * @description Represents a control evaluation result (match, non-match, or error). + */ + ControlMatch: { + /** + * Action + * @description Action configured for this control + * @enum {string} + */ + action: "allow" | "deny" | "warn" | "log"; + /** + * Control Execution Id + * @description Unique ID for this control execution (generated by engine) + */ + control_execution_id?: string; + /** + * Control Id + * @description Database ID of the control + */ + control_id: number; + /** + * Control Name + * @description Name of the control + */ + control_name: string; + /** @description Evaluator result (confidence, message, metadata) */ + result: components["schemas"]["EvaluatorResult"]; + }; + /** + * ControlScope + * @description Defines when a control applies to a Step. + * @example { + * "stages": [ + * "pre" + * ], + * "step_types": [ + * "tool" + * ] + * } + * @example { + * "step_names": [ + * "search_db", + * "fetch_user" + * ] + * } + * @example { + * "step_name_regex": "^db_.*" + * } + * @example { + * "stages": [ + * "post" + * ], + * "step_types": [ + * "llm" + * ] + * } + */ + ControlScope: { + /** + * Stages + * @description Evaluation stages this control applies to + */ + stages?: ("pre" | "post")[] | null; + /** + * Step Name Regex + * @description RE2 pattern matched with search() against step name + */ + step_name_regex?: string | null; + /** + * Step Names + * @description Exact step names this control applies to + */ + step_names?: string[] | null; + /** + * Step Types + * @description Step types this control applies to (omit to apply to all types). Built-in types are 'tool' and 'llm'. + */ + step_types?: string[] | null; + }; + /** + * ControlSelector + * @description Selects data from a Step payload. + * + * - path: which slice of the Step to feed into the evaluator. Optional, defaults to "*" + * meaning the entire Step object. + * @example { + * "path": "output" + * } + * @example { + * "path": "context.user_id" + * } + * @example { + * "path": "input" + * } + * @example { + * "path": "*" + * } + * @example { + * "path": "name" + * } + * @example { + * "path": "output" + * } + */ + ControlSelector: { + /** + * Path + * @description Path to data using dot notation. Examples: 'input', 'output', 'context.user_id', 'name', 'type', '*' + * @default * + */ + path: string | null; + }; + /** + * ControlStats + * @description Aggregated statistics for a single control. + * + * Attributes: + * control_id: Database ID of the control + * control_name: Name of the control + * execution_count: Total number of executions + * match_count: Number of times the control matched + * non_match_count: Number of times the control did not match + * allow_count: Number of allow actions + * deny_count: Number of deny actions + * warn_count: Number of warn actions + * log_count: Number of log actions + * error_count: Number of errors during evaluation + * avg_confidence: Average confidence score + * avg_duration_ms: Average execution duration in milliseconds + */ + ControlStats: { + /** + * Allow Count + * @description Allow actions + */ + allow_count: number; + /** + * Avg Confidence + * @description Average confidence + */ + avg_confidence: number; + /** + * Avg Duration Ms + * @description Average duration (ms) + */ + avg_duration_ms?: number | null; + /** + * Control Id + * @description Control ID + */ + control_id: number; + /** + * Control Name + * @description Control name + */ + control_name: string; + /** + * Deny Count + * @description Deny actions + */ + deny_count: number; + /** + * Error Count + * @description Evaluation errors + */ + error_count: number; + /** + * Execution Count + * @description Total executions + */ + execution_count: number; + /** + * Log Count + * @description Log actions + */ + log_count: number; + /** + * Match Count + * @description Total matches + */ + match_count: number; + /** + * Non Match Count + * @description Total non-matches + */ + non_match_count: number; + /** + * Warn Count + * @description Warn actions + */ + warn_count: number; + }; + /** + * ControlStatsResponse + * @description Response model for control-level statistics. + * + * Contains stats for a single control (with optional timeseries). + * + * Attributes: + * agent_name: Agent identifier + * time_range: Time range used + * control_id: Control ID + * control_name: Control name + * stats: Control statistics (includes timeseries when requested) + */ + ControlStatsResponse: { + /** + * Agent Name + * @description Agent identifier + */ + agent_name: string; + /** + * Control Id + * @description Control ID + */ + control_id: number; + /** + * Control Name + * @description Control name + */ + control_name: string; + /** @description Control statistics */ + stats: components["schemas"]["StatsTotals"]; + /** + * Time Range + * @description Time range used + */ + time_range: string; + }; + /** + * ControlSummary + * @description Summary of a control for list responses. + */ + ControlSummary: { + /** + * Description + * @description Control description + */ + description?: string | null; + /** + * Enabled + * @description Whether control is enabled + * @default true + */ + enabled: boolean; + /** + * Execution + * @description 'server' or 'sdk' + */ + execution?: string | null; + /** + * Id + * @description Control ID + */ + id: number; + /** + * Name + * @description Control name + */ + name: string; + /** + * Stages + * @description Evaluation stages in scope + */ + stages?: string[] | null; + /** + * Step Types + * @description Step types in scope + */ + step_types?: string[] | null; + /** + * Tags + * @description Control tags + */ + tags?: string[]; + /** @description Agent using this control */ + used_by_agent?: components["schemas"]["AgentRef"] | null; + }; + /** CreateControlRequest */ + CreateControlRequest: { + /** + * Name + * @description Unique control name (letters, numbers, hyphens, underscores) + */ + name: string; + }; + /** CreateControlResponse */ + CreateControlResponse: { + /** + * Control Id + * @description Identifier of the created control + */ + control_id: number; + }; + /** + * CreateEvaluatorConfigRequest + * @description Request to create an evaluator config template. + */ + CreateEvaluatorConfigRequest: { + /** + * Config + * @description Evaluator-specific configuration + */ + config: { + [key: string]: unknown; + }; + /** + * Description + * @description Optional description + */ + description?: string | null; + /** + * Evaluator + * @description Evaluator name (built-in or custom) + */ + evaluator: string; + /** + * Name + * @description Unique evaluator config name (letters, numbers, hyphens, underscores) + */ + name: string; + }; + /** CreatePolicyRequest */ + CreatePolicyRequest: { + /** + * Name + * @description Unique policy name (letters, numbers, hyphens, underscores) + */ + name: string; + }; + /** CreatePolicyResponse */ + CreatePolicyResponse: { + /** + * Policy Id + * @description Identifier of the created policy + */ + policy_id: number; + }; + /** + * DeleteControlResponse + * @description Response for deleting a control. + */ + DeleteControlResponse: { + /** + * Dissociated From + * @description Policy IDs the control was removed from before deletion + */ + dissociated_from?: number[]; + /** + * Success + * @description Whether the control was deleted + */ + success: boolean; + }; + /** + * DeleteEvaluatorConfigResponse + * @description Response for deleting an evaluator config. + */ + DeleteEvaluatorConfigResponse: { + /** + * Success + * @description Whether the evaluator config was deleted + */ + success: boolean; + }; + /** DeletePolicyResponse */ + DeletePolicyResponse: { + /** + * Success + * @description Whether the policy was successfully removed + */ + success: boolean; + }; + /** + * EvaluationRequest + * @description Request model for evaluation analysis. + * + * Used to analyze agent interactions for safety violations, + * policy compliance, and control rules. + * + * Attributes: + * agent_name: Unique identifier of the agent making the request + * step: Step payload for evaluation + * stage: 'pre' (before execution) or 'post' (after execution) + * @example { + * "agent_name": "customer-service-bot", + * "stage": "pre", + * "step": { + * "context": { + * "session_id": "abc123", + * "user_id": "user123" + * }, + * "input": "What is the customer's credit card number?", + * "name": "support-answer", + * "type": "llm" + * } + * } + * @example { + * "agent_name": "customer-service-bot", + * "stage": "post", + * "step": { + * "context": { + * "session_id": "abc123", + * "user_id": "user123" + * }, + * "input": "What is the customer's credit card number?", + * "name": "support-answer", + * "output": "I cannot share sensitive payment information.", + * "type": "llm" + * } + * } + * @example { + * "agent_name": "customer-service-bot", + * "stage": "pre", + * "step": { + * "context": { + * "user_id": "user123" + * }, + * "input": { + * "query": "SELECT * FROM users" + * }, + * "name": "search_database", + * "type": "tool" + * } + * } + * @example { + * "agent_name": "customer-service-bot", + * "stage": "post", + * "step": { + * "context": { + * "user_id": "user123" + * }, + * "input": { + * "query": "SELECT * FROM users" + * }, + * "name": "search_database", + * "output": { + * "results": [] + * }, + * "type": "tool" + * } + * } + */ + EvaluationRequest: { + /** + * Agent Name + * @description Identifier of the agent making the evaluation request + */ + agent_name: string; + /** + * Stage + * @description Evaluation stage: 'pre' or 'post' + * @enum {string} + */ + stage: "pre" | "post"; + /** @description Agent step payload to evaluate */ + step: components["schemas"]["Step"]; + }; + /** + * EvaluationResponse + * @description Response model from evaluation analysis (server-side). + * + * This is what the server returns. The SDK may transform this + * into an EvaluationResult for client convenience. + * + * Attributes: + * is_safe: Whether the content is considered safe + * confidence: Confidence score between 0.0 and 1.0 + * reason: Optional explanation for the decision + * matches: List of controls that matched/triggered (if any) + * errors: List of controls that failed during evaluation (if any) + * non_matches: List of controls that were evaluated but did not match (if any) + */ + EvaluationResponse: { + /** + * Confidence + * @description Confidence score (0.0 to 1.0) + */ + confidence: number; + /** + * Errors + * @description List of controls that failed during evaluation (if any) + */ + errors?: components["schemas"]["ControlMatch"][] | null; + /** + * Is Safe + * @description Whether content is safe + */ + is_safe: boolean; + /** + * Matches + * @description List of controls that matched/triggered (if any) + */ + matches?: components["schemas"]["ControlMatch"][] | null; + /** + * Non Matches + * @description List of controls that were evaluated but did not match (if any) + */ + non_matches?: components["schemas"]["ControlMatch"][] | null; + /** + * Reason + * @description Explanation for the decision + */ + reason?: string | null; + }; + /** + * EvaluatorConfigItem + * @description Evaluator config template stored in the server. + */ + EvaluatorConfigItem: { + /** + * Config + * @description Evaluator-specific configuration + */ + config: { + [key: string]: unknown; + }; + /** + * Created At + * @description ISO 8601 created timestamp + */ + created_at?: string | null; + /** + * Description + * @description Optional description + */ + description?: string | null; + /** + * Evaluator + * @description Evaluator name (built-in or custom) + */ + evaluator: string; + /** + * Id + * @description Evaluator config ID + */ + id: number; + /** + * Name + * @description Unique evaluator config name (letters, numbers, hyphens, underscores) + */ + name: string; + /** + * Updated At + * @description ISO 8601 updated timestamp + */ + updated_at?: string | null; + }; + /** + * EvaluatorInfo + * @description Information about a registered evaluator. + */ + EvaluatorInfo: { + /** + * Config Schema + * @description JSON Schema for config + */ + config_schema: { + [key: string]: unknown; + }; + /** + * Description + * @description Evaluator description + */ + description: string; + /** + * Name + * @description Evaluator name + */ + name: string; + /** + * Requires Api Key + * @description Whether evaluator requires API key + */ + requires_api_key: boolean; + /** + * Timeout Ms + * @description Default timeout in milliseconds + */ + timeout_ms: number; + /** + * Version + * @description Evaluator version + */ + version: string; + }; + /** + * EvaluatorResult + * @description Result from a control evaluator. + * + * The `error` field indicates evaluator failures, NOT validation failures: + * - Set `error` for: evaluator crashes, timeouts, missing dependencies, external service errors + * - Do NOT set `error` for: invalid input, syntax errors, schema violations, constraint failures + * + * When `error` is set, `matched` must be False (fail-open on evaluator errors). + * When `error` is None, `matched` reflects the actual validation result. + * + * This distinction allows: + * - Clients to distinguish "data violated rules" from "evaluator is broken" + * - Observability systems to monitor evaluator health separately from validation outcomes + */ + EvaluatorResult: { + /** + * Confidence + * @description Confidence in the evaluation + */ + confidence: number; + /** + * Error + * @description Error message if evaluation failed internally. When set, matched=False is due to error, not actual evaluation. + */ + error?: string | null; + /** + * Matched + * @description Whether the pattern matched + */ + matched: boolean; + /** + * Message + * @description Explanation of the result + */ + message?: string | null; + /** + * Metadata + * @description Additional result metadata + */ + metadata?: { + [key: string]: unknown; + } | null; + }; + /** + * EvaluatorSchema + * @description Schema for a custom evaluator registered with an agent. + * + * Custom evaluators are Evaluator classes deployed with the engine. + * This schema is registered via initAgent for validation and UI purposes. + */ + EvaluatorSchema: { + /** + * Config Schema + * @description JSON Schema for evaluator config validation + */ + config_schema?: { + [key: string]: unknown; + }; + /** + * Description + * @description Optional description + */ + description?: string | null; + /** + * Name + * @description Unique evaluator name + */ + name: string; + }; + /** + * EvaluatorSchemaItem + * @description Evaluator schema summary for list response. + */ + EvaluatorSchemaItem: { + /** Config Schema */ + config_schema: { + [key: string]: unknown; + }; + /** Description */ + description: string | null; + /** Name */ + name: string; + }; + /** + * EvaluatorSpec + * @description Evaluator specification. See GET /evaluators for available evaluators and schemas. + * + * Evaluator reference formats: + * - Built-in: "regex", "list", "json", "sql" + * - External: "galileo.luna2" (requires agent-control-evaluators[galileo]) + * - Agent-scoped: "my-agent:my-evaluator" (validated in endpoint, not here) + */ + EvaluatorSpec: { + /** + * Config + * @description Evaluator-specific configuration + * @example { + * "pattern": "\\d{3}-\\d{2}-\\d{4}" + * } + * @example { + * "logic": "any", + * "values": [ + * "admin" + * ] + * } + */ + config: { + [key: string]: unknown; + }; + /** + * Name + * @description Evaluator name or agent-scoped reference (agent:evaluator) + * @example regex + * @example list + * @example my-agent:pii-detector + */ + name: string; + }; + /** + * EventQueryRequest + * @description Request model for querying raw events. + * + * Supports filtering by various criteria and pagination. + * + * Attributes: + * trace_id: Filter by trace ID (get all events for a request) + * span_id: Filter by span ID (get all events for a function call) + * control_execution_id: Filter by specific event ID + * agent_name: Filter by agent identifier + * control_ids: Filter by control IDs + * actions: Filter by actions (allow, deny, warn, log) + * matched: Filter by matched status + * check_stages: Filter by check stages (pre, post) + * applies_to: Filter by call type (llm_call, tool_call) + * start_time: Filter events after this time + * end_time: Filter events before this time + * limit: Maximum number of events to return + * offset: Offset for pagination + * @example { + * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" + * } + * @example { + * "actions": [ + * "deny", + * "warn" + * ], + * "agent_name": "my-agent", + * "limit": 50, + * "start_time": "2025-01-09T00:00:00Z" + * } + */ + EventQueryRequest: { + /** + * Actions + * @description Filter by actions + */ + actions?: ("allow" | "deny" | "warn" | "log")[] | null; + /** + * Agent Name + * @description Filter by agent identifier + */ + agent_name?: string | null; + /** + * Applies To + * @description Filter by call types + */ + applies_to?: ("llm_call" | "tool_call")[] | null; + /** + * Check Stages + * @description Filter by check stages + */ + check_stages?: ("pre" | "post")[] | null; + /** + * Control Execution Id + * @description Filter by specific event ID + */ + control_execution_id?: string | null; + /** + * Control Ids + * @description Filter by control IDs + */ + control_ids?: number[] | null; + /** + * End Time + * @description Filter events before this time + */ + end_time?: string | null; + /** + * Limit + * @description Maximum events + * @default 100 + */ + limit: number; + /** + * Matched + * @description Filter by matched status + */ + matched?: boolean | null; + /** + * Offset + * @description Pagination offset + * @default 0 + */ + offset: number; + /** + * Span Id + * @description Filter by span ID (all events for a function) + */ + span_id?: string | null; + /** + * Start Time + * @description Filter events after this time + */ + start_time?: string | null; + /** + * Trace Id + * @description Filter by trace ID (all events for a request) + */ + trace_id?: string | null; + }; + /** + * EventQueryResponse + * @description Response model for event queries. + * + * Attributes: + * events: List of matching events + * total: Total number of matching events (for pagination) + * limit: Limit used in query + * offset: Offset used in query + */ + EventQueryResponse: { + /** + * Events + * @description Matching events + */ + events: components["schemas"]["ControlExecutionEvent"][]; + /** + * Limit + * @description Limit used in query + */ + limit: number; + /** + * Offset + * @description Offset used in query + */ + offset: number; + /** + * Total + * @description Total matching events + */ + total: number; + }; + /** + * GetAgentResponse + * @description Response containing agent details and registered steps. + */ + GetAgentResponse: { + /** @description Agent metadata */ + agent: components["schemas"]["Agent"]; + /** + * Evaluators + * @description Custom evaluators registered with this agent + */ + evaluators?: components["schemas"]["EvaluatorSchema"][]; + /** + * Steps + * @description Steps registered with this agent + */ + steps: components["schemas"]["StepSchema"][]; + }; + /** GetControlDataResponse */ + GetControlDataResponse: { + /** @description Control data payload */ + data: components["schemas"]["ControlDefinition"]; + }; + /** + * GetControlResponse + * @description Response containing control details. + */ + GetControlResponse: { + /** @description Control configuration data (None if not yet configured) */ + data?: components["schemas"]["ControlDefinition"] | null; + /** + * Id + * @description Control ID + */ + id: number; + /** + * Name + * @description Control name + */ + name: string; + }; + /** + * GetPolicyControlsResponse + * @description Response containing control IDs associated with a policy. + */ + GetPolicyControlsResponse: { + /** + * Control Ids + * @description List of control IDs associated with the policy + */ + control_ids: number[]; + }; + /** GetPolicyResponse */ + GetPolicyResponse: { + /** + * Policy Id + * @description Identifier of the policy assigned to the agent + */ + policy_id: number; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** + * HealthResponse + * @description Health check response model. + * + * Attributes: + * status: Current health status (e.g., "healthy", "degraded", "unhealthy") + * version: Application version + */ + HealthResponse: { + /** Status */ + status: string; + /** Version */ + version: string; + }; + /** + * InitAgentEvaluatorRemoval + * @description Details for an evaluator removed during overwrite mode. + */ + InitAgentEvaluatorRemoval: { + /** + * Control Ids + * @description IDs of active controls referencing this evaluator + */ + control_ids?: number[]; + /** + * Control Names + * @description Names of active controls referencing this evaluator + */ + control_names?: string[]; + /** + * Name + * @description Evaluator name removed by overwrite + */ + name: string; + /** + * Referenced By Active Controls + * @description Whether this evaluator is still referenced by active controls + * @default false + */ + referenced_by_active_controls: boolean; + }; + /** + * InitAgentOverwriteChanges + * @description Detailed change summary for initAgent overwrite mode. + */ + InitAgentOverwriteChanges: { + /** + * Evaluator Removals + * @description Per-evaluator removal details, including active control references + */ + evaluator_removals?: components["schemas"]["InitAgentEvaluatorRemoval"][]; + /** + * Evaluators Added + * @description Evaluator names added by overwrite + */ + evaluators_added?: string[]; + /** + * Evaluators Removed + * @description Evaluator names removed by overwrite + */ + evaluators_removed?: string[]; + /** + * Evaluators Updated + * @description Existing evaluator names updated by overwrite + */ + evaluators_updated?: string[]; + /** + * Metadata Changed + * @description Whether agent metadata changed + * @default false + */ + metadata_changed: boolean; + /** + * Steps Added + * @description Steps added by overwrite + */ + steps_added?: components["schemas"]["StepKey"][]; + /** + * Steps Removed + * @description Steps removed by overwrite + */ + steps_removed?: components["schemas"]["StepKey"][]; + /** + * Steps Updated + * @description Existing steps updated by overwrite + */ + steps_updated?: components["schemas"]["StepKey"][]; + }; + /** + * InitAgentRequest + * @description Request to initialize or update an agent registration. + * @example { + * "agent": { + * "agent_description": "Handles customer inquiries", + * "agent_name": "customer-service-bot", + * "agent_version": "1.0.0" + * }, + * "evaluators": [ + * { + * "config_schema": { + * "properties": { + * "sensitivity": { + * "type": "string" + * } + * }, + * "type": "object" + * }, + * "description": "Detects PII in text", + * "name": "pii-detector" + * } + * ], + * "steps": [ + * { + * "input_schema": { + * "query": { + * "type": "string" + * } + * }, + * "name": "search_kb", + * "output_schema": { + * "results": { + * "type": "array" + * } + * }, + * "type": "tool" + * } + * ] + * } + */ + InitAgentRequest: { + /** @description Agent metadata including ID, name, and version */ + agent: components["schemas"]["Agent"]; + /** + * @description Conflict handling mode for init registration updates. 'strict' preserves existing compatibility checks. 'overwrite' applies latest-init-wins replacement for steps and evaluators. + * @default strict + */ + conflict_mode: components["schemas"]["ConflictMode"]; + /** + * Evaluators + * @description Custom evaluator schemas for config validation + */ + evaluators?: components["schemas"]["EvaluatorSchema"][]; + /** + * Force Replace + * @description If true, replace corrupted agent data instead of failing. Use only when agent data is corrupted and cannot be parsed. + * @default false + */ + force_replace: boolean; + /** + * Steps + * @description List of steps available to the agent + */ + steps?: components["schemas"]["StepSchema"][]; + }; + /** + * InitAgentResponse + * @description Response from agent initialization. + */ + InitAgentResponse: { + /** + * Controls + * @description Active protection controls for the agent (if policy assigned) + */ + controls?: components["schemas"]["Control"][]; + /** + * Created + * @description True if agent was newly created, False if updated + */ + created: boolean; + /** + * Overwrite Applied + * @description True if overwrite mode changed registration data on an existing agent + * @default false + */ + overwrite_applied: boolean; + /** @description Detailed list of changes applied in overwrite mode */ + overwrite_changes?: components["schemas"]["InitAgentOverwriteChanges"]; + }; + JSONObject: { + [key: string]: components["schemas"]["JSONValue"]; + }; + /** @description Any JSON value */ + JSONValue: unknown; + /** + * ListAgentsResponse + * @description Response for listing agents. + */ + ListAgentsResponse: { + /** + * Agents + * @description List of agent summaries + */ + agents: components["schemas"]["AgentSummary"][]; + /** @description Pagination metadata */ + pagination: components["schemas"]["PaginationInfo"]; + }; + /** + * ListControlsResponse + * @description Response for listing controls. + */ + ListControlsResponse: { + /** + * Controls + * @description List of control summaries + */ + controls: components["schemas"]["ControlSummary"][]; + /** @description Pagination metadata */ + pagination: components["schemas"]["PaginationInfo"]; + }; + /** + * ListEvaluatorConfigsResponse + * @description Response for listing evaluator configs. + */ + ListEvaluatorConfigsResponse: { + /** + * Evaluator Configs + * @description List of evaluator configs + */ + evaluator_configs: components["schemas"]["EvaluatorConfigItem"][]; + /** @description Pagination metadata */ + pagination: components["schemas"]["PaginationInfo"]; + }; + /** + * ListEvaluatorsResponse + * @description Response for listing agent's evaluator schemas. + */ + ListEvaluatorsResponse: { + /** Evaluators */ + evaluators: components["schemas"]["EvaluatorSchemaItem"][]; + pagination: components["schemas"]["PaginationInfo"]; + }; + /** + * PaginationInfo + * @description Pagination metadata for cursor-based pagination. + */ + PaginationInfo: { + /** + * Has More + * @description Whether there are more pages available + */ + has_more: boolean; + /** + * Limit + * @description Number of items per page + */ + limit: number; + /** + * Next Cursor + * @description Cursor for fetching the next page (null if no more pages) + */ + next_cursor?: string | null; + /** + * Total + * @description Total number of items + */ + total: number; + }; + /** + * PatchAgentRequest + * @description Request to modify an agent (remove steps/evaluators). + */ + PatchAgentRequest: { + /** + * Remove Evaluators + * @description Evaluator names to remove from the agent + */ + remove_evaluators?: string[]; + /** + * Remove Steps + * @description Step identifiers to remove from the agent + */ + remove_steps?: components["schemas"]["StepKey"][]; + }; + /** + * PatchAgentResponse + * @description Response from agent modification. + */ + PatchAgentResponse: { + /** + * Evaluators Removed + * @description Evaluator names that were removed + */ + evaluators_removed?: string[]; + /** + * Steps Removed + * @description Step identifiers that were removed + */ + steps_removed?: components["schemas"]["StepKey"][]; + }; + /** + * PatchControlRequest + * @description Request to update control metadata (name, enabled status). + */ + PatchControlRequest: { + /** + * Enabled + * @description Enable or disable the control + */ + enabled?: boolean | null; + /** + * Name + * @description New name for the control + */ + name?: string | null; + }; + /** + * PatchControlResponse + * @description Response from control metadata update. + */ + PatchControlResponse: { + /** + * Enabled + * @description Current enabled status (if control has data configured) + */ + enabled?: boolean | null; + /** + * Name + * @description Current control name (may have changed) + */ + name: string; + /** + * Success + * @description Whether the update succeeded + */ + success: boolean; + }; + /** + * SetControlDataRequest + * @description Request to update control configuration data. + */ + SetControlDataRequest: { + /** @description Control configuration data (replaces existing) */ + data: components["schemas"]["ControlDefinition"]; + }; + /** SetControlDataResponse */ + SetControlDataResponse: { + /** + * Success + * @description Whether the control data was updated + */ + success: boolean; + }; + /** SetPolicyResponse */ + SetPolicyResponse: { + /** + * Old Policy Id + * @description Previous policy id if one was replaced + */ + old_policy_id?: number | null; + /** + * Success + * @description Whether the policy was successfully assigned + */ + success: boolean; + }; + /** + * StatsResponse + * @description Response model for agent-level aggregated statistics. + * + * Contains agent-level totals (with optional timeseries) and per-control breakdown. + * + * Attributes: + * agent_name: Agent identifier + * time_range: Time range used + * totals: Agent-level aggregate statistics (includes timeseries) + * controls: Per-control breakdown for discovery and detail + */ + StatsResponse: { + /** + * Agent Name + * @description Agent identifier + */ + agent_name: string; + /** + * Controls + * @description Per-control breakdown + */ + controls: components["schemas"]["ControlStats"][]; + /** + * Time Range + * @description Time range used + */ + time_range: string; + /** @description Agent-level aggregate statistics */ + totals: components["schemas"]["StatsTotals"]; + }; + /** + * StatsTotals + * @description Agent-level aggregate statistics. + * + * Invariant: execution_count = match_count + non_match_count + error_count + * + * Matches have actions (allow, deny, warn, log) tracked in action_counts. + * sum(action_counts.values()) == match_count + * + * Attributes: + * execution_count: Total executions across all controls + * match_count: Total matches across all controls (evaluator matched) + * non_match_count: Total non-matches across all controls (evaluator didn't match) + * error_count: Total errors across all controls (evaluation failed) + * action_counts: Breakdown of actions for matched executions + * timeseries: Time-series data points (only when include_timeseries=true) + */ + StatsTotals: { + /** + * Action Counts + * @description Action breakdown for matches: {allow, deny, warn, log} + */ + action_counts?: { + [key: string]: number; + }; + /** + * Error Count + * @description Total errors + * @default 0 + */ + error_count: number; + /** + * Execution Count + * @description Total executions + */ + execution_count: number; + /** + * Match Count + * @description Total matches + * @default 0 + */ + match_count: number; + /** + * Non Match Count + * @description Total non-matches + * @default 0 + */ + non_match_count: number; + /** + * Timeseries + * @description Time-series data points (only when include_timeseries=true) + */ + timeseries?: components["schemas"]["TimeseriesBucket"][] | null; + }; + /** + * Step + * @description Runtime payload for an agent step invocation. + */ + Step: { + /** @description Optional context (conversation history, metadata, etc.) */ + context?: components["schemas"]["JSONObject"] | null; + /** @description Input content for this step */ + input: components["schemas"]["JSONValue"]; + /** + * Name + * @description Step name (tool name or model/chain id) + */ + name: string; + /** @description Output content for this step (None for pre-checks) */ + output?: components["schemas"]["JSONValue"] | null; + /** + * Type + * @description Step type (e.g., 'tool', 'llm') + */ + type: string; + }; + /** + * StepKey + * @description Identifies a registered step schema by type and name. + */ + StepKey: { + /** + * Name + * @description Registered step name + */ + name: string; + /** + * Type + * @description Step type + */ + type: string; + }; + /** + * StepSchema + * @description Schema for a registered agent step. + * @example { + * "description": "Search the internal knowledge base", + * "input_schema": { + * "query": { + * "description": "Search query", + * "type": "string" + * } + * }, + * "name": "search_knowledge_base", + * "output_schema": { + * "results": { + * "items": { + * "type": "object" + * }, + * "type": "array" + * } + * }, + * "type": "tool" + * } + * @example { + * "description": "Customer support response generation", + * "input_schema": { + * "messages": { + * "items": { + * "type": "object" + * }, + * "type": "array" + * } + * }, + * "name": "support-answer", + * "output_schema": { + * "text": { + * "type": "string" + * } + * }, + * "type": "llm" + * } + */ + StepSchema: { + /** + * Description + * @description Optional description of the step + */ + description?: string | null; + /** + * Input Schema + * @description JSON schema describing step input + */ + input_schema?: { + [key: string]: unknown; + } | null; + /** + * Metadata + * @description Additional metadata for the step + */ + metadata?: { + [key: string]: unknown; + } | null; + /** + * Name + * @description Unique name for the step + */ + name: string; + /** + * Output Schema + * @description JSON schema describing step output + */ + output_schema?: { + [key: string]: unknown; + } | null; + /** + * Type + * @description Step type for this schema (e.g., 'tool', 'llm') + */ + type: string; + }; + /** + * TimeseriesBucket + * @description Single data point in a time-series. + * + * Represents aggregated metrics for a single time bucket. + * + * Attributes: + * timestamp: Start time of the bucket (UTC, always timezone-aware) + * execution_count: Total executions in this bucket + * match_count: Number of matches in this bucket + * non_match_count: Number of non-matches in this bucket + * error_count: Number of errors in this bucket + * action_counts: Breakdown of actions for matched executions + * avg_confidence: Average confidence score (None if no executions) + * avg_duration_ms: Average execution duration in milliseconds (None if no data) + */ + TimeseriesBucket: { + /** + * Action Counts + * @description Action breakdown: {allow, deny, warn, log} + */ + action_counts?: { + [key: string]: number; + }; + /** + * Avg Confidence + * @description Average confidence score + */ + avg_confidence?: number | null; + /** + * Avg Duration Ms + * @description Average duration (ms) + */ + avg_duration_ms?: number | null; + /** + * Error Count + * @description Errors in bucket + */ + error_count: number; + /** + * Execution Count + * @description Total executions in bucket + */ + execution_count: number; + /** + * Match Count + * @description Matches in bucket + */ + match_count: number; + /** + * Non Match Count + * @description Non-matches in bucket + */ + non_match_count: number; + /** + * Timestamp + * Format: date-time + * @description Start time of the bucket (UTC) + */ + timestamp: string; + }; + /** + * UpdateEvaluatorConfigRequest + * @description Request to replace an evaluator config template. + */ + UpdateEvaluatorConfigRequest: { + /** + * Config + * @description Evaluator-specific configuration + */ + config: { + [key: string]: unknown; + }; + /** + * Description + * @description Optional description + */ + description?: string | null; + /** + * Evaluator + * @description Evaluator name (built-in or custom) + */ + evaluator: string; + /** + * Name + * @description Unique evaluator config name (letters, numbers, hyphens, underscores) + */ + name: string; + }; + /** + * ValidateControlDataRequest + * @description Request to validate control configuration data without saving. + */ + ValidateControlDataRequest: { + /** @description Control configuration data to validate */ + data: components["schemas"]["ControlDefinition"]; + }; + /** ValidateControlDataResponse */ + ValidateControlDataResponse: { + /** + * Success + * @description Whether the control data is valid + */ + success: boolean; + }; + /** ValidationError */ + ValidationError: { + /** Context */ + ctx?: Record; + /** Input */ + input?: unknown; + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - list_agents_api_v1_agents_get: { - parameters: { - query?: { - cursor?: string | null; - limit?: number; - name?: string | null; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of agent summaries */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['ListAgentsResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - init_agent_api_v1_agents_initAgent_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['InitAgentRequest']; - }; - }; - responses: { - /** @description Agent registration status with active controls */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['InitAgentResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_agent_api_v1_agents__agent_id__get: { - parameters: { - query?: never; - header?: never; - path: { - agent_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Agent metadata and registered steps */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['GetAgentResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - patch_agent_api_v1_agents__agent_id__patch: { - parameters: { - query?: never; - header?: never; - path: { - agent_id: string; - }; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['PatchAgentRequest']; - }; - }; - responses: { - /** @description Lists of removed items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['PatchAgentResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - set_agent_policy_api_v1_agents__agent_id__policy__policy_id__post: { - parameters: { - query?: never; - header?: never; - path: { - agent_id: string; - policy_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Success status with previous policy ID */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['SetPolicyResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_agent_policy_api_v1_agents__agent_id__policy_get: { - parameters: { - query?: never; - header?: never; - path: { - agent_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Policy ID */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['GetPolicyResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - delete_agent_policy_api_v1_agents__agent_id__policy_delete: { - parameters: { - query?: never; - header?: never; - path: { - agent_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Success confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['DeletePolicyResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - list_agent_controls_api_v1_agents__agent_id__controls_get: { - parameters: { - query?: never; - header?: never; - path: { - agent_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of controls from agent's policy */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['AgentControlsResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - list_agent_evaluators_api_v1_agents__agent_id__evaluators_get: { - parameters: { - query?: { - cursor?: string | null; - limit?: number; - }; - header?: never; - path: { - agent_id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Evaluator schemas registered with this agent */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['ListEvaluatorsResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_agent_evaluator_api_v1_agents__agent_id__evaluators__evaluator_name__get: { - parameters: { - query?: never; - header?: never; - path: { - agent_id: string; - evaluator_name: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Evaluator schema details */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['EvaluatorSchemaItem']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - create_policy_api_v1_policies_put: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['CreatePolicyRequest']; - }; - }; - responses: { - /** @description Created policy ID */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['CreatePolicyResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - add_control_to_policy_api_v1_policies__policy_id__controls__control_id__post: { - parameters: { - query?: never; - header?: never; - path: { - policy_id: number; - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Success confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['AssocResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - remove_control_from_policy_api_v1_policies__policy_id__controls__control_id__delete: { - parameters: { - query?: never; - header?: never; - path: { - policy_id: number; - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Success confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['AssocResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - list_policy_controls_api_v1_policies__policy_id__controls_get: { - parameters: { - query?: never; - header?: never; - path: { - policy_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of control IDs */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['GetPolicyControlsResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - list_controls_api_v1_controls_get: { - parameters: { - query?: { - /** @description Control ID to start after */ - cursor?: number | null; - limit?: number; - /** @description Filter by name (partial, case-insensitive) */ - name?: string | null; - /** @description Filter by enabled status */ - enabled?: boolean | null; - /** @description Filter by step type (built-ins: 'tool', 'llm') */ - step_type?: string | null; - /** @description Filter by stage ('pre' or 'post') */ - stage?: string | null; - /** @description Filter by execution ('server' or 'sdk') */ - execution?: string | null; - /** @description Filter by tag */ - tag?: string | null; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of controls */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['ListControlsResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - create_control_api_v1_controls_put: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['CreateControlRequest']; - }; - }; - responses: { - /** @description Created control ID */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['CreateControlResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_control_api_v1_controls__control_id__get: { - parameters: { - query?: never; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Control metadata and configuration */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['GetControlResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - delete_control_api_v1_controls__control_id__delete: { - parameters: { - query?: { - /** @description If true, dissociate from all policies before deleting. If false, fail if control is associated with any policy. */ - force?: boolean; - }; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Deletion confirmation with dissociation info */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['DeleteControlResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - patch_control_api_v1_controls__control_id__patch: { - parameters: { - query?: never; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['PatchControlRequest']; - }; - }; - responses: { - /** @description Updated control information */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['PatchControlResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_control_data_api_v1_controls__control_id__data_get: { - parameters: { - query?: never; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Control data payload */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['GetControlDataResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - set_control_data_api_v1_controls__control_id__data_put: { - parameters: { - query?: never; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['SetControlDataRequest']; - }; - }; - responses: { - /** @description Success confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['SetControlDataResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - list_evaluator_configs_api_v1_evaluator_configs_get: { - parameters: { - query?: { - /** @description Evaluator config ID to start after */ - cursor?: number | null; - limit?: number; - /** @description Filter by name (partial, case-insensitive) */ - name?: string | null; - /** @description Filter by evaluator name */ - evaluator?: string | null; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of evaluator configs */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['ListEvaluatorConfigsResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - create_evaluator_config_api_v1_evaluator_configs_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['CreateEvaluatorConfigRequest']; - }; - }; - responses: { - /** @description Created evaluator config */ - 201: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['EvaluatorConfigItem']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_evaluator_config_api_v1_evaluator_configs__config_id__get: { - parameters: { - query?: never; - header?: never; - path: { - config_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Evaluator config details */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['EvaluatorConfigItem']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - update_evaluator_config_api_v1_evaluator_configs__config_id__put: { - parameters: { - query?: never; - header?: never; - path: { - config_id: number; - }; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['UpdateEvaluatorConfigRequest']; - }; - }; - responses: { - /** @description Updated evaluator config */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['EvaluatorConfigItem']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - delete_evaluator_config_api_v1_evaluator_configs__config_id__delete: { - parameters: { - query?: never; - header?: never; - path: { - config_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Deletion confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['DeleteEvaluatorConfigResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - evaluate_api_v1_evaluation_post: { - parameters: { - query?: never; - header?: { - 'X-Trace-Id'?: string | null; - 'X-Span-Id'?: string | null; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['EvaluationRequest']; - }; - }; - responses: { - /** @description Safety analysis result */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['EvaluationResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_evaluators_api_v1_evaluators_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Dictionary of evaluator name to evaluator info */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': { - [key: string]: components['schemas']['EvaluatorInfo']; - }; - }; - }; - }; - }; - ingest_events_api_v1_observability_events_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['BatchEventsRequest']; - }; - }; - responses: { - /** @description Successful Response */ - 202: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['BatchEventsResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - query_events_api_v1_observability_events_query_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['EventQueryRequest']; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['EventQueryResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_stats_api_v1_observability_stats_get: { - parameters: { - query: { - agent_uuid: string; - time_range?: - | '1m' - | '5m' - | '15m' - | '1h' - | '24h' - | '7d' - | '30d' - | '180d' - | '365d'; - include_timeseries?: boolean; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['StatsResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_control_stats_api_v1_observability_stats_controls__control_id__get: { - parameters: { - query: { - agent_uuid: string; - time_range?: - | '1m' - | '5m' - | '15m' - | '1h' - | '24h' - | '7d' - | '30d' - | '180d' - | '365d'; - include_timeseries?: boolean; - }; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['ControlStatsResponse']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_status_api_v1_observability_status_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': { - [key: string]: unknown; - }; - }; - }; - }; - }; - health_check_health_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Server health status */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HealthResponse']; + list_agents_api_v1_agents_get: { + parameters: { + query?: { + cursor?: string | null; + limit?: number; + name?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of agent summaries */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListAgentsResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + init_agent_api_v1_agents_initAgent_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["InitAgentRequest"]; + }; + }; + responses: { + /** @description Agent registration status with active controls */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InitAgentResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_agent_api_v1_agents__agent_name__get: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Agent metadata and registered steps */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetAgentResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + patch_agent_api_v1_agents__agent_name__patch: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PatchAgentRequest"]; + }; + }; + responses: { + /** @description Lists of removed items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PatchAgentResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_agent_controls_api_v1_agents__agent_name__controls_get: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of controls from agent's policy */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentControlsResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_agent_evaluators_api_v1_agents__agent_name__evaluators_get: { + parameters: { + query?: { + cursor?: string | null; + limit?: number; + }; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Evaluator schemas registered with this agent */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListEvaluatorsResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_agent_evaluator_api_v1_agents__agent_name__evaluators__evaluator_name__get: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + evaluator_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Evaluator schema details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EvaluatorSchemaItem"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_agent_policy_api_v1_agents__agent_name__policy_get: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Policy ID */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetPolicyResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_agent_policy_api_v1_agents__agent_name__policy_delete: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeletePolicyResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + set_agent_policy_api_v1_agents__agent_name__policy__policy_id__post: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + policy_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success status with previous policy ID */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SetPolicyResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_controls_api_v1_controls_get: { + parameters: { + query?: { + /** @description Control ID to start after */ + cursor?: number | null; + limit?: number; + /** @description Filter by name (partial, case-insensitive) */ + name?: string | null; + /** @description Filter by enabled status */ + enabled?: boolean | null; + /** @description Filter by step type (built-ins: 'tool', 'llm') */ + step_type?: string | null; + /** @description Filter by stage ('pre' or 'post') */ + stage?: string | null; + /** @description Filter by execution ('server' or 'sdk') */ + execution?: string | null; + /** @description Filter by tag */ + tag?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of controls */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListControlsResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_control_api_v1_controls_put: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateControlRequest"]; + }; + }; + responses: { + /** @description Created control ID */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateControlResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + validate_control_data_api_v1_controls_validate_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ValidateControlDataRequest"]; + }; + }; + responses: { + /** @description Validation result */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ValidateControlDataResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_control_api_v1_controls__control_id__get: { + parameters: { + query?: never; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Control metadata and configuration */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetControlResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_control_api_v1_controls__control_id__delete: { + parameters: { + query?: { + /** @description If true, dissociate from all policies before deleting. If false, fail if control is associated with any policy. */ + force?: boolean; + }; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Deletion confirmation with dissociation info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeleteControlResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + patch_control_api_v1_controls__control_id__patch: { + parameters: { + query?: never; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PatchControlRequest"]; + }; + }; + responses: { + /** @description Updated control information */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PatchControlResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_control_data_api_v1_controls__control_id__data_get: { + parameters: { + query?: never; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Control data payload */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetControlDataResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + set_control_data_api_v1_controls__control_id__data_put: { + parameters: { + query?: never; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SetControlDataRequest"]; + }; + }; + responses: { + /** @description Success confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SetControlDataResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + evaluate_api_v1_evaluation_post: { + parameters: { + query?: never; + header?: { + "X-Trace-Id"?: string | null; + "X-Span-Id"?: string | null; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EvaluationRequest"]; + }; + }; + responses: { + /** @description Safety analysis result */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EvaluationResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_evaluator_configs_api_v1_evaluator_configs_get: { + parameters: { + query?: { + /** @description Evaluator config ID to start after */ + cursor?: number | null; + limit?: number; + /** @description Filter by name (partial, case-insensitive) */ + name?: string | null; + /** @description Filter by evaluator name */ + evaluator?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of evaluator configs */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListEvaluatorConfigsResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_evaluator_config_api_v1_evaluator_configs_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateEvaluatorConfigRequest"]; + }; + }; + responses: { + /** @description Created evaluator config */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EvaluatorConfigItem"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_evaluator_config_api_v1_evaluator_configs__config_id__get: { + parameters: { + query?: never; + header?: never; + path: { + config_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Evaluator config details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EvaluatorConfigItem"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_evaluator_config_api_v1_evaluator_configs__config_id__put: { + parameters: { + query?: never; + header?: never; + path: { + config_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateEvaluatorConfigRequest"]; + }; + }; + responses: { + /** @description Updated evaluator config */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EvaluatorConfigItem"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_evaluator_config_api_v1_evaluator_configs__config_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + config_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Deletion confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeleteEvaluatorConfigResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_evaluators_api_v1_evaluators_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Dictionary of evaluator name to evaluator info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: components["schemas"]["EvaluatorInfo"]; + }; + }; + }; + }; + }; + ingest_events_api_v1_observability_events_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BatchEventsRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BatchEventsResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + query_events_api_v1_observability_events_query_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EventQueryRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EventQueryResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_stats_api_v1_observability_stats_get: { + parameters: { + query: { + agent_name: string; + time_range?: "1m" | "5m" | "15m" | "1h" | "24h" | "7d" | "30d" | "180d" | "365d"; + include_timeseries?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StatsResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_control_stats_api_v1_observability_stats_controls__control_id__get: { + parameters: { + query: { + agent_name: string; + time_range?: "1m" | "5m" | "15m" | "1h" | "24h" | "7d" | "30d" | "180d" | "365d"; + include_timeseries?: boolean; + }; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ControlStatsResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_status_api_v1_observability_status_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + }; + }; + create_policy_api_v1_policies_put: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreatePolicyRequest"]; + }; + }; + responses: { + /** @description Created policy ID */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreatePolicyResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_policy_controls_api_v1_policies__policy_id__controls_get: { + parameters: { + query?: never; + header?: never; + path: { + policy_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of control IDs */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetPolicyControlsResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + add_control_to_policy_api_v1_policies__policy_id__controls__control_id__post: { + parameters: { + query?: never; + header?: never; + path: { + policy_id: number; + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AssocResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + remove_control_from_policy_api_v1_policies__policy_id__controls__control_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + policy_id: number; + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AssocResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + health_check_health_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Server health status */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HealthResponse"]; + }; + }; }; - }; }; - }; } diff --git a/ui/src/core/api/types.ts b/ui/src/core/api/types.ts index d60c0705..ca2f944e 100644 --- a/ui/src/core/api/types.ts +++ b/ui/src/core/api/types.ts @@ -106,11 +106,7 @@ export type ListControlsResponse = components['schemas']['ListControlsResponse']; // AgentRef - reference to an agent (for used_by_agents) -// Note: This will be in generated types after running pnpm fetch-api-types -export type AgentRef = { - agent_id: string; - agent_name: string; -}; +export type AgentRef = components['schemas']['AgentRef']; // Helper type to extract query parameters from operations type ExtractQueryParams = T extends { parameters: { query?: infer Q } } @@ -134,10 +130,10 @@ export type ListAgentsQueryParams = ExtractQueryParams< operations['list_agents_api_v1_agents_get'] >; export type GetAgentPathParams = ExtractPathParams< - operations['get_agent_api_v1_agents__agent_id__get'] + operations['get_agent_api_v1_agents__agent_name__get'] >; export type GetAgentControlsPathParams = ExtractPathParams< - operations['list_agent_controls_api_v1_agents__agent_id__controls_get'] + operations['list_agent_controls_api_v1_agents__agent_name__controls_get'] >; // Request body types diff --git a/ui/src/core/hooks/query-hooks/use-agent-controls.ts b/ui/src/core/hooks/query-hooks/use-agent-controls.ts index baaedaa9..0cb18df3 100644 --- a/ui/src/core/hooks/query-hooks/use-agent-controls.ts +++ b/ui/src/core/hooks/query-hooks/use-agent-controls.ts @@ -9,15 +9,15 @@ import type { /** * Query hook to fetch active controls for an agent * - * @param agentId - UUID of the agent (required) + * @param agentName - Immutable agent name (required) */ export function useAgentControls( - agentId: GetAgentControlsPathParams['agent_id'] + agentName: GetAgentControlsPathParams['agent_name'] ) { return useQuery({ - queryKey: ['agent', agentId, 'controls'], + queryKey: ['agent', agentName, 'controls'], queryFn: async () => { - const { data, error } = await api.agents.getControls(agentId); + const { data, error } = await api.agents.getControls(agentName); if (error) throw error; return data; }, diff --git a/ui/src/core/hooks/query-hooks/use-agent-monitor.ts b/ui/src/core/hooks/query-hooks/use-agent-monitor.ts index cb1198a8..5e818c08 100644 --- a/ui/src/core/hooks/query-hooks/use-agent-monitor.ts +++ b/ui/src/core/hooks/query-hooks/use-agent-monitor.ts @@ -19,7 +19,7 @@ export type TimeseriesBucket = components['schemas']['TimeseriesBucket']; export type StatsTotals = components['schemas']['StatsTotals']; export function useAgentMonitor( - agentUuid: string, + agentName: string, timeRange: TimeRange = '1h', options?: { enabled?: boolean; @@ -30,13 +30,13 @@ export function useAgentMonitor( return useQuery({ queryKey: [ 'agent-monitor', - agentUuid, + agentName, timeRange, options?.includeTimeseries ?? false, ], queryFn: async (): Promise => { const { data, error } = await api.observability.getStats({ - agent_uuid: agentUuid, + agent_name: agentName, time_range: timeRange, include_timeseries: options?.includeTimeseries ?? false, }); @@ -47,7 +47,7 @@ export function useAgentMonitor( return data; }, - enabled: options?.enabled !== false && !!agentUuid, + enabled: options?.enabled !== false && !!agentName, refetchInterval: options?.refetchInterval ?? 5000, // Default 5 seconds refetchIntervalInBackground: false, // Pause polling when tab is not visible placeholderData: keepPreviousData, // Keep showing previous data while loading new time range diff --git a/ui/src/core/hooks/query-hooks/use-agent.ts b/ui/src/core/hooks/query-hooks/use-agent.ts index 6c728ca4..e01f8a30 100644 --- a/ui/src/core/hooks/query-hooks/use-agent.ts +++ b/ui/src/core/hooks/query-hooks/use-agent.ts @@ -5,13 +5,13 @@ import { api } from '@/core/api/client'; import type { GetAgentPathParams, GetAgentResponse } from '@/core/api/types'; /** - * Query hook to fetch a single agent by ID + * Query hook to fetch a single agent by identifier. * - * @param agentId - UUID of the agent (required) + * @param agentName - Immutable agent name (required) * */ export function useAgent( - agentId: GetAgentPathParams['agent_id'], + agentName: GetAgentPathParams['agent_name'], options?: Omit< UseQueryOptions< GetAgentResponse, @@ -23,12 +23,12 @@ export function useAgent( > ): UseQueryResult { const { enabled, ...rest } = options ?? {}; - const isEnabled = enabled ?? Boolean(agentId); + const isEnabled = enabled ?? Boolean(agentName); return useQuery({ - queryKey: ['agent', agentId], + queryKey: ['agent', agentName], queryFn: async () => { - const { data, error } = await api.agents.get(agentId); + const { data, error } = await api.agents.get(agentName); if (error) throw error; return data; }, diff --git a/ui/src/core/hooks/query-hooks/use-has-monitor-data.ts b/ui/src/core/hooks/query-hooks/use-has-monitor-data.ts index e66fdc92..e6276b78 100644 --- a/ui/src/core/hooks/query-hooks/use-has-monitor-data.ts +++ b/ui/src/core/hooks/query-hooks/use-has-monitor-data.ts @@ -34,18 +34,18 @@ function getStoredTimeRange(): TimeRange { * Uses the stored time range preference from localStorage. */ export function useHasMonitorData( - agentUuid: string, + agentName: string, options?: { enabled?: boolean; } ) { return useQuery({ - queryKey: ['has-monitor-data', agentUuid], + queryKey: ['has-monitor-data', agentName], queryFn: async () => { const timeRange = getStoredTimeRange(); const { data, error } = await api.observability.getStats({ - agent_uuid: agentUuid, + agent_name: agentName, time_range: timeRange, include_timeseries: false, // Don't need timeseries, just totals }); @@ -61,7 +61,7 @@ export function useHasMonitorData( return hasData; }, - enabled: options?.enabled !== false && !!agentUuid, + enabled: options?.enabled !== false && !!agentName, staleTime: 30000, // Consider data fresh for 30 seconds refetchOnWindowFocus: false, // Don't refetch on window focus }); diff --git a/ui/src/core/layouts/app-layout.tsx b/ui/src/core/layouts/app-layout.tsx index 40b0c142..077ceb51 100644 --- a/ui/src/core/layouts/app-layout.tsx +++ b/ui/src/core/layouts/app-layout.tsx @@ -236,10 +236,10 @@ export function AppLayout({ children }: AppLayoutProps) { <> {allAgents.map((agent) => ( ))} diff --git a/ui/src/core/page-components/agent-detail/agent-detail.tsx b/ui/src/core/page-components/agent-detail/agent-detail.tsx index 2405b4d3..aa251f59 100644 --- a/ui/src/core/page-components/agent-detail/agent-detail.tsx +++ b/ui/src/core/page-components/agent-detail/agent-detail.tsx @@ -328,9 +328,9 @@ const AgentDetailPage = ({ agentId, defaultTab }: AgentDetailPageProps) => { - {agent?.agent.agent_id && activeTab === 'monitor' ? ( + {agent?.agent.agent_name && activeTab === 'monitor' ? ( ) : null} diff --git a/ui/src/core/page-components/agent-detail/modals/control-store/index.tsx b/ui/src/core/page-components/agent-detail/modals/control-store/index.tsx index e5d7609d..de09c575 100644 --- a/ui/src/core/page-components/agent-detail/modals/control-store/index.tsx +++ b/ui/src/core/page-components/agent-detail/modals/control-store/index.tsx @@ -37,7 +37,7 @@ import { AddNewControlModal } from '../add-new-control'; import { EditControlContent } from '../edit-control/edit-control-content'; import { sanitizeControlNamePart } from '../edit-control/utils'; -// Extended ControlSummary with used_by_agent (until API types are regenerated) +// Extended ControlSummary with optional used_by_agent type ControlSummaryWithAgent = ControlSummary & { used_by_agent?: AgentRef | null; }; @@ -289,7 +289,7 @@ export function ControlStoreModal({ ); } // Link to agent controls tab with control name filter - const href = `/agents/${agent.agent_id}/controls?q=${encodeURIComponent(control.name)}`; + const href = `/agents/${agent.agent_name}/controls?q=${encodeURIComponent(control.name)}`; return ( { }, [data]); const handleRowClick = (agent: AgentTableRow) => { - router.push(`/agents/${agent.agent_id}`); + router.push(`/agents/${agent.agent_name}`); }; // Define table columns diff --git a/ui/tests/fixtures.ts b/ui/tests/fixtures.ts index de77e844..69725b20 100644 --- a/ui/tests/fixtures.ts +++ b/ui/tests/fixtures.ts @@ -20,8 +20,7 @@ import type { StatsResponse } from '@/core/hooks/query-hooks/use-agent-monitor'; // Satisfies ensures type checking while allowing inference of literal types const agentsList: AgentSummary[] = [ { - agent_id: 'agent-1', - agent_name: 'Customer Support Bot', + agent_name: 'customer-support-bot', policy_id: 1, created_at: '2024-01-01T00:00:00Z', step_count: 5, @@ -29,8 +28,7 @@ const agentsList: AgentSummary[] = [ active_controls_count: 3, }, { - agent_id: 'agent-2', - agent_name: 'Data Analysis Agent', + agent_name: 'data-analysis-agent', policy_id: 2, created_at: '2024-01-02T00:00:00Z', step_count: 3, @@ -38,8 +36,7 @@ const agentsList: AgentSummary[] = [ active_controls_count: 2, }, { - agent_id: 'agent-3', - agent_name: 'Code Review Assistant', + agent_name: 'code-review-assistant', policy_id: 3, created_at: '2024-01-03T00:00:00Z', step_count: 8, @@ -60,8 +57,7 @@ const agentsResponse: ListAgentsResponse = { const agentResponse: GetAgentResponse = { agent: { - agent_id: 'agent-1', - agent_name: 'Customer Support Bot', + agent_name: 'customer-support-bot', agent_description: 'Handles customer inquiries and support tickets', agent_created_at: '2024-01-01T00:00:00Z', agent_updated_at: '2024-01-15T00:00:00Z', @@ -148,7 +144,7 @@ const controlsResponse: AgentControlsResponse = { // Control summaries for GET /api/v1/controls (list all controls) const controlSummariesList: (ControlSummary & { - used_by_agent?: { agent_id: string; agent_name: string } | null; + used_by_agent?: { agent_name: string } | null; })[] = [ { id: 1, @@ -159,7 +155,7 @@ const controlSummariesList: (ControlSummary & { step_types: ['llm'], stages: ['post'], tags: ['pii', 'compliance'], - used_by_agent: { agent_id: 'agent-1', agent_name: 'Customer Support Bot' }, + used_by_agent: { agent_name: 'customer-support-bot' }, }, { id: 2, @@ -170,7 +166,7 @@ const controlSummariesList: (ControlSummary & { step_types: ['tool'], stages: ['pre'], tags: ['security'], - used_by_agent: { agent_id: 'agent-2', agent_name: 'Data Analysis Agent' }, + used_by_agent: { agent_name: 'data-analysis-agent' }, }, { id: 3, @@ -269,7 +265,7 @@ const evaluatorsResponse: EvaluatorsResponse = { }; const statsResponse: StatsResponse = { - agent_uuid: 'agent-1', + agent_name: 'customer-support-bot', time_range: '1h', totals: { execution_count: 430, @@ -330,7 +326,7 @@ const statsResponse: StatsResponse = { }; const emptyStatsResponse: StatsResponse = { - agent_uuid: 'agent-1', + agent_name: 'customer-support-bot', time_range: '1h', totals: { execution_count: 0, diff --git a/ui/tests/home.spec.ts b/ui/tests/home.spec.ts index d2a95886..bc8751d2 100644 --- a/ui/tests/home.spec.ts +++ b/ui/tests/home.spec.ts @@ -46,12 +46,12 @@ test.describe('Home Page - Agents Overview', () => { ); // Only the matching agent should be visible - await expect(mockedPage.getByText('Customer Support Bot')).toBeVisible(); + await expect(mockedPage.getByText('customer-support-bot')).toBeVisible(); // Non-matching agents should be hidden - await expect(mockedPage.getByText('Data Analysis Agent')).not.toBeVisible(); + await expect(mockedPage.getByText('data-analysis-agent')).not.toBeVisible(); await expect( - mockedPage.getByText('Code Review Assistant') + mockedPage.getByText('code-review-assistant') ).not.toBeVisible(); // Clear search to show all agents again @@ -62,7 +62,7 @@ test.describe('Home Page - Agents Overview', () => { // Wait for a previously hidden agent to become visible (confirms filter was cleared) // This is more reliable than waiting for an API response that might not happen - await expect(mockedPage.getByText('Data Analysis Agent')).toBeVisible({ + await expect(mockedPage.getByText('data-analysis-agent')).toBeVisible({ timeout: 5000, }); @@ -129,7 +129,7 @@ test.describe('Home Page - Agents Overview', () => { // Verify navigation to agent detail page // Since stats mock returns data, it will redirect to monitor tab await expect(mockedPage).toHaveURL( - `/agents/${firstAgent.agent_id}/monitor` + `/agents/${firstAgent.agent_name}/monitor` ); }); From b950927051b9aa5ffb5d6398b6c5c80d2218e7ac Mon Sep 17 00:00:00 2001 From: Lev Neiman Date: Wed, 25 Feb 2026 17:39:47 -0500 Subject: [PATCH 2/6] style(ui): format generated api types --- ui/src/core/api/generated/api-types.ts | 8104 ++++++++++++------------ 1 file changed, 4061 insertions(+), 4043 deletions(-) diff --git a/ui/src/core/api/generated/api-types.ts b/ui/src/core/api/generated/api-types.ts index f90c3286..8f635916 100644 --- a/ui/src/core/api/generated/api-types.ts +++ b/ui/src/core/api/generated/api-types.ts @@ -4,4056 +4,4074 @@ */ export interface paths { - "/api/v1/agents": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List all agents - * @description List all registered agents with cursor-based pagination. - * - * Returns a summary of each agent including identifier, policy assignment, - * and counts of registered steps and evaluators. - * - * Args: - * cursor: Optional cursor for pagination (last agent name from previous page) - * limit: Pagination limit (default 20, max 100) - * name: Optional name filter (case-insensitive partial match) - * db: Database session (injected) - * - * Returns: - * ListAgentsResponse with agent summaries and pagination info - */ - get: operations["list_agents_api_v1_agents_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/agents/initAgent": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Initialize or update an agent - * @description Register a new agent or update an existing agent's steps and metadata. - * - * This endpoint is idempotent: - * - If the agent name doesn't exist, creates a new agent - * - If the agent name exists, updates registration data in place - * - * conflict_mode controls registration conflict handling: - * - strict (default): preserve compatibility checks and conflict errors - * - overwrite: latest init payload replaces steps/evaluators and returns change summary - * - * Args: - * request: Agent metadata and step schemas - * db: Database session (injected) - * - * Returns: - * InitAgentResponse with created flag and active controls (if policy assigned) - */ - post: operations["init_agent_api_v1_agents_initAgent_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/agents/{agent_name}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get agent details - * @description Retrieve agent metadata and all registered steps. - * - * Returns the latest version of each step (deduplicated by type+name). - * - * Args: - * agent_name: Agent identifier - * db: Database session (injected) - * - * Returns: - * GetAgentResponse with agent metadata and step list - * - * Raises: - * HTTPException 404: Agent not found - * HTTPException 422: Agent data is corrupted - */ - get: operations["get_agent_api_v1_agents__agent_name__get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - /** - * Modify agent (remove steps/evaluators) - * @description Remove steps and/or evaluators from an agent. - * - * This is the complement to initAgent which only adds items. - * Removals are idempotent - attempting to remove non-existent items is not an error. - * - * Args: - * agent_name: Agent identifier - * request: Lists of step/evaluator identifiers to remove - * db: Database session (injected) - * - * Returns: - * PatchAgentResponse with lists of actually removed items - * - * Raises: - * HTTPException 404: Agent not found - * HTTPException 500: Database error during update - */ - patch: operations["patch_agent_api_v1_agents__agent_name__patch"]; - trace?: never; - }; - "/api/v1/agents/{agent_name}/controls": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List agent's active controls - * @description List all protection controls active for an agent. - * - * Controls are inherited from the agent's assigned policy. - * Returns an empty list if the agent has no policy. - * - * Args: - * agent_name: Agent identifier - * db: Database session (injected) - * - * Returns: - * AgentControlsResponse with list of controls (empty if no policy) - * - * Raises: - * HTTPException 404: Agent not found - */ - get: operations["list_agent_controls_api_v1_agents__agent_name__controls_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/agents/{agent_name}/evaluators": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List agent's registered evaluator schemas - * @description List all evaluator schemas registered with an agent. - * - * Evaluator schemas are registered via initAgent and used for: - * - Config validation when creating Controls - * - UI to display available config options - * - * Args: - * agent_name: Agent identifier - * cursor: Optional cursor for pagination (name of last evaluator from previous page) - * limit: Pagination limit (default 20, max 100) - * db: Database session (injected) - * - * Returns: - * ListEvaluatorsResponse with evaluator schemas and pagination - * - * Raises: - * HTTPException 404: Agent not found - */ - get: operations["list_agent_evaluators_api_v1_agents__agent_name__evaluators_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/agents/{agent_name}/evaluators/{evaluator_name}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get specific evaluator schema - * @description Get a specific evaluator schema registered with an agent. - * - * Args: - * agent_name: Agent identifier - * evaluator_name: Name of the evaluator - * db: Database session (injected) - * - * Returns: - * EvaluatorSchemaItem with schema details - * - * Raises: - * HTTPException 404: Agent or evaluator not found - */ - get: operations["get_agent_evaluator_api_v1_agents__agent_name__evaluators__evaluator_name__get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/agents/{agent_name}/policy": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get agent's assigned policy - * @description Retrieve the policy currently assigned to an agent. - * - * Args: - * agent_name: Agent identifier - * db: Database session (injected) - * - * Returns: - * GetPolicyResponse with policy ID - * - * Raises: - * HTTPException 404: Agent not found or agent has no policy assigned - */ - get: operations["get_agent_policy_api_v1_agents__agent_name__policy_get"]; - put?: never; - post?: never; - /** - * Remove agent's policy assignment - * @description Remove the policy assignment from an agent. - * - * The agent will no longer have any protection controls active. - * - * Args: - * agent_name: Agent identifier - * db: Database session (injected) - * - * Returns: - * DeletePolicyResponse with success flag - * - * Raises: - * HTTPException 404: Agent not found or agent has no policy assigned - * HTTPException 500: Database error during removal - */ - delete: operations["delete_agent_policy_api_v1_agents__agent_name__policy_delete"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/agents/{agent_name}/policy/{policy_id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Assign policy to agent - * @description Assign a policy to an agent, replacing any existing policy assignment. - * - * The agent will immediately inherit all controls from the assigned policy. - * - * Args: - * agent_name: Agent identifier - * policy_id: ID of the policy to assign - * db: Database session (injected) - * - * Returns: - * SetPolicyResponse with success flag and previous policy ID (if any) - * - * Raises: - * HTTPException 404: Agent or policy not found - * HTTPException 500: Database error during assignment - */ - post: operations["set_agent_policy_api_v1_agents__agent_name__policy__policy_id__post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/controls": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List all controls - * @description List all controls with optional filtering and cursor-based pagination. - * - * Controls are returned ordered by ID descending (newest first). - * - * Args: - * cursor: ID of the last control from the previous page (for pagination) - * limit: Maximum number of controls to return (default 20, max 100) - * name: Optional filter by name (partial, case-insensitive match) - * enabled: Optional filter by enabled status - * step_type: Optional filter by step type (built-ins: 'tool', 'llm') - * stage: Optional filter by stage ('pre' or 'post') - * execution: Optional filter by execution ('server' or 'sdk') - * tag: Optional filter by tag - * db: Database session (injected) - * - * Returns: - * ListControlsResponse with control summaries and pagination info - * - * Example: - * GET /controls?limit=10&enabled=true&step_type=tool - */ - get: operations["list_controls_api_v1_controls_get"]; - /** - * Create a new control - * @description Create a new control with a unique name and empty data. - * - * Controls define protection logic and can be added to policies. - * Use the PUT /{control_id}/data endpoint to set control configuration. - * - * Args: - * request: Control creation request with unique name - * db: Database session (injected) - * - * Returns: - * CreateControlResponse with the new control's ID - * - * Raises: - * HTTPException 409: Control with this name already exists - * HTTPException 500: Database error during creation - */ - put: operations["create_control_api_v1_controls_put"]; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/controls/validate": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Validate control configuration - * @description Validate control configuration data without saving it. - * - * Args: - * request: Control configuration data to validate - * db: Database session (injected) - * - * Returns: - * ValidateControlDataResponse with success=True if valid - */ - post: operations["validate_control_data_api_v1_controls_validate_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/controls/{control_id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get control details - * @description Retrieve a control by ID including its name and configuration data. - * - * Args: - * control_id: ID of the control - * db: Database session (injected) - * - * Returns: - * GetControlResponse with control id, name, and data - * - * Raises: - * HTTPException 404: Control not found - */ - get: operations["get_control_api_v1_controls__control_id__get"]; - put?: never; - post?: never; - /** - * Delete a control - * @description Delete a control by ID. - * - * By default, deletion fails if the control is associated with any policy. - * Use force=true to automatically dissociate and delete. - * - * Args: - * control_id: ID of the control to delete - * force: If true, remove associations before deleting - * db: Database session (injected) - * - * Returns: - * DeleteControlResponse with success flag and list of dissociated policies - * - * Raises: - * HTTPException 404: Control not found - * HTTPException 409: Control is in use (and force=false) - * HTTPException 500: Database error during deletion - */ - delete: operations["delete_control_api_v1_controls__control_id__delete"]; - options?: never; - head?: never; - /** - * Update control metadata - * @description Update control metadata (name and/or enabled status). - * - * This endpoint allows partial updates: - * - To rename: provide 'name' field - * - To enable/disable: provide 'enabled' field (updates the control's data) - * - * Args: - * control_id: ID of the control to update - * request: Fields to update (name, enabled) - * db: Database session (injected) - * - * Returns: - * PatchControlResponse with current control state - * - * Raises: - * HTTPException 404: Control not found - * HTTPException 409: New name conflicts with existing control - * HTTPException 422: Cannot update enabled status (control has no data configured) - * HTTPException 500: Database error during update - */ - patch: operations["patch_control_api_v1_controls__control_id__patch"]; - trace?: never; - }; - "/api/v1/controls/{control_id}/data": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get control configuration data - * @description Retrieve the configuration data for a control. - * - * Control data is a JSONB field that must follow the ControlDefinition schema. - * - * Args: - * control_id: ID of the control - * db: Database session (injected) - * - * Returns: - * GetControlDataResponse with validated ControlDefinition - * - * Raises: - * HTTPException 404: Control not found - * HTTPException 422: Control data is corrupted - */ - get: operations["get_control_data_api_v1_controls__control_id__data_get"]; - /** - * Update control configuration data - * @description Update the configuration data for a control. - * - * This replaces the entire data payload. The data is validated against - * the ControlDefinition schema. - * - * Args: - * control_id: ID of the control - * request: New control data (replaces existing) - * db: Database session (injected) - * - * Returns: - * SetControlDataResponse with success flag - * - * Raises: - * HTTPException 404: Control not found - * HTTPException 500: Database error during update - */ - put: operations["set_control_data_api_v1_controls__control_id__data_put"]; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/evaluation": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Analyze content safety - * @description Analyze content for safety and control violations. - * - * Runs all controls assigned to the agent via policy through the - * evaluation engine. Controls are evaluated in parallel with - * cancel-on-deny for efficiency. - * - * Custom evaluators must be deployed as Evaluator classes - * with the engine. Their schemas are registered via initAgent. - * - * Optionally accepts X-Trace-Id and X-Span-Id headers for - * OpenTelemetry-compatible distributed tracing. - */ - post: operations["evaluate_api_v1_evaluation_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/evaluator-configs": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** List evaluator configs */ - get: operations["list_evaluator_configs_api_v1_evaluator_configs_get"]; - put?: never; - /** Create evaluator config */ - post: operations["create_evaluator_config_api_v1_evaluator_configs_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/evaluator-configs/{config_id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get evaluator config */ - get: operations["get_evaluator_config_api_v1_evaluator_configs__config_id__get"]; - /** Update evaluator config */ - put: operations["update_evaluator_config_api_v1_evaluator_configs__config_id__put"]; - post?: never; - /** Delete evaluator config */ - delete: operations["delete_evaluator_config_api_v1_evaluator_configs__config_id__delete"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/evaluators": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List available evaluators - * @description List all available evaluators. - * - * Returns metadata and JSON Schema for each built-in evaluator. - * - * Built-in evaluators: - * - **regex**: Regular expression pattern matching - * - **list**: List-based value matching with flexible logic - * - **json**: JSON validation with schema, types, constraints - * - **sql**: SQL query validation - * - * Custom evaluators are registered per-agent via initAgent. - * Use GET /agents/{agent_name}/evaluators to list agent-specific schemas. - */ - get: operations["get_evaluators_api_v1_evaluators_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/observability/events": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Ingest Events - * @description Ingest batched control execution events. - * - * Events are stored directly to the database with ~5-20ms latency. - * - * Args: - * request: Batch of events to ingest - * ingestor: Event ingestor (injected) - * - * Returns: - * BatchEventsResponse with counts of received/processed/dropped - */ - post: operations["ingest_events_api_v1_observability_events_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/observability/events/query": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Query Events - * @description Query raw control execution events. - * - * Supports filtering by: - * - trace_id: Get all events for a request - * - span_id: Get all events for a function call - * - control_execution_id: Get a specific event - * - agent_name: Filter by agent - * - control_ids: Filter by controls - * - actions: Filter by actions (allow, deny, warn, log) - * - matched: Filter by matched status - * - check_stages: Filter by check stage (pre, post) - * - applies_to: Filter by call type (llm_call, tool_call) - * - start_time/end_time: Filter by time range - * - * Results are paginated with limit/offset. - * - * Args: - * request: Query parameters - * store: Event store (injected) - * - * Returns: - * EventQueryResponse with matching events and pagination info - */ - post: operations["query_events_api_v1_observability_events_query_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/observability/stats": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Stats - * @description Get agent-level aggregated statistics. - * - * Returns totals across all controls plus per-control breakdown. - * Use /stats/controls/{control_id} for single control stats. - * - * Args: - * agent_name: Agent to get stats for - * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) - * include_timeseries: Include time-series data points for trend visualization - * store: Event store (injected) - * - * Returns: - * StatsResponse with agent-level totals and per-control breakdown - */ - get: operations["get_stats_api_v1_observability_stats_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/observability/stats/controls/{control_id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Control Stats - * @description Get statistics for a single control. - * - * Returns stats for the specified control with optional time-series. - * - * Args: - * control_id: Control ID to get stats for - * agent_name: Agent to get stats for - * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) - * include_timeseries: Include time-series data points for trend visualization - * store: Event store (injected) - * - * Returns: - * ControlStatsResponse with control stats and optional timeseries - */ - get: operations["get_control_stats_api_v1_observability_stats_controls__control_id__get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/observability/status": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get Status - * @description Get observability system status. - * - * Returns basic health information. - */ - get: operations["get_status_api_v1_observability_status_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/policies": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - /** - * Create a new policy - * @description Create a new empty policy with a unique name. - * - * Policies contain controls and can be assigned to agents. - * A newly created policy has no controls until they are explicitly added. - * - * Args: - * request: Policy creation request with unique name - * db: Database session (injected) - * - * Returns: - * CreatePolicyResponse with the new policy's ID - * - * Raises: - * HTTPException 409: Policy with this name already exists - * HTTPException 500: Database error during creation - */ - put: operations["create_policy_api_v1_policies_put"]; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/policies/{policy_id}/controls": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List policy's controls - * @description List all controls associated with a policy. - * - * Args: - * policy_id: ID of the policy - * db: Database session (injected) - * - * Returns: - * GetPolicyControlsResponse with list of control IDs - * - * Raises: - * HTTPException 404: Policy not found - */ - get: operations["list_policy_controls_api_v1_policies__policy_id__controls_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/v1/policies/{policy_id}/controls/{control_id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Add control to policy - * @description Associate a control with a policy. - * - * This operation is idempotent - adding the same control multiple times has no effect. - * Agents with this policy will immediately see the added control. - * - * Args: - * policy_id: ID of the policy - * control_id: ID of the control to add - * db: Database session (injected) - * - * Returns: - * AssocResponse with success flag - * - * Raises: - * HTTPException 404: Policy or control not found - * HTTPException 500: Database error - */ - post: operations["add_control_to_policy_api_v1_policies__policy_id__controls__control_id__post"]; - /** - * Remove control from policy - * @description Remove a control from a policy. - * - * This operation is idempotent - removing a non-associated control has no effect. - * Agents with this policy will immediately lose the removed control. - * - * Args: - * policy_id: ID of the policy - * control_id: ID of the control to remove - * db: Database session (injected) - * - * Returns: - * AssocResponse with success flag - * - * Raises: - * HTTPException 404: Policy or control not found - * HTTPException 500: Database error - */ - delete: operations["remove_control_from_policy_api_v1_policies__policy_id__controls__control_id__delete"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/health": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Health check - * @description Check if the server is running and responsive. - * - * This endpoint does not check database connectivity. - * - * Returns: - * HealthResponse with status and version - */ - get: operations["health_check_health_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + '/api/v1/agents': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; + /** + * List all agents + * @description List all registered agents with cursor-based pagination. + * + * Returns a summary of each agent including identifier, policy assignment, + * and counts of registered steps and evaluators. + * + * Args: + * cursor: Optional cursor for pagination (last agent name from previous page) + * limit: Pagination limit (default 20, max 100) + * name: Optional name filter (case-insensitive partial match) + * db: Database session (injected) + * + * Returns: + * ListAgentsResponse with agent summaries and pagination info + */ + get: operations['list_agents_api_v1_agents_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/agents/initAgent': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Initialize or update an agent + * @description Register a new agent or update an existing agent's steps and metadata. + * + * This endpoint is idempotent: + * - If the agent name doesn't exist, creates a new agent + * - If the agent name exists, updates registration data in place + * + * conflict_mode controls registration conflict handling: + * - strict (default): preserve compatibility checks and conflict errors + * - overwrite: latest init payload replaces steps/evaluators and returns change summary + * + * Args: + * request: Agent metadata and step schemas + * db: Database session (injected) + * + * Returns: + * InitAgentResponse with created flag and active controls (if policy assigned) + */ + post: operations['init_agent_api_v1_agents_initAgent_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/agents/{agent_name}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get agent details + * @description Retrieve agent metadata and all registered steps. + * + * Returns the latest version of each step (deduplicated by type+name). + * + * Args: + * agent_name: Agent identifier + * db: Database session (injected) + * + * Returns: + * GetAgentResponse with agent metadata and step list + * + * Raises: + * HTTPException 404: Agent not found + * HTTPException 422: Agent data is corrupted + */ + get: operations['get_agent_api_v1_agents__agent_name__get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * Modify agent (remove steps/evaluators) + * @description Remove steps and/or evaluators from an agent. + * + * This is the complement to initAgent which only adds items. + * Removals are idempotent - attempting to remove non-existent items is not an error. + * + * Args: + * agent_name: Agent identifier + * request: Lists of step/evaluator identifiers to remove + * db: Database session (injected) + * + * Returns: + * PatchAgentResponse with lists of actually removed items + * + * Raises: + * HTTPException 404: Agent not found + * HTTPException 500: Database error during update + */ + patch: operations['patch_agent_api_v1_agents__agent_name__patch']; + trace?: never; + }; + '/api/v1/agents/{agent_name}/controls': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List agent's active controls + * @description List all protection controls active for an agent. + * + * Controls are inherited from the agent's assigned policy. + * Returns an empty list if the agent has no policy. + * + * Args: + * agent_name: Agent identifier + * db: Database session (injected) + * + * Returns: + * AgentControlsResponse with list of controls (empty if no policy) + * + * Raises: + * HTTPException 404: Agent not found + */ + get: operations['list_agent_controls_api_v1_agents__agent_name__controls_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/agents/{agent_name}/evaluators': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List agent's registered evaluator schemas + * @description List all evaluator schemas registered with an agent. + * + * Evaluator schemas are registered via initAgent and used for: + * - Config validation when creating Controls + * - UI to display available config options + * + * Args: + * agent_name: Agent identifier + * cursor: Optional cursor for pagination (name of last evaluator from previous page) + * limit: Pagination limit (default 20, max 100) + * db: Database session (injected) + * + * Returns: + * ListEvaluatorsResponse with evaluator schemas and pagination + * + * Raises: + * HTTPException 404: Agent not found + */ + get: operations['list_agent_evaluators_api_v1_agents__agent_name__evaluators_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/agents/{agent_name}/evaluators/{evaluator_name}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get specific evaluator schema + * @description Get a specific evaluator schema registered with an agent. + * + * Args: + * agent_name: Agent identifier + * evaluator_name: Name of the evaluator + * db: Database session (injected) + * + * Returns: + * EvaluatorSchemaItem with schema details + * + * Raises: + * HTTPException 404: Agent or evaluator not found + */ + get: operations['get_agent_evaluator_api_v1_agents__agent_name__evaluators__evaluator_name__get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/agents/{agent_name}/policy': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get agent's assigned policy + * @description Retrieve the policy currently assigned to an agent. + * + * Args: + * agent_name: Agent identifier + * db: Database session (injected) + * + * Returns: + * GetPolicyResponse with policy ID + * + * Raises: + * HTTPException 404: Agent not found or agent has no policy assigned + */ + get: operations['get_agent_policy_api_v1_agents__agent_name__policy_get']; + put?: never; + post?: never; + /** + * Remove agent's policy assignment + * @description Remove the policy assignment from an agent. + * + * The agent will no longer have any protection controls active. + * + * Args: + * agent_name: Agent identifier + * db: Database session (injected) + * + * Returns: + * DeletePolicyResponse with success flag + * + * Raises: + * HTTPException 404: Agent not found or agent has no policy assigned + * HTTPException 500: Database error during removal + */ + delete: operations['delete_agent_policy_api_v1_agents__agent_name__policy_delete']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/agents/{agent_name}/policy/{policy_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Assign policy to agent + * @description Assign a policy to an agent, replacing any existing policy assignment. + * + * The agent will immediately inherit all controls from the assigned policy. + * + * Args: + * agent_name: Agent identifier + * policy_id: ID of the policy to assign + * db: Database session (injected) + * + * Returns: + * SetPolicyResponse with success flag and previous policy ID (if any) + * + * Raises: + * HTTPException 404: Agent or policy not found + * HTTPException 500: Database error during assignment + */ + post: operations['set_agent_policy_api_v1_agents__agent_name__policy__policy_id__post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/controls': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List all controls + * @description List all controls with optional filtering and cursor-based pagination. + * + * Controls are returned ordered by ID descending (newest first). + * + * Args: + * cursor: ID of the last control from the previous page (for pagination) + * limit: Maximum number of controls to return (default 20, max 100) + * name: Optional filter by name (partial, case-insensitive match) + * enabled: Optional filter by enabled status + * step_type: Optional filter by step type (built-ins: 'tool', 'llm') + * stage: Optional filter by stage ('pre' or 'post') + * execution: Optional filter by execution ('server' or 'sdk') + * tag: Optional filter by tag + * db: Database session (injected) + * + * Returns: + * ListControlsResponse with control summaries and pagination info + * + * Example: + * GET /controls?limit=10&enabled=true&step_type=tool + */ + get: operations['list_controls_api_v1_controls_get']; + /** + * Create a new control + * @description Create a new control with a unique name and empty data. + * + * Controls define protection logic and can be added to policies. + * Use the PUT /{control_id}/data endpoint to set control configuration. + * + * Args: + * request: Control creation request with unique name + * db: Database session (injected) + * + * Returns: + * CreateControlResponse with the new control's ID + * + * Raises: + * HTTPException 409: Control with this name already exists + * HTTPException 500: Database error during creation + */ + put: operations['create_control_api_v1_controls_put']; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/controls/validate': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Validate control configuration + * @description Validate control configuration data without saving it. + * + * Args: + * request: Control configuration data to validate + * db: Database session (injected) + * + * Returns: + * ValidateControlDataResponse with success=True if valid + */ + post: operations['validate_control_data_api_v1_controls_validate_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/controls/{control_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get control details + * @description Retrieve a control by ID including its name and configuration data. + * + * Args: + * control_id: ID of the control + * db: Database session (injected) + * + * Returns: + * GetControlResponse with control id, name, and data + * + * Raises: + * HTTPException 404: Control not found + */ + get: operations['get_control_api_v1_controls__control_id__get']; + put?: never; + post?: never; + /** + * Delete a control + * @description Delete a control by ID. + * + * By default, deletion fails if the control is associated with any policy. + * Use force=true to automatically dissociate and delete. + * + * Args: + * control_id: ID of the control to delete + * force: If true, remove associations before deleting + * db: Database session (injected) + * + * Returns: + * DeleteControlResponse with success flag and list of dissociated policies + * + * Raises: + * HTTPException 404: Control not found + * HTTPException 409: Control is in use (and force=false) + * HTTPException 500: Database error during deletion + */ + delete: operations['delete_control_api_v1_controls__control_id__delete']; + options?: never; + head?: never; + /** + * Update control metadata + * @description Update control metadata (name and/or enabled status). + * + * This endpoint allows partial updates: + * - To rename: provide 'name' field + * - To enable/disable: provide 'enabled' field (updates the control's data) + * + * Args: + * control_id: ID of the control to update + * request: Fields to update (name, enabled) + * db: Database session (injected) + * + * Returns: + * PatchControlResponse with current control state + * + * Raises: + * HTTPException 404: Control not found + * HTTPException 409: New name conflicts with existing control + * HTTPException 422: Cannot update enabled status (control has no data configured) + * HTTPException 500: Database error during update + */ + patch: operations['patch_control_api_v1_controls__control_id__patch']; + trace?: never; + }; + '/api/v1/controls/{control_id}/data': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get control configuration data + * @description Retrieve the configuration data for a control. + * + * Control data is a JSONB field that must follow the ControlDefinition schema. + * + * Args: + * control_id: ID of the control + * db: Database session (injected) + * + * Returns: + * GetControlDataResponse with validated ControlDefinition + * + * Raises: + * HTTPException 404: Control not found + * HTTPException 422: Control data is corrupted + */ + get: operations['get_control_data_api_v1_controls__control_id__data_get']; + /** + * Update control configuration data + * @description Update the configuration data for a control. + * + * This replaces the entire data payload. The data is validated against + * the ControlDefinition schema. + * + * Args: + * control_id: ID of the control + * request: New control data (replaces existing) + * db: Database session (injected) + * + * Returns: + * SetControlDataResponse with success flag + * + * Raises: + * HTTPException 404: Control not found + * HTTPException 500: Database error during update + */ + put: operations['set_control_data_api_v1_controls__control_id__data_put']; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/evaluation': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Analyze content safety + * @description Analyze content for safety and control violations. + * + * Runs all controls assigned to the agent via policy through the + * evaluation engine. Controls are evaluated in parallel with + * cancel-on-deny for efficiency. + * + * Custom evaluators must be deployed as Evaluator classes + * with the engine. Their schemas are registered via initAgent. + * + * Optionally accepts X-Trace-Id and X-Span-Id headers for + * OpenTelemetry-compatible distributed tracing. + */ + post: operations['evaluate_api_v1_evaluation_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/evaluator-configs': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List evaluator configs */ + get: operations['list_evaluator_configs_api_v1_evaluator_configs_get']; + put?: never; + /** Create evaluator config */ + post: operations['create_evaluator_config_api_v1_evaluator_configs_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/evaluator-configs/{config_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get evaluator config */ + get: operations['get_evaluator_config_api_v1_evaluator_configs__config_id__get']; + /** Update evaluator config */ + put: operations['update_evaluator_config_api_v1_evaluator_configs__config_id__put']; + post?: never; + /** Delete evaluator config */ + delete: operations['delete_evaluator_config_api_v1_evaluator_configs__config_id__delete']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/evaluators': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List available evaluators + * @description List all available evaluators. + * + * Returns metadata and JSON Schema for each built-in evaluator. + * + * Built-in evaluators: + * - **regex**: Regular expression pattern matching + * - **list**: List-based value matching with flexible logic + * - **json**: JSON validation with schema, types, constraints + * - **sql**: SQL query validation + * + * Custom evaluators are registered per-agent via initAgent. + * Use GET /agents/{agent_name}/evaluators to list agent-specific schemas. + */ + get: operations['get_evaluators_api_v1_evaluators_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/observability/events': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Ingest Events + * @description Ingest batched control execution events. + * + * Events are stored directly to the database with ~5-20ms latency. + * + * Args: + * request: Batch of events to ingest + * ingestor: Event ingestor (injected) + * + * Returns: + * BatchEventsResponse with counts of received/processed/dropped + */ + post: operations['ingest_events_api_v1_observability_events_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/observability/events/query': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Query Events + * @description Query raw control execution events. + * + * Supports filtering by: + * - trace_id: Get all events for a request + * - span_id: Get all events for a function call + * - control_execution_id: Get a specific event + * - agent_name: Filter by agent + * - control_ids: Filter by controls + * - actions: Filter by actions (allow, deny, warn, log) + * - matched: Filter by matched status + * - check_stages: Filter by check stage (pre, post) + * - applies_to: Filter by call type (llm_call, tool_call) + * - start_time/end_time: Filter by time range + * + * Results are paginated with limit/offset. + * + * Args: + * request: Query parameters + * store: Event store (injected) + * + * Returns: + * EventQueryResponse with matching events and pagination info + */ + post: operations['query_events_api_v1_observability_events_query_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/observability/stats': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Stats + * @description Get agent-level aggregated statistics. + * + * Returns totals across all controls plus per-control breakdown. + * Use /stats/controls/{control_id} for single control stats. + * + * Args: + * agent_name: Agent to get stats for + * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) + * include_timeseries: Include time-series data points for trend visualization + * store: Event store (injected) + * + * Returns: + * StatsResponse with agent-level totals and per-control breakdown + */ + get: operations['get_stats_api_v1_observability_stats_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/observability/stats/controls/{control_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Control Stats + * @description Get statistics for a single control. + * + * Returns stats for the specified control with optional time-series. + * + * Args: + * control_id: Control ID to get stats for + * agent_name: Agent to get stats for + * time_range: Time range (1m, 5m, 15m, 1h, 24h, 7d, 30d, 180d, 365d) + * include_timeseries: Include time-series data points for trend visualization + * store: Event store (injected) + * + * Returns: + * ControlStatsResponse with control stats and optional timeseries + */ + get: operations['get_control_stats_api_v1_observability_stats_controls__control_id__get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/observability/status': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Status + * @description Get observability system status. + * + * Returns basic health information. + */ + get: operations['get_status_api_v1_observability_status_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/policies': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Create a new policy + * @description Create a new empty policy with a unique name. + * + * Policies contain controls and can be assigned to agents. + * A newly created policy has no controls until they are explicitly added. + * + * Args: + * request: Policy creation request with unique name + * db: Database session (injected) + * + * Returns: + * CreatePolicyResponse with the new policy's ID + * + * Raises: + * HTTPException 409: Policy with this name already exists + * HTTPException 500: Database error during creation + */ + put: operations['create_policy_api_v1_policies_put']; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/policies/{policy_id}/controls': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List policy's controls + * @description List all controls associated with a policy. + * + * Args: + * policy_id: ID of the policy + * db: Database session (injected) + * + * Returns: + * GetPolicyControlsResponse with list of control IDs + * + * Raises: + * HTTPException 404: Policy not found + */ + get: operations['list_policy_controls_api_v1_policies__policy_id__controls_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/policies/{policy_id}/controls/{control_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Add control to policy + * @description Associate a control with a policy. + * + * This operation is idempotent - adding the same control multiple times has no effect. + * Agents with this policy will immediately see the added control. + * + * Args: + * policy_id: ID of the policy + * control_id: ID of the control to add + * db: Database session (injected) + * + * Returns: + * AssocResponse with success flag + * + * Raises: + * HTTPException 404: Policy or control not found + * HTTPException 500: Database error + */ + post: operations['add_control_to_policy_api_v1_policies__policy_id__controls__control_id__post']; + /** + * Remove control from policy + * @description Remove a control from a policy. + * + * This operation is idempotent - removing a non-associated control has no effect. + * Agents with this policy will immediately lose the removed control. + * + * Args: + * policy_id: ID of the policy + * control_id: ID of the control to remove + * db: Database session (injected) + * + * Returns: + * AssocResponse with success flag + * + * Raises: + * HTTPException 404: Policy or control not found + * HTTPException 500: Database error + */ + delete: operations['remove_control_from_policy_api_v1_policies__policy_id__controls__control_id__delete']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/health': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Health check + * @description Check if the server is running and responsive. + * + * This endpoint does not check database connectivity. + * + * Returns: + * HealthResponse with status and version + */ + get: operations['health_check_health_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { - schemas: { - /** - * Agent - * @description Agent metadata for registration and tracking. - * - * An agent represents an AI system that can be protected and monitored. - * Each agent has a unique immutable name and can have multiple steps registered with it. - * @example { - * "agent_description": "Handles customer inquiries and support tickets", - * "agent_metadata": { - * "environment": "production", - * "team": "support" - * }, - * "agent_name": "customer-service-bot", - * "agent_version": "1.0.0" - * } - */ - Agent: { - /** - * Agent Created At - * @description ISO 8601 timestamp when agent was created - */ - agent_created_at?: string | null; - /** - * Agent Description - * @description Optional description of the agent's purpose - */ - agent_description?: string | null; - /** - * Agent Metadata - * @description Free-form metadata dictionary for custom properties - */ - agent_metadata?: { - [key: string]: unknown; - } | null; - /** - * Agent Name - * @description Unique immutable identifier for the agent - */ - agent_name: string; - /** - * Agent Updated At - * @description ISO 8601 timestamp when agent was last updated - */ - agent_updated_at?: string | null; - /** - * Agent Version - * @description Semantic version string (e.g. '1.0.0') - */ - agent_version?: string | null; - }; - /** AgentControlsResponse */ - AgentControlsResponse: { - /** - * Controls - * @description List of controls associated with the agent via its policy - */ - controls: components["schemas"]["Control"][]; - }; - /** - * AgentRef - * @description Reference to an agent (for listing which agents use a control). - */ - AgentRef: { - /** - * Agent Name - * @description Agent identifier - */ - agent_name: string; - }; - /** - * AgentSummary - * @description Summary of an agent for list responses. - */ - AgentSummary: { - /** - * Active Controls Count - * @description Number of active controls from agent's policy - * @default 0 - */ - active_controls_count: number; - /** - * Agent Name - * @description Unique identifier of the agent - */ - agent_name: string; - /** - * Created At - * @description ISO 8601 timestamp when agent was created - */ - created_at?: string | null; - /** - * Evaluator Count - * @description Number of evaluators registered with the agent - * @default 0 - */ - evaluator_count: number; - /** - * Policy Id - * @description ID of assigned policy, if any - */ - policy_id?: number | null; - /** - * Step Count - * @description Number of steps registered with the agent - * @default 0 - */ - step_count: number; - }; - /** AssocResponse */ - AssocResponse: { - /** - * Success - * @description Whether the association change succeeded - */ - success: boolean; - }; - /** - * BatchEventsRequest - * @description Request model for batch event ingestion. - * - * SDKs batch events and send them to the server periodically. - * This reduces HTTP overhead significantly (100x reduction). - * - * Attributes: - * events: List of control execution events to ingest - * @example { - * "events": [ - * { - * "action": "deny", - * "agent_name": "my-agent", - * "applies_to": "llm_call", - * "check_stage": "pre", - * "confidence": 0.95, - * "control_id": 123, - * "control_name": "sql-injection-check", - * "matched": true, - * "span_id": "00f067aa0ba902b7", - * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" - * } - * ] - * } - */ - BatchEventsRequest: { - /** - * Events - * @description List of events to ingest - */ - events: components["schemas"]["ControlExecutionEvent"][]; - }; - /** - * BatchEventsResponse - * @description Response model for batch event ingestion. - * - * Attributes: - * received: Number of events received - * enqueued: Number of events successfully enqueued - * dropped: Number of events dropped (queue full) - * status: Overall status ('queued', 'partial', 'failed') - */ - BatchEventsResponse: { - /** - * Dropped - * @description Number of events dropped - */ - dropped: number; - /** - * Enqueued - * @description Number of events enqueued - */ - enqueued: number; - /** - * Received - * @description Number of events received - */ - received: number; - /** - * Status - * @description Overall ingestion status - * @enum {string} - */ - status: "queued" | "partial" | "failed"; - }; - /** - * ConflictMode - * @description Conflict handling mode for initAgent registration updates. - * - * STRICT preserves compatibility checks and raises conflicts on incompatible changes. - * OVERWRITE applies latest-init-wins replacement for steps and evaluators. - * @enum {string} - */ - ConflictMode: "strict" | "overwrite"; - /** - * Control - * @description A control with identity and configuration. - * - * Note: Only fully-configured controls (with valid ControlDefinition) - * are returned from API endpoints. Unconfigured controls are filtered out. - */ - Control: { - control: components["schemas"]["ControlDefinition"]; - /** Id */ - id: number; - /** Name */ - name: string; - }; - /** - * ControlAction - * @description What to do when control matches. - */ - ControlAction: { - /** - * Decision - * @description Action to take when control is triggered - * @enum {string} - */ - decision: "allow" | "deny" | "warn" | "log"; - }; - /** - * ControlDefinition - * @description A control definition to evaluate agent interactions. - * - * This model contains only the logic and configuration. - * Identity fields (id, name) are managed by the database. - * @example { - * "action": { - * "decision": "deny" - * }, - * "description": "Block outputs containing US Social Security Numbers", - * "enabled": true, - * "evaluator": { - * "config": { - * "pattern": "\\b\\d{3}-\\d{2}-\\d{4}\\b" - * }, - * "name": "regex" - * }, - * "execution": "server", - * "scope": { - * "stages": [ - * "post" - * ], - * "step_types": [ - * "llm" - * ] - * }, - * "selector": { - * "path": "output" - * }, - * "tags": [ - * "pii", - * "compliance" - * ] - * } - */ - ControlDefinition: { - /** @description What action to take when control matches */ - action: components["schemas"]["ControlAction"]; - /** - * Description - * @description Detailed description of the control - */ - description?: string | null; - /** - * Enabled - * @description Whether this control is active - * @default true - */ - enabled: boolean; - /** @description How to evaluate the selected data */ - evaluator: components["schemas"]["EvaluatorSpec"]; - /** - * Execution - * @description Where this control executes - * @enum {string} - */ - execution: "server" | "sdk"; - /** @description Which steps and stages this control applies to */ - scope?: components["schemas"]["ControlScope"]; - /** @description What data to select from the payload */ - selector: components["schemas"]["ControlSelector"]; - /** - * Tags - * @description Tags for categorization - */ - tags?: string[]; - }; - /** - * ControlExecutionEvent - * @description Represents a single control execution event. - * - * This is the core observability data model, capturing: - * - Identity: control_execution_id, trace_id, span_id (OpenTelemetry-compatible) - * - Context: agent, control, check stage, applies to - * - Result: action taken, whether matched, confidence score - * - Timing: when it happened, how long it took - * - Optional details: evaluator name, selector path, errors, metadata - * - * Attributes: - * control_execution_id: Unique ID for this specific control execution - * trace_id: OpenTelemetry-compatible trace ID (128-bit hex, 32 chars) - * span_id: OpenTelemetry-compatible span ID (64-bit hex, 16 chars) - * agent_name: Identifier of the agent that executed the control - * control_id: Database ID of the control - * control_name: Name of the control (denormalized for queries) - * check_stage: "pre" (before execution) or "post" (after execution) - * applies_to: "llm_call" or "tool_call" - * action: The action taken (allow, deny, warn, log) - * matched: Whether the control evaluator matched - * confidence: Confidence score from the evaluator (0.0-1.0) - * timestamp: When the control was executed (UTC) - * execution_duration_ms: How long the control evaluation took - * evaluator_name: Name of the evaluator used - * selector_path: The selector path used to extract data - * error_message: Error message if evaluation failed - * metadata: Additional metadata for extensibility - * @example { - * "action": "deny", - * "agent_name": "my-agent", - * "applies_to": "llm_call", - * "check_stage": "pre", - * "confidence": 0.95, - * "control_execution_id": "550e8400-e29b-41d4-a716-446655440000", - * "control_id": 123, - * "control_name": "sql-injection-check", - * "evaluator_name": "regex", - * "execution_duration_ms": 15.3, - * "matched": true, - * "selector_path": "input", - * "span_id": "00f067aa0ba902b7", - * "timestamp": "2025-01-09T10:30:00Z", - * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" - * } - */ - ControlExecutionEvent: { - /** - * Action - * @description Action taken by the control - * @enum {string} - */ - action: "allow" | "deny" | "warn" | "log"; - /** - * Agent Name - * @description Identifier of the agent - */ - agent_name: string; - /** - * Applies To - * @description Type of call: 'llm_call' or 'tool_call' - * @enum {string} - */ - applies_to: "llm_call" | "tool_call"; - /** - * Check Stage - * @description Check stage: 'pre' or 'post' - * @enum {string} - */ - check_stage: "pre" | "post"; - /** - * Confidence - * @description Confidence score (0.0 to 1.0) - */ - confidence: number; - /** - * Control Execution Id - * @description Unique ID for this control execution - */ - control_execution_id?: string; - /** - * Control Id - * @description Database ID of the control - */ - control_id: number; - /** - * Control Name - * @description Name of the control (denormalized) - */ - control_name: string; - /** - * Error Message - * @description Error message if evaluation failed - */ - error_message?: string | null; - /** - * Evaluator Name - * @description Name of the evaluator used - */ - evaluator_name?: string | null; - /** - * Execution Duration Ms - * @description Execution duration in milliseconds - */ - execution_duration_ms?: number | null; - /** - * Matched - * @description Whether the evaluator matched (True) or not (False) - */ - matched: boolean; - /** - * Metadata - * @description Additional metadata - */ - metadata?: { - [key: string]: unknown; - }; - /** - * Selector Path - * @description Selector path used to extract data - */ - selector_path?: string | null; - /** - * Span Id - * @description Span ID for distributed tracing (SDK generates OTEL-compatible 16-char hex) - */ - span_id: string; - /** - * Timestamp - * Format: date-time - * @description When the control was executed (UTC) - */ - timestamp?: string; - /** - * Trace Id - * @description Trace ID for distributed tracing (SDK generates OTEL-compatible 32-char hex) - */ - trace_id: string; - }; - /** - * ControlMatch - * @description Represents a control evaluation result (match, non-match, or error). - */ - ControlMatch: { - /** - * Action - * @description Action configured for this control - * @enum {string} - */ - action: "allow" | "deny" | "warn" | "log"; - /** - * Control Execution Id - * @description Unique ID for this control execution (generated by engine) - */ - control_execution_id?: string; - /** - * Control Id - * @description Database ID of the control - */ - control_id: number; - /** - * Control Name - * @description Name of the control - */ - control_name: string; - /** @description Evaluator result (confidence, message, metadata) */ - result: components["schemas"]["EvaluatorResult"]; - }; - /** - * ControlScope - * @description Defines when a control applies to a Step. - * @example { - * "stages": [ - * "pre" - * ], - * "step_types": [ - * "tool" - * ] - * } - * @example { - * "step_names": [ - * "search_db", - * "fetch_user" - * ] - * } - * @example { - * "step_name_regex": "^db_.*" - * } - * @example { - * "stages": [ - * "post" - * ], - * "step_types": [ - * "llm" - * ] - * } - */ - ControlScope: { - /** - * Stages - * @description Evaluation stages this control applies to - */ - stages?: ("pre" | "post")[] | null; - /** - * Step Name Regex - * @description RE2 pattern matched with search() against step name - */ - step_name_regex?: string | null; - /** - * Step Names - * @description Exact step names this control applies to - */ - step_names?: string[] | null; - /** - * Step Types - * @description Step types this control applies to (omit to apply to all types). Built-in types are 'tool' and 'llm'. - */ - step_types?: string[] | null; - }; - /** - * ControlSelector - * @description Selects data from a Step payload. - * - * - path: which slice of the Step to feed into the evaluator. Optional, defaults to "*" - * meaning the entire Step object. - * @example { - * "path": "output" - * } - * @example { - * "path": "context.user_id" - * } - * @example { - * "path": "input" - * } - * @example { - * "path": "*" - * } - * @example { - * "path": "name" - * } - * @example { - * "path": "output" - * } - */ - ControlSelector: { - /** - * Path - * @description Path to data using dot notation. Examples: 'input', 'output', 'context.user_id', 'name', 'type', '*' - * @default * - */ - path: string | null; - }; - /** - * ControlStats - * @description Aggregated statistics for a single control. - * - * Attributes: - * control_id: Database ID of the control - * control_name: Name of the control - * execution_count: Total number of executions - * match_count: Number of times the control matched - * non_match_count: Number of times the control did not match - * allow_count: Number of allow actions - * deny_count: Number of deny actions - * warn_count: Number of warn actions - * log_count: Number of log actions - * error_count: Number of errors during evaluation - * avg_confidence: Average confidence score - * avg_duration_ms: Average execution duration in milliseconds - */ - ControlStats: { - /** - * Allow Count - * @description Allow actions - */ - allow_count: number; - /** - * Avg Confidence - * @description Average confidence - */ - avg_confidence: number; - /** - * Avg Duration Ms - * @description Average duration (ms) - */ - avg_duration_ms?: number | null; - /** - * Control Id - * @description Control ID - */ - control_id: number; - /** - * Control Name - * @description Control name - */ - control_name: string; - /** - * Deny Count - * @description Deny actions - */ - deny_count: number; - /** - * Error Count - * @description Evaluation errors - */ - error_count: number; - /** - * Execution Count - * @description Total executions - */ - execution_count: number; - /** - * Log Count - * @description Log actions - */ - log_count: number; - /** - * Match Count - * @description Total matches - */ - match_count: number; - /** - * Non Match Count - * @description Total non-matches - */ - non_match_count: number; - /** - * Warn Count - * @description Warn actions - */ - warn_count: number; - }; - /** - * ControlStatsResponse - * @description Response model for control-level statistics. - * - * Contains stats for a single control (with optional timeseries). - * - * Attributes: - * agent_name: Agent identifier - * time_range: Time range used - * control_id: Control ID - * control_name: Control name - * stats: Control statistics (includes timeseries when requested) - */ - ControlStatsResponse: { - /** - * Agent Name - * @description Agent identifier - */ - agent_name: string; - /** - * Control Id - * @description Control ID - */ - control_id: number; - /** - * Control Name - * @description Control name - */ - control_name: string; - /** @description Control statistics */ - stats: components["schemas"]["StatsTotals"]; - /** - * Time Range - * @description Time range used - */ - time_range: string; - }; - /** - * ControlSummary - * @description Summary of a control for list responses. - */ - ControlSummary: { - /** - * Description - * @description Control description - */ - description?: string | null; - /** - * Enabled - * @description Whether control is enabled - * @default true - */ - enabled: boolean; - /** - * Execution - * @description 'server' or 'sdk' - */ - execution?: string | null; - /** - * Id - * @description Control ID - */ - id: number; - /** - * Name - * @description Control name - */ - name: string; - /** - * Stages - * @description Evaluation stages in scope - */ - stages?: string[] | null; - /** - * Step Types - * @description Step types in scope - */ - step_types?: string[] | null; - /** - * Tags - * @description Control tags - */ - tags?: string[]; - /** @description Agent using this control */ - used_by_agent?: components["schemas"]["AgentRef"] | null; - }; - /** CreateControlRequest */ - CreateControlRequest: { - /** - * Name - * @description Unique control name (letters, numbers, hyphens, underscores) - */ - name: string; - }; - /** CreateControlResponse */ - CreateControlResponse: { - /** - * Control Id - * @description Identifier of the created control - */ - control_id: number; - }; - /** - * CreateEvaluatorConfigRequest - * @description Request to create an evaluator config template. - */ - CreateEvaluatorConfigRequest: { - /** - * Config - * @description Evaluator-specific configuration - */ - config: { - [key: string]: unknown; - }; - /** - * Description - * @description Optional description - */ - description?: string | null; - /** - * Evaluator - * @description Evaluator name (built-in or custom) - */ - evaluator: string; - /** - * Name - * @description Unique evaluator config name (letters, numbers, hyphens, underscores) - */ - name: string; - }; - /** CreatePolicyRequest */ - CreatePolicyRequest: { - /** - * Name - * @description Unique policy name (letters, numbers, hyphens, underscores) - */ - name: string; - }; - /** CreatePolicyResponse */ - CreatePolicyResponse: { - /** - * Policy Id - * @description Identifier of the created policy - */ - policy_id: number; - }; - /** - * DeleteControlResponse - * @description Response for deleting a control. - */ - DeleteControlResponse: { - /** - * Dissociated From - * @description Policy IDs the control was removed from before deletion - */ - dissociated_from?: number[]; - /** - * Success - * @description Whether the control was deleted - */ - success: boolean; - }; - /** - * DeleteEvaluatorConfigResponse - * @description Response for deleting an evaluator config. - */ - DeleteEvaluatorConfigResponse: { - /** - * Success - * @description Whether the evaluator config was deleted - */ - success: boolean; - }; - /** DeletePolicyResponse */ - DeletePolicyResponse: { - /** - * Success - * @description Whether the policy was successfully removed - */ - success: boolean; - }; - /** - * EvaluationRequest - * @description Request model for evaluation analysis. - * - * Used to analyze agent interactions for safety violations, - * policy compliance, and control rules. - * - * Attributes: - * agent_name: Unique identifier of the agent making the request - * step: Step payload for evaluation - * stage: 'pre' (before execution) or 'post' (after execution) - * @example { - * "agent_name": "customer-service-bot", - * "stage": "pre", - * "step": { - * "context": { - * "session_id": "abc123", - * "user_id": "user123" - * }, - * "input": "What is the customer's credit card number?", - * "name": "support-answer", - * "type": "llm" - * } - * } - * @example { - * "agent_name": "customer-service-bot", - * "stage": "post", - * "step": { - * "context": { - * "session_id": "abc123", - * "user_id": "user123" - * }, - * "input": "What is the customer's credit card number?", - * "name": "support-answer", - * "output": "I cannot share sensitive payment information.", - * "type": "llm" - * } - * } - * @example { - * "agent_name": "customer-service-bot", - * "stage": "pre", - * "step": { - * "context": { - * "user_id": "user123" - * }, - * "input": { - * "query": "SELECT * FROM users" - * }, - * "name": "search_database", - * "type": "tool" - * } - * } - * @example { - * "agent_name": "customer-service-bot", - * "stage": "post", - * "step": { - * "context": { - * "user_id": "user123" - * }, - * "input": { - * "query": "SELECT * FROM users" - * }, - * "name": "search_database", - * "output": { - * "results": [] - * }, - * "type": "tool" - * } - * } - */ - EvaluationRequest: { - /** - * Agent Name - * @description Identifier of the agent making the evaluation request - */ - agent_name: string; - /** - * Stage - * @description Evaluation stage: 'pre' or 'post' - * @enum {string} - */ - stage: "pre" | "post"; - /** @description Agent step payload to evaluate */ - step: components["schemas"]["Step"]; - }; - /** - * EvaluationResponse - * @description Response model from evaluation analysis (server-side). - * - * This is what the server returns. The SDK may transform this - * into an EvaluationResult for client convenience. - * - * Attributes: - * is_safe: Whether the content is considered safe - * confidence: Confidence score between 0.0 and 1.0 - * reason: Optional explanation for the decision - * matches: List of controls that matched/triggered (if any) - * errors: List of controls that failed during evaluation (if any) - * non_matches: List of controls that were evaluated but did not match (if any) - */ - EvaluationResponse: { - /** - * Confidence - * @description Confidence score (0.0 to 1.0) - */ - confidence: number; - /** - * Errors - * @description List of controls that failed during evaluation (if any) - */ - errors?: components["schemas"]["ControlMatch"][] | null; - /** - * Is Safe - * @description Whether content is safe - */ - is_safe: boolean; - /** - * Matches - * @description List of controls that matched/triggered (if any) - */ - matches?: components["schemas"]["ControlMatch"][] | null; - /** - * Non Matches - * @description List of controls that were evaluated but did not match (if any) - */ - non_matches?: components["schemas"]["ControlMatch"][] | null; - /** - * Reason - * @description Explanation for the decision - */ - reason?: string | null; - }; - /** - * EvaluatorConfigItem - * @description Evaluator config template stored in the server. - */ - EvaluatorConfigItem: { - /** - * Config - * @description Evaluator-specific configuration - */ - config: { - [key: string]: unknown; - }; - /** - * Created At - * @description ISO 8601 created timestamp - */ - created_at?: string | null; - /** - * Description - * @description Optional description - */ - description?: string | null; - /** - * Evaluator - * @description Evaluator name (built-in or custom) - */ - evaluator: string; - /** - * Id - * @description Evaluator config ID - */ - id: number; - /** - * Name - * @description Unique evaluator config name (letters, numbers, hyphens, underscores) - */ - name: string; - /** - * Updated At - * @description ISO 8601 updated timestamp - */ - updated_at?: string | null; - }; - /** - * EvaluatorInfo - * @description Information about a registered evaluator. - */ - EvaluatorInfo: { - /** - * Config Schema - * @description JSON Schema for config - */ - config_schema: { - [key: string]: unknown; - }; - /** - * Description - * @description Evaluator description - */ - description: string; - /** - * Name - * @description Evaluator name - */ - name: string; - /** - * Requires Api Key - * @description Whether evaluator requires API key - */ - requires_api_key: boolean; - /** - * Timeout Ms - * @description Default timeout in milliseconds - */ - timeout_ms: number; - /** - * Version - * @description Evaluator version - */ - version: string; - }; - /** - * EvaluatorResult - * @description Result from a control evaluator. - * - * The `error` field indicates evaluator failures, NOT validation failures: - * - Set `error` for: evaluator crashes, timeouts, missing dependencies, external service errors - * - Do NOT set `error` for: invalid input, syntax errors, schema violations, constraint failures - * - * When `error` is set, `matched` must be False (fail-open on evaluator errors). - * When `error` is None, `matched` reflects the actual validation result. - * - * This distinction allows: - * - Clients to distinguish "data violated rules" from "evaluator is broken" - * - Observability systems to monitor evaluator health separately from validation outcomes - */ - EvaluatorResult: { - /** - * Confidence - * @description Confidence in the evaluation - */ - confidence: number; - /** - * Error - * @description Error message if evaluation failed internally. When set, matched=False is due to error, not actual evaluation. - */ - error?: string | null; - /** - * Matched - * @description Whether the pattern matched - */ - matched: boolean; - /** - * Message - * @description Explanation of the result - */ - message?: string | null; - /** - * Metadata - * @description Additional result metadata - */ - metadata?: { - [key: string]: unknown; - } | null; - }; - /** - * EvaluatorSchema - * @description Schema for a custom evaluator registered with an agent. - * - * Custom evaluators are Evaluator classes deployed with the engine. - * This schema is registered via initAgent for validation and UI purposes. - */ - EvaluatorSchema: { - /** - * Config Schema - * @description JSON Schema for evaluator config validation - */ - config_schema?: { - [key: string]: unknown; - }; - /** - * Description - * @description Optional description - */ - description?: string | null; - /** - * Name - * @description Unique evaluator name - */ - name: string; - }; - /** - * EvaluatorSchemaItem - * @description Evaluator schema summary for list response. - */ - EvaluatorSchemaItem: { - /** Config Schema */ - config_schema: { - [key: string]: unknown; - }; - /** Description */ - description: string | null; - /** Name */ - name: string; - }; - /** - * EvaluatorSpec - * @description Evaluator specification. See GET /evaluators for available evaluators and schemas. - * - * Evaluator reference formats: - * - Built-in: "regex", "list", "json", "sql" - * - External: "galileo.luna2" (requires agent-control-evaluators[galileo]) - * - Agent-scoped: "my-agent:my-evaluator" (validated in endpoint, not here) - */ - EvaluatorSpec: { - /** - * Config - * @description Evaluator-specific configuration - * @example { - * "pattern": "\\d{3}-\\d{2}-\\d{4}" - * } - * @example { - * "logic": "any", - * "values": [ - * "admin" - * ] - * } - */ - config: { - [key: string]: unknown; - }; - /** - * Name - * @description Evaluator name or agent-scoped reference (agent:evaluator) - * @example regex - * @example list - * @example my-agent:pii-detector - */ - name: string; - }; - /** - * EventQueryRequest - * @description Request model for querying raw events. - * - * Supports filtering by various criteria and pagination. - * - * Attributes: - * trace_id: Filter by trace ID (get all events for a request) - * span_id: Filter by span ID (get all events for a function call) - * control_execution_id: Filter by specific event ID - * agent_name: Filter by agent identifier - * control_ids: Filter by control IDs - * actions: Filter by actions (allow, deny, warn, log) - * matched: Filter by matched status - * check_stages: Filter by check stages (pre, post) - * applies_to: Filter by call type (llm_call, tool_call) - * start_time: Filter events after this time - * end_time: Filter events before this time - * limit: Maximum number of events to return - * offset: Offset for pagination - * @example { - * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" - * } - * @example { - * "actions": [ - * "deny", - * "warn" - * ], - * "agent_name": "my-agent", - * "limit": 50, - * "start_time": "2025-01-09T00:00:00Z" - * } - */ - EventQueryRequest: { - /** - * Actions - * @description Filter by actions - */ - actions?: ("allow" | "deny" | "warn" | "log")[] | null; - /** - * Agent Name - * @description Filter by agent identifier - */ - agent_name?: string | null; - /** - * Applies To - * @description Filter by call types - */ - applies_to?: ("llm_call" | "tool_call")[] | null; - /** - * Check Stages - * @description Filter by check stages - */ - check_stages?: ("pre" | "post")[] | null; - /** - * Control Execution Id - * @description Filter by specific event ID - */ - control_execution_id?: string | null; - /** - * Control Ids - * @description Filter by control IDs - */ - control_ids?: number[] | null; - /** - * End Time - * @description Filter events before this time - */ - end_time?: string | null; - /** - * Limit - * @description Maximum events - * @default 100 - */ - limit: number; - /** - * Matched - * @description Filter by matched status - */ - matched?: boolean | null; - /** - * Offset - * @description Pagination offset - * @default 0 - */ - offset: number; - /** - * Span Id - * @description Filter by span ID (all events for a function) - */ - span_id?: string | null; - /** - * Start Time - * @description Filter events after this time - */ - start_time?: string | null; - /** - * Trace Id - * @description Filter by trace ID (all events for a request) - */ - trace_id?: string | null; - }; - /** - * EventQueryResponse - * @description Response model for event queries. - * - * Attributes: - * events: List of matching events - * total: Total number of matching events (for pagination) - * limit: Limit used in query - * offset: Offset used in query - */ - EventQueryResponse: { - /** - * Events - * @description Matching events - */ - events: components["schemas"]["ControlExecutionEvent"][]; - /** - * Limit - * @description Limit used in query - */ - limit: number; - /** - * Offset - * @description Offset used in query - */ - offset: number; - /** - * Total - * @description Total matching events - */ - total: number; - }; - /** - * GetAgentResponse - * @description Response containing agent details and registered steps. - */ - GetAgentResponse: { - /** @description Agent metadata */ - agent: components["schemas"]["Agent"]; - /** - * Evaluators - * @description Custom evaluators registered with this agent - */ - evaluators?: components["schemas"]["EvaluatorSchema"][]; - /** - * Steps - * @description Steps registered with this agent - */ - steps: components["schemas"]["StepSchema"][]; - }; - /** GetControlDataResponse */ - GetControlDataResponse: { - /** @description Control data payload */ - data: components["schemas"]["ControlDefinition"]; - }; - /** - * GetControlResponse - * @description Response containing control details. - */ - GetControlResponse: { - /** @description Control configuration data (None if not yet configured) */ - data?: components["schemas"]["ControlDefinition"] | null; - /** - * Id - * @description Control ID - */ - id: number; - /** - * Name - * @description Control name - */ - name: string; - }; - /** - * GetPolicyControlsResponse - * @description Response containing control IDs associated with a policy. - */ - GetPolicyControlsResponse: { - /** - * Control Ids - * @description List of control IDs associated with the policy - */ - control_ids: number[]; - }; - /** GetPolicyResponse */ - GetPolicyResponse: { - /** - * Policy Id - * @description Identifier of the policy assigned to the agent - */ - policy_id: number; - }; - /** HTTPValidationError */ - HTTPValidationError: { - /** Detail */ - detail?: components["schemas"]["ValidationError"][]; - }; - /** - * HealthResponse - * @description Health check response model. - * - * Attributes: - * status: Current health status (e.g., "healthy", "degraded", "unhealthy") - * version: Application version - */ - HealthResponse: { - /** Status */ - status: string; - /** Version */ - version: string; - }; - /** - * InitAgentEvaluatorRemoval - * @description Details for an evaluator removed during overwrite mode. - */ - InitAgentEvaluatorRemoval: { - /** - * Control Ids - * @description IDs of active controls referencing this evaluator - */ - control_ids?: number[]; - /** - * Control Names - * @description Names of active controls referencing this evaluator - */ - control_names?: string[]; - /** - * Name - * @description Evaluator name removed by overwrite - */ - name: string; - /** - * Referenced By Active Controls - * @description Whether this evaluator is still referenced by active controls - * @default false - */ - referenced_by_active_controls: boolean; - }; - /** - * InitAgentOverwriteChanges - * @description Detailed change summary for initAgent overwrite mode. - */ - InitAgentOverwriteChanges: { - /** - * Evaluator Removals - * @description Per-evaluator removal details, including active control references - */ - evaluator_removals?: components["schemas"]["InitAgentEvaluatorRemoval"][]; - /** - * Evaluators Added - * @description Evaluator names added by overwrite - */ - evaluators_added?: string[]; - /** - * Evaluators Removed - * @description Evaluator names removed by overwrite - */ - evaluators_removed?: string[]; - /** - * Evaluators Updated - * @description Existing evaluator names updated by overwrite - */ - evaluators_updated?: string[]; - /** - * Metadata Changed - * @description Whether agent metadata changed - * @default false - */ - metadata_changed: boolean; - /** - * Steps Added - * @description Steps added by overwrite - */ - steps_added?: components["schemas"]["StepKey"][]; - /** - * Steps Removed - * @description Steps removed by overwrite - */ - steps_removed?: components["schemas"]["StepKey"][]; - /** - * Steps Updated - * @description Existing steps updated by overwrite - */ - steps_updated?: components["schemas"]["StepKey"][]; - }; - /** - * InitAgentRequest - * @description Request to initialize or update an agent registration. - * @example { - * "agent": { - * "agent_description": "Handles customer inquiries", - * "agent_name": "customer-service-bot", - * "agent_version": "1.0.0" - * }, - * "evaluators": [ - * { - * "config_schema": { - * "properties": { - * "sensitivity": { - * "type": "string" - * } - * }, - * "type": "object" - * }, - * "description": "Detects PII in text", - * "name": "pii-detector" - * } - * ], - * "steps": [ - * { - * "input_schema": { - * "query": { - * "type": "string" - * } - * }, - * "name": "search_kb", - * "output_schema": { - * "results": { - * "type": "array" - * } - * }, - * "type": "tool" - * } - * ] - * } - */ - InitAgentRequest: { - /** @description Agent metadata including ID, name, and version */ - agent: components["schemas"]["Agent"]; - /** - * @description Conflict handling mode for init registration updates. 'strict' preserves existing compatibility checks. 'overwrite' applies latest-init-wins replacement for steps and evaluators. - * @default strict - */ - conflict_mode: components["schemas"]["ConflictMode"]; - /** - * Evaluators - * @description Custom evaluator schemas for config validation - */ - evaluators?: components["schemas"]["EvaluatorSchema"][]; - /** - * Force Replace - * @description If true, replace corrupted agent data instead of failing. Use only when agent data is corrupted and cannot be parsed. - * @default false - */ - force_replace: boolean; - /** - * Steps - * @description List of steps available to the agent - */ - steps?: components["schemas"]["StepSchema"][]; - }; - /** - * InitAgentResponse - * @description Response from agent initialization. - */ - InitAgentResponse: { - /** - * Controls - * @description Active protection controls for the agent (if policy assigned) - */ - controls?: components["schemas"]["Control"][]; - /** - * Created - * @description True if agent was newly created, False if updated - */ - created: boolean; - /** - * Overwrite Applied - * @description True if overwrite mode changed registration data on an existing agent - * @default false - */ - overwrite_applied: boolean; - /** @description Detailed list of changes applied in overwrite mode */ - overwrite_changes?: components["schemas"]["InitAgentOverwriteChanges"]; - }; - JSONObject: { - [key: string]: components["schemas"]["JSONValue"]; - }; - /** @description Any JSON value */ - JSONValue: unknown; - /** - * ListAgentsResponse - * @description Response for listing agents. - */ - ListAgentsResponse: { - /** - * Agents - * @description List of agent summaries - */ - agents: components["schemas"]["AgentSummary"][]; - /** @description Pagination metadata */ - pagination: components["schemas"]["PaginationInfo"]; - }; - /** - * ListControlsResponse - * @description Response for listing controls. - */ - ListControlsResponse: { - /** - * Controls - * @description List of control summaries - */ - controls: components["schemas"]["ControlSummary"][]; - /** @description Pagination metadata */ - pagination: components["schemas"]["PaginationInfo"]; - }; - /** - * ListEvaluatorConfigsResponse - * @description Response for listing evaluator configs. - */ - ListEvaluatorConfigsResponse: { - /** - * Evaluator Configs - * @description List of evaluator configs - */ - evaluator_configs: components["schemas"]["EvaluatorConfigItem"][]; - /** @description Pagination metadata */ - pagination: components["schemas"]["PaginationInfo"]; - }; - /** - * ListEvaluatorsResponse - * @description Response for listing agent's evaluator schemas. - */ - ListEvaluatorsResponse: { - /** Evaluators */ - evaluators: components["schemas"]["EvaluatorSchemaItem"][]; - pagination: components["schemas"]["PaginationInfo"]; - }; - /** - * PaginationInfo - * @description Pagination metadata for cursor-based pagination. - */ - PaginationInfo: { - /** - * Has More - * @description Whether there are more pages available - */ - has_more: boolean; - /** - * Limit - * @description Number of items per page - */ - limit: number; - /** - * Next Cursor - * @description Cursor for fetching the next page (null if no more pages) - */ - next_cursor?: string | null; - /** - * Total - * @description Total number of items - */ - total: number; - }; - /** - * PatchAgentRequest - * @description Request to modify an agent (remove steps/evaluators). - */ - PatchAgentRequest: { - /** - * Remove Evaluators - * @description Evaluator names to remove from the agent - */ - remove_evaluators?: string[]; - /** - * Remove Steps - * @description Step identifiers to remove from the agent - */ - remove_steps?: components["schemas"]["StepKey"][]; - }; - /** - * PatchAgentResponse - * @description Response from agent modification. - */ - PatchAgentResponse: { - /** - * Evaluators Removed - * @description Evaluator names that were removed - */ - evaluators_removed?: string[]; - /** - * Steps Removed - * @description Step identifiers that were removed - */ - steps_removed?: components["schemas"]["StepKey"][]; - }; - /** - * PatchControlRequest - * @description Request to update control metadata (name, enabled status). - */ - PatchControlRequest: { - /** - * Enabled - * @description Enable or disable the control - */ - enabled?: boolean | null; - /** - * Name - * @description New name for the control - */ - name?: string | null; - }; - /** - * PatchControlResponse - * @description Response from control metadata update. - */ - PatchControlResponse: { - /** - * Enabled - * @description Current enabled status (if control has data configured) - */ - enabled?: boolean | null; - /** - * Name - * @description Current control name (may have changed) - */ - name: string; - /** - * Success - * @description Whether the update succeeded - */ - success: boolean; - }; - /** - * SetControlDataRequest - * @description Request to update control configuration data. - */ - SetControlDataRequest: { - /** @description Control configuration data (replaces existing) */ - data: components["schemas"]["ControlDefinition"]; - }; - /** SetControlDataResponse */ - SetControlDataResponse: { - /** - * Success - * @description Whether the control data was updated - */ - success: boolean; - }; - /** SetPolicyResponse */ - SetPolicyResponse: { - /** - * Old Policy Id - * @description Previous policy id if one was replaced - */ - old_policy_id?: number | null; - /** - * Success - * @description Whether the policy was successfully assigned - */ - success: boolean; - }; - /** - * StatsResponse - * @description Response model for agent-level aggregated statistics. - * - * Contains agent-level totals (with optional timeseries) and per-control breakdown. - * - * Attributes: - * agent_name: Agent identifier - * time_range: Time range used - * totals: Agent-level aggregate statistics (includes timeseries) - * controls: Per-control breakdown for discovery and detail - */ - StatsResponse: { - /** - * Agent Name - * @description Agent identifier - */ - agent_name: string; - /** - * Controls - * @description Per-control breakdown - */ - controls: components["schemas"]["ControlStats"][]; - /** - * Time Range - * @description Time range used - */ - time_range: string; - /** @description Agent-level aggregate statistics */ - totals: components["schemas"]["StatsTotals"]; - }; - /** - * StatsTotals - * @description Agent-level aggregate statistics. - * - * Invariant: execution_count = match_count + non_match_count + error_count - * - * Matches have actions (allow, deny, warn, log) tracked in action_counts. - * sum(action_counts.values()) == match_count - * - * Attributes: - * execution_count: Total executions across all controls - * match_count: Total matches across all controls (evaluator matched) - * non_match_count: Total non-matches across all controls (evaluator didn't match) - * error_count: Total errors across all controls (evaluation failed) - * action_counts: Breakdown of actions for matched executions - * timeseries: Time-series data points (only when include_timeseries=true) - */ - StatsTotals: { - /** - * Action Counts - * @description Action breakdown for matches: {allow, deny, warn, log} - */ - action_counts?: { - [key: string]: number; - }; - /** - * Error Count - * @description Total errors - * @default 0 - */ - error_count: number; - /** - * Execution Count - * @description Total executions - */ - execution_count: number; - /** - * Match Count - * @description Total matches - * @default 0 - */ - match_count: number; - /** - * Non Match Count - * @description Total non-matches - * @default 0 - */ - non_match_count: number; - /** - * Timeseries - * @description Time-series data points (only when include_timeseries=true) - */ - timeseries?: components["schemas"]["TimeseriesBucket"][] | null; - }; - /** - * Step - * @description Runtime payload for an agent step invocation. - */ - Step: { - /** @description Optional context (conversation history, metadata, etc.) */ - context?: components["schemas"]["JSONObject"] | null; - /** @description Input content for this step */ - input: components["schemas"]["JSONValue"]; - /** - * Name - * @description Step name (tool name or model/chain id) - */ - name: string; - /** @description Output content for this step (None for pre-checks) */ - output?: components["schemas"]["JSONValue"] | null; - /** - * Type - * @description Step type (e.g., 'tool', 'llm') - */ - type: string; - }; - /** - * StepKey - * @description Identifies a registered step schema by type and name. - */ - StepKey: { - /** - * Name - * @description Registered step name - */ - name: string; - /** - * Type - * @description Step type - */ - type: string; - }; - /** - * StepSchema - * @description Schema for a registered agent step. - * @example { - * "description": "Search the internal knowledge base", - * "input_schema": { - * "query": { - * "description": "Search query", - * "type": "string" - * } - * }, - * "name": "search_knowledge_base", - * "output_schema": { - * "results": { - * "items": { - * "type": "object" - * }, - * "type": "array" - * } - * }, - * "type": "tool" - * } - * @example { - * "description": "Customer support response generation", - * "input_schema": { - * "messages": { - * "items": { - * "type": "object" - * }, - * "type": "array" - * } - * }, - * "name": "support-answer", - * "output_schema": { - * "text": { - * "type": "string" - * } - * }, - * "type": "llm" - * } - */ - StepSchema: { - /** - * Description - * @description Optional description of the step - */ - description?: string | null; - /** - * Input Schema - * @description JSON schema describing step input - */ - input_schema?: { - [key: string]: unknown; - } | null; - /** - * Metadata - * @description Additional metadata for the step - */ - metadata?: { - [key: string]: unknown; - } | null; - /** - * Name - * @description Unique name for the step - */ - name: string; - /** - * Output Schema - * @description JSON schema describing step output - */ - output_schema?: { - [key: string]: unknown; - } | null; - /** - * Type - * @description Step type for this schema (e.g., 'tool', 'llm') - */ - type: string; - }; - /** - * TimeseriesBucket - * @description Single data point in a time-series. - * - * Represents aggregated metrics for a single time bucket. - * - * Attributes: - * timestamp: Start time of the bucket (UTC, always timezone-aware) - * execution_count: Total executions in this bucket - * match_count: Number of matches in this bucket - * non_match_count: Number of non-matches in this bucket - * error_count: Number of errors in this bucket - * action_counts: Breakdown of actions for matched executions - * avg_confidence: Average confidence score (None if no executions) - * avg_duration_ms: Average execution duration in milliseconds (None if no data) - */ - TimeseriesBucket: { - /** - * Action Counts - * @description Action breakdown: {allow, deny, warn, log} - */ - action_counts?: { - [key: string]: number; - }; - /** - * Avg Confidence - * @description Average confidence score - */ - avg_confidence?: number | null; - /** - * Avg Duration Ms - * @description Average duration (ms) - */ - avg_duration_ms?: number | null; - /** - * Error Count - * @description Errors in bucket - */ - error_count: number; - /** - * Execution Count - * @description Total executions in bucket - */ - execution_count: number; - /** - * Match Count - * @description Matches in bucket - */ - match_count: number; - /** - * Non Match Count - * @description Non-matches in bucket - */ - non_match_count: number; - /** - * Timestamp - * Format: date-time - * @description Start time of the bucket (UTC) - */ - timestamp: string; - }; - /** - * UpdateEvaluatorConfigRequest - * @description Request to replace an evaluator config template. - */ - UpdateEvaluatorConfigRequest: { - /** - * Config - * @description Evaluator-specific configuration - */ - config: { - [key: string]: unknown; - }; - /** - * Description - * @description Optional description - */ - description?: string | null; - /** - * Evaluator - * @description Evaluator name (built-in or custom) - */ - evaluator: string; - /** - * Name - * @description Unique evaluator config name (letters, numbers, hyphens, underscores) - */ - name: string; - }; - /** - * ValidateControlDataRequest - * @description Request to validate control configuration data without saving. - */ - ValidateControlDataRequest: { - /** @description Control configuration data to validate */ - data: components["schemas"]["ControlDefinition"]; - }; - /** ValidateControlDataResponse */ - ValidateControlDataResponse: { - /** - * Success - * @description Whether the control data is valid - */ - success: boolean; - }; - /** ValidationError */ - ValidationError: { - /** Context */ - ctx?: Record; - /** Input */ - input?: unknown; - /** Location */ - loc: (string | number)[]; - /** Message */ - msg: string; - /** Error Type */ - type: string; - }; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + schemas: { + /** + * Agent + * @description Agent metadata for registration and tracking. + * + * An agent represents an AI system that can be protected and monitored. + * Each agent has a unique immutable name and can have multiple steps registered with it. + * @example { + * "agent_description": "Handles customer inquiries and support tickets", + * "agent_metadata": { + * "environment": "production", + * "team": "support" + * }, + * "agent_name": "customer-service-bot", + * "agent_version": "1.0.0" + * } + */ + Agent: { + /** + * Agent Created At + * @description ISO 8601 timestamp when agent was created + */ + agent_created_at?: string | null; + /** + * Agent Description + * @description Optional description of the agent's purpose + */ + agent_description?: string | null; + /** + * Agent Metadata + * @description Free-form metadata dictionary for custom properties + */ + agent_metadata?: { + [key: string]: unknown; + } | null; + /** + * Agent Name + * @description Unique immutable identifier for the agent + */ + agent_name: string; + /** + * Agent Updated At + * @description ISO 8601 timestamp when agent was last updated + */ + agent_updated_at?: string | null; + /** + * Agent Version + * @description Semantic version string (e.g. '1.0.0') + */ + agent_version?: string | null; + }; + /** AgentControlsResponse */ + AgentControlsResponse: { + /** + * Controls + * @description List of controls associated with the agent via its policy + */ + controls: components['schemas']['Control'][]; + }; + /** + * AgentRef + * @description Reference to an agent (for listing which agents use a control). + */ + AgentRef: { + /** + * Agent Name + * @description Agent identifier + */ + agent_name: string; + }; + /** + * AgentSummary + * @description Summary of an agent for list responses. + */ + AgentSummary: { + /** + * Active Controls Count + * @description Number of active controls from agent's policy + * @default 0 + */ + active_controls_count: number; + /** + * Agent Name + * @description Unique identifier of the agent + */ + agent_name: string; + /** + * Created At + * @description ISO 8601 timestamp when agent was created + */ + created_at?: string | null; + /** + * Evaluator Count + * @description Number of evaluators registered with the agent + * @default 0 + */ + evaluator_count: number; + /** + * Policy Id + * @description ID of assigned policy, if any + */ + policy_id?: number | null; + /** + * Step Count + * @description Number of steps registered with the agent + * @default 0 + */ + step_count: number; + }; + /** AssocResponse */ + AssocResponse: { + /** + * Success + * @description Whether the association change succeeded + */ + success: boolean; + }; + /** + * BatchEventsRequest + * @description Request model for batch event ingestion. + * + * SDKs batch events and send them to the server periodically. + * This reduces HTTP overhead significantly (100x reduction). + * + * Attributes: + * events: List of control execution events to ingest + * @example { + * "events": [ + * { + * "action": "deny", + * "agent_name": "my-agent", + * "applies_to": "llm_call", + * "check_stage": "pre", + * "confidence": 0.95, + * "control_id": 123, + * "control_name": "sql-injection-check", + * "matched": true, + * "span_id": "00f067aa0ba902b7", + * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" + * } + * ] + * } + */ + BatchEventsRequest: { + /** + * Events + * @description List of events to ingest + */ + events: components['schemas']['ControlExecutionEvent'][]; + }; + /** + * BatchEventsResponse + * @description Response model for batch event ingestion. + * + * Attributes: + * received: Number of events received + * enqueued: Number of events successfully enqueued + * dropped: Number of events dropped (queue full) + * status: Overall status ('queued', 'partial', 'failed') + */ + BatchEventsResponse: { + /** + * Dropped + * @description Number of events dropped + */ + dropped: number; + /** + * Enqueued + * @description Number of events enqueued + */ + enqueued: number; + /** + * Received + * @description Number of events received + */ + received: number; + /** + * Status + * @description Overall ingestion status + * @enum {string} + */ + status: 'queued' | 'partial' | 'failed'; + }; + /** + * ConflictMode + * @description Conflict handling mode for initAgent registration updates. + * + * STRICT preserves compatibility checks and raises conflicts on incompatible changes. + * OVERWRITE applies latest-init-wins replacement for steps and evaluators. + * @enum {string} + */ + ConflictMode: 'strict' | 'overwrite'; + /** + * Control + * @description A control with identity and configuration. + * + * Note: Only fully-configured controls (with valid ControlDefinition) + * are returned from API endpoints. Unconfigured controls are filtered out. + */ + Control: { + control: components['schemas']['ControlDefinition']; + /** Id */ + id: number; + /** Name */ + name: string; + }; + /** + * ControlAction + * @description What to do when control matches. + */ + ControlAction: { + /** + * Decision + * @description Action to take when control is triggered + * @enum {string} + */ + decision: 'allow' | 'deny' | 'warn' | 'log'; + }; + /** + * ControlDefinition + * @description A control definition to evaluate agent interactions. + * + * This model contains only the logic and configuration. + * Identity fields (id, name) are managed by the database. + * @example { + * "action": { + * "decision": "deny" + * }, + * "description": "Block outputs containing US Social Security Numbers", + * "enabled": true, + * "evaluator": { + * "config": { + * "pattern": "\\b\\d{3}-\\d{2}-\\d{4}\\b" + * }, + * "name": "regex" + * }, + * "execution": "server", + * "scope": { + * "stages": [ + * "post" + * ], + * "step_types": [ + * "llm" + * ] + * }, + * "selector": { + * "path": "output" + * }, + * "tags": [ + * "pii", + * "compliance" + * ] + * } + */ + ControlDefinition: { + /** @description What action to take when control matches */ + action: components['schemas']['ControlAction']; + /** + * Description + * @description Detailed description of the control + */ + description?: string | null; + /** + * Enabled + * @description Whether this control is active + * @default true + */ + enabled: boolean; + /** @description How to evaluate the selected data */ + evaluator: components['schemas']['EvaluatorSpec']; + /** + * Execution + * @description Where this control executes + * @enum {string} + */ + execution: 'server' | 'sdk'; + /** @description Which steps and stages this control applies to */ + scope?: components['schemas']['ControlScope']; + /** @description What data to select from the payload */ + selector: components['schemas']['ControlSelector']; + /** + * Tags + * @description Tags for categorization + */ + tags?: string[]; + }; + /** + * ControlExecutionEvent + * @description Represents a single control execution event. + * + * This is the core observability data model, capturing: + * - Identity: control_execution_id, trace_id, span_id (OpenTelemetry-compatible) + * - Context: agent, control, check stage, applies to + * - Result: action taken, whether matched, confidence score + * - Timing: when it happened, how long it took + * - Optional details: evaluator name, selector path, errors, metadata + * + * Attributes: + * control_execution_id: Unique ID for this specific control execution + * trace_id: OpenTelemetry-compatible trace ID (128-bit hex, 32 chars) + * span_id: OpenTelemetry-compatible span ID (64-bit hex, 16 chars) + * agent_name: Identifier of the agent that executed the control + * control_id: Database ID of the control + * control_name: Name of the control (denormalized for queries) + * check_stage: "pre" (before execution) or "post" (after execution) + * applies_to: "llm_call" or "tool_call" + * action: The action taken (allow, deny, warn, log) + * matched: Whether the control evaluator matched + * confidence: Confidence score from the evaluator (0.0-1.0) + * timestamp: When the control was executed (UTC) + * execution_duration_ms: How long the control evaluation took + * evaluator_name: Name of the evaluator used + * selector_path: The selector path used to extract data + * error_message: Error message if evaluation failed + * metadata: Additional metadata for extensibility + * @example { + * "action": "deny", + * "agent_name": "my-agent", + * "applies_to": "llm_call", + * "check_stage": "pre", + * "confidence": 0.95, + * "control_execution_id": "550e8400-e29b-41d4-a716-446655440000", + * "control_id": 123, + * "control_name": "sql-injection-check", + * "evaluator_name": "regex", + * "execution_duration_ms": 15.3, + * "matched": true, + * "selector_path": "input", + * "span_id": "00f067aa0ba902b7", + * "timestamp": "2025-01-09T10:30:00Z", + * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" + * } + */ + ControlExecutionEvent: { + /** + * Action + * @description Action taken by the control + * @enum {string} + */ + action: 'allow' | 'deny' | 'warn' | 'log'; + /** + * Agent Name + * @description Identifier of the agent + */ + agent_name: string; + /** + * Applies To + * @description Type of call: 'llm_call' or 'tool_call' + * @enum {string} + */ + applies_to: 'llm_call' | 'tool_call'; + /** + * Check Stage + * @description Check stage: 'pre' or 'post' + * @enum {string} + */ + check_stage: 'pre' | 'post'; + /** + * Confidence + * @description Confidence score (0.0 to 1.0) + */ + confidence: number; + /** + * Control Execution Id + * @description Unique ID for this control execution + */ + control_execution_id?: string; + /** + * Control Id + * @description Database ID of the control + */ + control_id: number; + /** + * Control Name + * @description Name of the control (denormalized) + */ + control_name: string; + /** + * Error Message + * @description Error message if evaluation failed + */ + error_message?: string | null; + /** + * Evaluator Name + * @description Name of the evaluator used + */ + evaluator_name?: string | null; + /** + * Execution Duration Ms + * @description Execution duration in milliseconds + */ + execution_duration_ms?: number | null; + /** + * Matched + * @description Whether the evaluator matched (True) or not (False) + */ + matched: boolean; + /** + * Metadata + * @description Additional metadata + */ + metadata?: { + [key: string]: unknown; + }; + /** + * Selector Path + * @description Selector path used to extract data + */ + selector_path?: string | null; + /** + * Span Id + * @description Span ID for distributed tracing (SDK generates OTEL-compatible 16-char hex) + */ + span_id: string; + /** + * Timestamp + * Format: date-time + * @description When the control was executed (UTC) + */ + timestamp?: string; + /** + * Trace Id + * @description Trace ID for distributed tracing (SDK generates OTEL-compatible 32-char hex) + */ + trace_id: string; + }; + /** + * ControlMatch + * @description Represents a control evaluation result (match, non-match, or error). + */ + ControlMatch: { + /** + * Action + * @description Action configured for this control + * @enum {string} + */ + action: 'allow' | 'deny' | 'warn' | 'log'; + /** + * Control Execution Id + * @description Unique ID for this control execution (generated by engine) + */ + control_execution_id?: string; + /** + * Control Id + * @description Database ID of the control + */ + control_id: number; + /** + * Control Name + * @description Name of the control + */ + control_name: string; + /** @description Evaluator result (confidence, message, metadata) */ + result: components['schemas']['EvaluatorResult']; + }; + /** + * ControlScope + * @description Defines when a control applies to a Step. + * @example { + * "stages": [ + * "pre" + * ], + * "step_types": [ + * "tool" + * ] + * } + * @example { + * "step_names": [ + * "search_db", + * "fetch_user" + * ] + * } + * @example { + * "step_name_regex": "^db_.*" + * } + * @example { + * "stages": [ + * "post" + * ], + * "step_types": [ + * "llm" + * ] + * } + */ + ControlScope: { + /** + * Stages + * @description Evaluation stages this control applies to + */ + stages?: ('pre' | 'post')[] | null; + /** + * Step Name Regex + * @description RE2 pattern matched with search() against step name + */ + step_name_regex?: string | null; + /** + * Step Names + * @description Exact step names this control applies to + */ + step_names?: string[] | null; + /** + * Step Types + * @description Step types this control applies to (omit to apply to all types). Built-in types are 'tool' and 'llm'. + */ + step_types?: string[] | null; + }; + /** + * ControlSelector + * @description Selects data from a Step payload. + * + * - path: which slice of the Step to feed into the evaluator. Optional, defaults to "*" + * meaning the entire Step object. + * @example { + * "path": "output" + * } + * @example { + * "path": "context.user_id" + * } + * @example { + * "path": "input" + * } + * @example { + * "path": "*" + * } + * @example { + * "path": "name" + * } + * @example { + * "path": "output" + * } + */ + ControlSelector: { + /** + * Path + * @description Path to data using dot notation. Examples: 'input', 'output', 'context.user_id', 'name', 'type', '*' + * @default * + */ + path: string | null; + }; + /** + * ControlStats + * @description Aggregated statistics for a single control. + * + * Attributes: + * control_id: Database ID of the control + * control_name: Name of the control + * execution_count: Total number of executions + * match_count: Number of times the control matched + * non_match_count: Number of times the control did not match + * allow_count: Number of allow actions + * deny_count: Number of deny actions + * warn_count: Number of warn actions + * log_count: Number of log actions + * error_count: Number of errors during evaluation + * avg_confidence: Average confidence score + * avg_duration_ms: Average execution duration in milliseconds + */ + ControlStats: { + /** + * Allow Count + * @description Allow actions + */ + allow_count: number; + /** + * Avg Confidence + * @description Average confidence + */ + avg_confidence: number; + /** + * Avg Duration Ms + * @description Average duration (ms) + */ + avg_duration_ms?: number | null; + /** + * Control Id + * @description Control ID + */ + control_id: number; + /** + * Control Name + * @description Control name + */ + control_name: string; + /** + * Deny Count + * @description Deny actions + */ + deny_count: number; + /** + * Error Count + * @description Evaluation errors + */ + error_count: number; + /** + * Execution Count + * @description Total executions + */ + execution_count: number; + /** + * Log Count + * @description Log actions + */ + log_count: number; + /** + * Match Count + * @description Total matches + */ + match_count: number; + /** + * Non Match Count + * @description Total non-matches + */ + non_match_count: number; + /** + * Warn Count + * @description Warn actions + */ + warn_count: number; + }; + /** + * ControlStatsResponse + * @description Response model for control-level statistics. + * + * Contains stats for a single control (with optional timeseries). + * + * Attributes: + * agent_name: Agent identifier + * time_range: Time range used + * control_id: Control ID + * control_name: Control name + * stats: Control statistics (includes timeseries when requested) + */ + ControlStatsResponse: { + /** + * Agent Name + * @description Agent identifier + */ + agent_name: string; + /** + * Control Id + * @description Control ID + */ + control_id: number; + /** + * Control Name + * @description Control name + */ + control_name: string; + /** @description Control statistics */ + stats: components['schemas']['StatsTotals']; + /** + * Time Range + * @description Time range used + */ + time_range: string; + }; + /** + * ControlSummary + * @description Summary of a control for list responses. + */ + ControlSummary: { + /** + * Description + * @description Control description + */ + description?: string | null; + /** + * Enabled + * @description Whether control is enabled + * @default true + */ + enabled: boolean; + /** + * Execution + * @description 'server' or 'sdk' + */ + execution?: string | null; + /** + * Id + * @description Control ID + */ + id: number; + /** + * Name + * @description Control name + */ + name: string; + /** + * Stages + * @description Evaluation stages in scope + */ + stages?: string[] | null; + /** + * Step Types + * @description Step types in scope + */ + step_types?: string[] | null; + /** + * Tags + * @description Control tags + */ + tags?: string[]; + /** @description Agent using this control */ + used_by_agent?: components['schemas']['AgentRef'] | null; + }; + /** CreateControlRequest */ + CreateControlRequest: { + /** + * Name + * @description Unique control name (letters, numbers, hyphens, underscores) + */ + name: string; + }; + /** CreateControlResponse */ + CreateControlResponse: { + /** + * Control Id + * @description Identifier of the created control + */ + control_id: number; + }; + /** + * CreateEvaluatorConfigRequest + * @description Request to create an evaluator config template. + */ + CreateEvaluatorConfigRequest: { + /** + * Config + * @description Evaluator-specific configuration + */ + config: { + [key: string]: unknown; + }; + /** + * Description + * @description Optional description + */ + description?: string | null; + /** + * Evaluator + * @description Evaluator name (built-in or custom) + */ + evaluator: string; + /** + * Name + * @description Unique evaluator config name (letters, numbers, hyphens, underscores) + */ + name: string; + }; + /** CreatePolicyRequest */ + CreatePolicyRequest: { + /** + * Name + * @description Unique policy name (letters, numbers, hyphens, underscores) + */ + name: string; + }; + /** CreatePolicyResponse */ + CreatePolicyResponse: { + /** + * Policy Id + * @description Identifier of the created policy + */ + policy_id: number; + }; + /** + * DeleteControlResponse + * @description Response for deleting a control. + */ + DeleteControlResponse: { + /** + * Dissociated From + * @description Policy IDs the control was removed from before deletion + */ + dissociated_from?: number[]; + /** + * Success + * @description Whether the control was deleted + */ + success: boolean; + }; + /** + * DeleteEvaluatorConfigResponse + * @description Response for deleting an evaluator config. + */ + DeleteEvaluatorConfigResponse: { + /** + * Success + * @description Whether the evaluator config was deleted + */ + success: boolean; + }; + /** DeletePolicyResponse */ + DeletePolicyResponse: { + /** + * Success + * @description Whether the policy was successfully removed + */ + success: boolean; + }; + /** + * EvaluationRequest + * @description Request model for evaluation analysis. + * + * Used to analyze agent interactions for safety violations, + * policy compliance, and control rules. + * + * Attributes: + * agent_name: Unique identifier of the agent making the request + * step: Step payload for evaluation + * stage: 'pre' (before execution) or 'post' (after execution) + * @example { + * "agent_name": "customer-service-bot", + * "stage": "pre", + * "step": { + * "context": { + * "session_id": "abc123", + * "user_id": "user123" + * }, + * "input": "What is the customer's credit card number?", + * "name": "support-answer", + * "type": "llm" + * } + * } + * @example { + * "agent_name": "customer-service-bot", + * "stage": "post", + * "step": { + * "context": { + * "session_id": "abc123", + * "user_id": "user123" + * }, + * "input": "What is the customer's credit card number?", + * "name": "support-answer", + * "output": "I cannot share sensitive payment information.", + * "type": "llm" + * } + * } + * @example { + * "agent_name": "customer-service-bot", + * "stage": "pre", + * "step": { + * "context": { + * "user_id": "user123" + * }, + * "input": { + * "query": "SELECT * FROM users" + * }, + * "name": "search_database", + * "type": "tool" + * } + * } + * @example { + * "agent_name": "customer-service-bot", + * "stage": "post", + * "step": { + * "context": { + * "user_id": "user123" + * }, + * "input": { + * "query": "SELECT * FROM users" + * }, + * "name": "search_database", + * "output": { + * "results": [] + * }, + * "type": "tool" + * } + * } + */ + EvaluationRequest: { + /** + * Agent Name + * @description Identifier of the agent making the evaluation request + */ + agent_name: string; + /** + * Stage + * @description Evaluation stage: 'pre' or 'post' + * @enum {string} + */ + stage: 'pre' | 'post'; + /** @description Agent step payload to evaluate */ + step: components['schemas']['Step']; + }; + /** + * EvaluationResponse + * @description Response model from evaluation analysis (server-side). + * + * This is what the server returns. The SDK may transform this + * into an EvaluationResult for client convenience. + * + * Attributes: + * is_safe: Whether the content is considered safe + * confidence: Confidence score between 0.0 and 1.0 + * reason: Optional explanation for the decision + * matches: List of controls that matched/triggered (if any) + * errors: List of controls that failed during evaluation (if any) + * non_matches: List of controls that were evaluated but did not match (if any) + */ + EvaluationResponse: { + /** + * Confidence + * @description Confidence score (0.0 to 1.0) + */ + confidence: number; + /** + * Errors + * @description List of controls that failed during evaluation (if any) + */ + errors?: components['schemas']['ControlMatch'][] | null; + /** + * Is Safe + * @description Whether content is safe + */ + is_safe: boolean; + /** + * Matches + * @description List of controls that matched/triggered (if any) + */ + matches?: components['schemas']['ControlMatch'][] | null; + /** + * Non Matches + * @description List of controls that were evaluated but did not match (if any) + */ + non_matches?: components['schemas']['ControlMatch'][] | null; + /** + * Reason + * @description Explanation for the decision + */ + reason?: string | null; + }; + /** + * EvaluatorConfigItem + * @description Evaluator config template stored in the server. + */ + EvaluatorConfigItem: { + /** + * Config + * @description Evaluator-specific configuration + */ + config: { + [key: string]: unknown; + }; + /** + * Created At + * @description ISO 8601 created timestamp + */ + created_at?: string | null; + /** + * Description + * @description Optional description + */ + description?: string | null; + /** + * Evaluator + * @description Evaluator name (built-in or custom) + */ + evaluator: string; + /** + * Id + * @description Evaluator config ID + */ + id: number; + /** + * Name + * @description Unique evaluator config name (letters, numbers, hyphens, underscores) + */ + name: string; + /** + * Updated At + * @description ISO 8601 updated timestamp + */ + updated_at?: string | null; + }; + /** + * EvaluatorInfo + * @description Information about a registered evaluator. + */ + EvaluatorInfo: { + /** + * Config Schema + * @description JSON Schema for config + */ + config_schema: { + [key: string]: unknown; + }; + /** + * Description + * @description Evaluator description + */ + description: string; + /** + * Name + * @description Evaluator name + */ + name: string; + /** + * Requires Api Key + * @description Whether evaluator requires API key + */ + requires_api_key: boolean; + /** + * Timeout Ms + * @description Default timeout in milliseconds + */ + timeout_ms: number; + /** + * Version + * @description Evaluator version + */ + version: string; + }; + /** + * EvaluatorResult + * @description Result from a control evaluator. + * + * The `error` field indicates evaluator failures, NOT validation failures: + * - Set `error` for: evaluator crashes, timeouts, missing dependencies, external service errors + * - Do NOT set `error` for: invalid input, syntax errors, schema violations, constraint failures + * + * When `error` is set, `matched` must be False (fail-open on evaluator errors). + * When `error` is None, `matched` reflects the actual validation result. + * + * This distinction allows: + * - Clients to distinguish "data violated rules" from "evaluator is broken" + * - Observability systems to monitor evaluator health separately from validation outcomes + */ + EvaluatorResult: { + /** + * Confidence + * @description Confidence in the evaluation + */ + confidence: number; + /** + * Error + * @description Error message if evaluation failed internally. When set, matched=False is due to error, not actual evaluation. + */ + error?: string | null; + /** + * Matched + * @description Whether the pattern matched + */ + matched: boolean; + /** + * Message + * @description Explanation of the result + */ + message?: string | null; + /** + * Metadata + * @description Additional result metadata + */ + metadata?: { + [key: string]: unknown; + } | null; + }; + /** + * EvaluatorSchema + * @description Schema for a custom evaluator registered with an agent. + * + * Custom evaluators are Evaluator classes deployed with the engine. + * This schema is registered via initAgent for validation and UI purposes. + */ + EvaluatorSchema: { + /** + * Config Schema + * @description JSON Schema for evaluator config validation + */ + config_schema?: { + [key: string]: unknown; + }; + /** + * Description + * @description Optional description + */ + description?: string | null; + /** + * Name + * @description Unique evaluator name + */ + name: string; + }; + /** + * EvaluatorSchemaItem + * @description Evaluator schema summary for list response. + */ + EvaluatorSchemaItem: { + /** Config Schema */ + config_schema: { + [key: string]: unknown; + }; + /** Description */ + description: string | null; + /** Name */ + name: string; + }; + /** + * EvaluatorSpec + * @description Evaluator specification. See GET /evaluators for available evaluators and schemas. + * + * Evaluator reference formats: + * - Built-in: "regex", "list", "json", "sql" + * - External: "galileo.luna2" (requires agent-control-evaluators[galileo]) + * - Agent-scoped: "my-agent:my-evaluator" (validated in endpoint, not here) + */ + EvaluatorSpec: { + /** + * Config + * @description Evaluator-specific configuration + * @example { + * "pattern": "\\d{3}-\\d{2}-\\d{4}" + * } + * @example { + * "logic": "any", + * "values": [ + * "admin" + * ] + * } + */ + config: { + [key: string]: unknown; + }; + /** + * Name + * @description Evaluator name or agent-scoped reference (agent:evaluator) + * @example regex + * @example list + * @example my-agent:pii-detector + */ + name: string; + }; + /** + * EventQueryRequest + * @description Request model for querying raw events. + * + * Supports filtering by various criteria and pagination. + * + * Attributes: + * trace_id: Filter by trace ID (get all events for a request) + * span_id: Filter by span ID (get all events for a function call) + * control_execution_id: Filter by specific event ID + * agent_name: Filter by agent identifier + * control_ids: Filter by control IDs + * actions: Filter by actions (allow, deny, warn, log) + * matched: Filter by matched status + * check_stages: Filter by check stages (pre, post) + * applies_to: Filter by call type (llm_call, tool_call) + * start_time: Filter events after this time + * end_time: Filter events before this time + * limit: Maximum number of events to return + * offset: Offset for pagination + * @example { + * "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" + * } + * @example { + * "actions": [ + * "deny", + * "warn" + * ], + * "agent_name": "my-agent", + * "limit": 50, + * "start_time": "2025-01-09T00:00:00Z" + * } + */ + EventQueryRequest: { + /** + * Actions + * @description Filter by actions + */ + actions?: ('allow' | 'deny' | 'warn' | 'log')[] | null; + /** + * Agent Name + * @description Filter by agent identifier + */ + agent_name?: string | null; + /** + * Applies To + * @description Filter by call types + */ + applies_to?: ('llm_call' | 'tool_call')[] | null; + /** + * Check Stages + * @description Filter by check stages + */ + check_stages?: ('pre' | 'post')[] | null; + /** + * Control Execution Id + * @description Filter by specific event ID + */ + control_execution_id?: string | null; + /** + * Control Ids + * @description Filter by control IDs + */ + control_ids?: number[] | null; + /** + * End Time + * @description Filter events before this time + */ + end_time?: string | null; + /** + * Limit + * @description Maximum events + * @default 100 + */ + limit: number; + /** + * Matched + * @description Filter by matched status + */ + matched?: boolean | null; + /** + * Offset + * @description Pagination offset + * @default 0 + */ + offset: number; + /** + * Span Id + * @description Filter by span ID (all events for a function) + */ + span_id?: string | null; + /** + * Start Time + * @description Filter events after this time + */ + start_time?: string | null; + /** + * Trace Id + * @description Filter by trace ID (all events for a request) + */ + trace_id?: string | null; + }; + /** + * EventQueryResponse + * @description Response model for event queries. + * + * Attributes: + * events: List of matching events + * total: Total number of matching events (for pagination) + * limit: Limit used in query + * offset: Offset used in query + */ + EventQueryResponse: { + /** + * Events + * @description Matching events + */ + events: components['schemas']['ControlExecutionEvent'][]; + /** + * Limit + * @description Limit used in query + */ + limit: number; + /** + * Offset + * @description Offset used in query + */ + offset: number; + /** + * Total + * @description Total matching events + */ + total: number; + }; + /** + * GetAgentResponse + * @description Response containing agent details and registered steps. + */ + GetAgentResponse: { + /** @description Agent metadata */ + agent: components['schemas']['Agent']; + /** + * Evaluators + * @description Custom evaluators registered with this agent + */ + evaluators?: components['schemas']['EvaluatorSchema'][]; + /** + * Steps + * @description Steps registered with this agent + */ + steps: components['schemas']['StepSchema'][]; + }; + /** GetControlDataResponse */ + GetControlDataResponse: { + /** @description Control data payload */ + data: components['schemas']['ControlDefinition']; + }; + /** + * GetControlResponse + * @description Response containing control details. + */ + GetControlResponse: { + /** @description Control configuration data (None if not yet configured) */ + data?: components['schemas']['ControlDefinition'] | null; + /** + * Id + * @description Control ID + */ + id: number; + /** + * Name + * @description Control name + */ + name: string; + }; + /** + * GetPolicyControlsResponse + * @description Response containing control IDs associated with a policy. + */ + GetPolicyControlsResponse: { + /** + * Control Ids + * @description List of control IDs associated with the policy + */ + control_ids: number[]; + }; + /** GetPolicyResponse */ + GetPolicyResponse: { + /** + * Policy Id + * @description Identifier of the policy assigned to the agent + */ + policy_id: number; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components['schemas']['ValidationError'][]; + }; + /** + * HealthResponse + * @description Health check response model. + * + * Attributes: + * status: Current health status (e.g., "healthy", "degraded", "unhealthy") + * version: Application version + */ + HealthResponse: { + /** Status */ + status: string; + /** Version */ + version: string; + }; + /** + * InitAgentEvaluatorRemoval + * @description Details for an evaluator removed during overwrite mode. + */ + InitAgentEvaluatorRemoval: { + /** + * Control Ids + * @description IDs of active controls referencing this evaluator + */ + control_ids?: number[]; + /** + * Control Names + * @description Names of active controls referencing this evaluator + */ + control_names?: string[]; + /** + * Name + * @description Evaluator name removed by overwrite + */ + name: string; + /** + * Referenced By Active Controls + * @description Whether this evaluator is still referenced by active controls + * @default false + */ + referenced_by_active_controls: boolean; + }; + /** + * InitAgentOverwriteChanges + * @description Detailed change summary for initAgent overwrite mode. + */ + InitAgentOverwriteChanges: { + /** + * Evaluator Removals + * @description Per-evaluator removal details, including active control references + */ + evaluator_removals?: components['schemas']['InitAgentEvaluatorRemoval'][]; + /** + * Evaluators Added + * @description Evaluator names added by overwrite + */ + evaluators_added?: string[]; + /** + * Evaluators Removed + * @description Evaluator names removed by overwrite + */ + evaluators_removed?: string[]; + /** + * Evaluators Updated + * @description Existing evaluator names updated by overwrite + */ + evaluators_updated?: string[]; + /** + * Metadata Changed + * @description Whether agent metadata changed + * @default false + */ + metadata_changed: boolean; + /** + * Steps Added + * @description Steps added by overwrite + */ + steps_added?: components['schemas']['StepKey'][]; + /** + * Steps Removed + * @description Steps removed by overwrite + */ + steps_removed?: components['schemas']['StepKey'][]; + /** + * Steps Updated + * @description Existing steps updated by overwrite + */ + steps_updated?: components['schemas']['StepKey'][]; + }; + /** + * InitAgentRequest + * @description Request to initialize or update an agent registration. + * @example { + * "agent": { + * "agent_description": "Handles customer inquiries", + * "agent_name": "customer-service-bot", + * "agent_version": "1.0.0" + * }, + * "evaluators": [ + * { + * "config_schema": { + * "properties": { + * "sensitivity": { + * "type": "string" + * } + * }, + * "type": "object" + * }, + * "description": "Detects PII in text", + * "name": "pii-detector" + * } + * ], + * "steps": [ + * { + * "input_schema": { + * "query": { + * "type": "string" + * } + * }, + * "name": "search_kb", + * "output_schema": { + * "results": { + * "type": "array" + * } + * }, + * "type": "tool" + * } + * ] + * } + */ + InitAgentRequest: { + /** @description Agent metadata including ID, name, and version */ + agent: components['schemas']['Agent']; + /** + * @description Conflict handling mode for init registration updates. 'strict' preserves existing compatibility checks. 'overwrite' applies latest-init-wins replacement for steps and evaluators. + * @default strict + */ + conflict_mode: components['schemas']['ConflictMode']; + /** + * Evaluators + * @description Custom evaluator schemas for config validation + */ + evaluators?: components['schemas']['EvaluatorSchema'][]; + /** + * Force Replace + * @description If true, replace corrupted agent data instead of failing. Use only when agent data is corrupted and cannot be parsed. + * @default false + */ + force_replace: boolean; + /** + * Steps + * @description List of steps available to the agent + */ + steps?: components['schemas']['StepSchema'][]; + }; + /** + * InitAgentResponse + * @description Response from agent initialization. + */ + InitAgentResponse: { + /** + * Controls + * @description Active protection controls for the agent (if policy assigned) + */ + controls?: components['schemas']['Control'][]; + /** + * Created + * @description True if agent was newly created, False if updated + */ + created: boolean; + /** + * Overwrite Applied + * @description True if overwrite mode changed registration data on an existing agent + * @default false + */ + overwrite_applied: boolean; + /** @description Detailed list of changes applied in overwrite mode */ + overwrite_changes?: components['schemas']['InitAgentOverwriteChanges']; + }; + JSONObject: { + [key: string]: components['schemas']['JSONValue']; + }; + /** @description Any JSON value */ + JSONValue: unknown; + /** + * ListAgentsResponse + * @description Response for listing agents. + */ + ListAgentsResponse: { + /** + * Agents + * @description List of agent summaries + */ + agents: components['schemas']['AgentSummary'][]; + /** @description Pagination metadata */ + pagination: components['schemas']['PaginationInfo']; + }; + /** + * ListControlsResponse + * @description Response for listing controls. + */ + ListControlsResponse: { + /** + * Controls + * @description List of control summaries + */ + controls: components['schemas']['ControlSummary'][]; + /** @description Pagination metadata */ + pagination: components['schemas']['PaginationInfo']; + }; + /** + * ListEvaluatorConfigsResponse + * @description Response for listing evaluator configs. + */ + ListEvaluatorConfigsResponse: { + /** + * Evaluator Configs + * @description List of evaluator configs + */ + evaluator_configs: components['schemas']['EvaluatorConfigItem'][]; + /** @description Pagination metadata */ + pagination: components['schemas']['PaginationInfo']; + }; + /** + * ListEvaluatorsResponse + * @description Response for listing agent's evaluator schemas. + */ + ListEvaluatorsResponse: { + /** Evaluators */ + evaluators: components['schemas']['EvaluatorSchemaItem'][]; + pagination: components['schemas']['PaginationInfo']; + }; + /** + * PaginationInfo + * @description Pagination metadata for cursor-based pagination. + */ + PaginationInfo: { + /** + * Has More + * @description Whether there are more pages available + */ + has_more: boolean; + /** + * Limit + * @description Number of items per page + */ + limit: number; + /** + * Next Cursor + * @description Cursor for fetching the next page (null if no more pages) + */ + next_cursor?: string | null; + /** + * Total + * @description Total number of items + */ + total: number; + }; + /** + * PatchAgentRequest + * @description Request to modify an agent (remove steps/evaluators). + */ + PatchAgentRequest: { + /** + * Remove Evaluators + * @description Evaluator names to remove from the agent + */ + remove_evaluators?: string[]; + /** + * Remove Steps + * @description Step identifiers to remove from the agent + */ + remove_steps?: components['schemas']['StepKey'][]; + }; + /** + * PatchAgentResponse + * @description Response from agent modification. + */ + PatchAgentResponse: { + /** + * Evaluators Removed + * @description Evaluator names that were removed + */ + evaluators_removed?: string[]; + /** + * Steps Removed + * @description Step identifiers that were removed + */ + steps_removed?: components['schemas']['StepKey'][]; + }; + /** + * PatchControlRequest + * @description Request to update control metadata (name, enabled status). + */ + PatchControlRequest: { + /** + * Enabled + * @description Enable or disable the control + */ + enabled?: boolean | null; + /** + * Name + * @description New name for the control + */ + name?: string | null; + }; + /** + * PatchControlResponse + * @description Response from control metadata update. + */ + PatchControlResponse: { + /** + * Enabled + * @description Current enabled status (if control has data configured) + */ + enabled?: boolean | null; + /** + * Name + * @description Current control name (may have changed) + */ + name: string; + /** + * Success + * @description Whether the update succeeded + */ + success: boolean; + }; + /** + * SetControlDataRequest + * @description Request to update control configuration data. + */ + SetControlDataRequest: { + /** @description Control configuration data (replaces existing) */ + data: components['schemas']['ControlDefinition']; + }; + /** SetControlDataResponse */ + SetControlDataResponse: { + /** + * Success + * @description Whether the control data was updated + */ + success: boolean; + }; + /** SetPolicyResponse */ + SetPolicyResponse: { + /** + * Old Policy Id + * @description Previous policy id if one was replaced + */ + old_policy_id?: number | null; + /** + * Success + * @description Whether the policy was successfully assigned + */ + success: boolean; + }; + /** + * StatsResponse + * @description Response model for agent-level aggregated statistics. + * + * Contains agent-level totals (with optional timeseries) and per-control breakdown. + * + * Attributes: + * agent_name: Agent identifier + * time_range: Time range used + * totals: Agent-level aggregate statistics (includes timeseries) + * controls: Per-control breakdown for discovery and detail + */ + StatsResponse: { + /** + * Agent Name + * @description Agent identifier + */ + agent_name: string; + /** + * Controls + * @description Per-control breakdown + */ + controls: components['schemas']['ControlStats'][]; + /** + * Time Range + * @description Time range used + */ + time_range: string; + /** @description Agent-level aggregate statistics */ + totals: components['schemas']['StatsTotals']; + }; + /** + * StatsTotals + * @description Agent-level aggregate statistics. + * + * Invariant: execution_count = match_count + non_match_count + error_count + * + * Matches have actions (allow, deny, warn, log) tracked in action_counts. + * sum(action_counts.values()) == match_count + * + * Attributes: + * execution_count: Total executions across all controls + * match_count: Total matches across all controls (evaluator matched) + * non_match_count: Total non-matches across all controls (evaluator didn't match) + * error_count: Total errors across all controls (evaluation failed) + * action_counts: Breakdown of actions for matched executions + * timeseries: Time-series data points (only when include_timeseries=true) + */ + StatsTotals: { + /** + * Action Counts + * @description Action breakdown for matches: {allow, deny, warn, log} + */ + action_counts?: { + [key: string]: number; + }; + /** + * Error Count + * @description Total errors + * @default 0 + */ + error_count: number; + /** + * Execution Count + * @description Total executions + */ + execution_count: number; + /** + * Match Count + * @description Total matches + * @default 0 + */ + match_count: number; + /** + * Non Match Count + * @description Total non-matches + * @default 0 + */ + non_match_count: number; + /** + * Timeseries + * @description Time-series data points (only when include_timeseries=true) + */ + timeseries?: components['schemas']['TimeseriesBucket'][] | null; + }; + /** + * Step + * @description Runtime payload for an agent step invocation. + */ + Step: { + /** @description Optional context (conversation history, metadata, etc.) */ + context?: components['schemas']['JSONObject'] | null; + /** @description Input content for this step */ + input: components['schemas']['JSONValue']; + /** + * Name + * @description Step name (tool name or model/chain id) + */ + name: string; + /** @description Output content for this step (None for pre-checks) */ + output?: components['schemas']['JSONValue'] | null; + /** + * Type + * @description Step type (e.g., 'tool', 'llm') + */ + type: string; + }; + /** + * StepKey + * @description Identifies a registered step schema by type and name. + */ + StepKey: { + /** + * Name + * @description Registered step name + */ + name: string; + /** + * Type + * @description Step type + */ + type: string; + }; + /** + * StepSchema + * @description Schema for a registered agent step. + * @example { + * "description": "Search the internal knowledge base", + * "input_schema": { + * "query": { + * "description": "Search query", + * "type": "string" + * } + * }, + * "name": "search_knowledge_base", + * "output_schema": { + * "results": { + * "items": { + * "type": "object" + * }, + * "type": "array" + * } + * }, + * "type": "tool" + * } + * @example { + * "description": "Customer support response generation", + * "input_schema": { + * "messages": { + * "items": { + * "type": "object" + * }, + * "type": "array" + * } + * }, + * "name": "support-answer", + * "output_schema": { + * "text": { + * "type": "string" + * } + * }, + * "type": "llm" + * } + */ + StepSchema: { + /** + * Description + * @description Optional description of the step + */ + description?: string | null; + /** + * Input Schema + * @description JSON schema describing step input + */ + input_schema?: { + [key: string]: unknown; + } | null; + /** + * Metadata + * @description Additional metadata for the step + */ + metadata?: { + [key: string]: unknown; + } | null; + /** + * Name + * @description Unique name for the step + */ + name: string; + /** + * Output Schema + * @description JSON schema describing step output + */ + output_schema?: { + [key: string]: unknown; + } | null; + /** + * Type + * @description Step type for this schema (e.g., 'tool', 'llm') + */ + type: string; + }; + /** + * TimeseriesBucket + * @description Single data point in a time-series. + * + * Represents aggregated metrics for a single time bucket. + * + * Attributes: + * timestamp: Start time of the bucket (UTC, always timezone-aware) + * execution_count: Total executions in this bucket + * match_count: Number of matches in this bucket + * non_match_count: Number of non-matches in this bucket + * error_count: Number of errors in this bucket + * action_counts: Breakdown of actions for matched executions + * avg_confidence: Average confidence score (None if no executions) + * avg_duration_ms: Average execution duration in milliseconds (None if no data) + */ + TimeseriesBucket: { + /** + * Action Counts + * @description Action breakdown: {allow, deny, warn, log} + */ + action_counts?: { + [key: string]: number; + }; + /** + * Avg Confidence + * @description Average confidence score + */ + avg_confidence?: number | null; + /** + * Avg Duration Ms + * @description Average duration (ms) + */ + avg_duration_ms?: number | null; + /** + * Error Count + * @description Errors in bucket + */ + error_count: number; + /** + * Execution Count + * @description Total executions in bucket + */ + execution_count: number; + /** + * Match Count + * @description Matches in bucket + */ + match_count: number; + /** + * Non Match Count + * @description Non-matches in bucket + */ + non_match_count: number; + /** + * Timestamp + * Format: date-time + * @description Start time of the bucket (UTC) + */ + timestamp: string; + }; + /** + * UpdateEvaluatorConfigRequest + * @description Request to replace an evaluator config template. + */ + UpdateEvaluatorConfigRequest: { + /** + * Config + * @description Evaluator-specific configuration + */ + config: { + [key: string]: unknown; + }; + /** + * Description + * @description Optional description + */ + description?: string | null; + /** + * Evaluator + * @description Evaluator name (built-in or custom) + */ + evaluator: string; + /** + * Name + * @description Unique evaluator config name (letters, numbers, hyphens, underscores) + */ + name: string; + }; + /** + * ValidateControlDataRequest + * @description Request to validate control configuration data without saving. + */ + ValidateControlDataRequest: { + /** @description Control configuration data to validate */ + data: components['schemas']['ControlDefinition']; + }; + /** ValidateControlDataResponse */ + ValidateControlDataResponse: { + /** + * Success + * @description Whether the control data is valid + */ + success: boolean; + }; + /** ValidationError */ + ValidationError: { + /** Context */ + ctx?: Record; + /** Input */ + input?: unknown; + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - list_agents_api_v1_agents_get: { - parameters: { - query?: { - cursor?: string | null; - limit?: number; - name?: string | null; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of agent summaries */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ListAgentsResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - init_agent_api_v1_agents_initAgent_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["InitAgentRequest"]; - }; - }; - responses: { - /** @description Agent registration status with active controls */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["InitAgentResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_agent_api_v1_agents__agent_name__get: { - parameters: { - query?: never; - header?: never; - path: { - agent_name: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Agent metadata and registered steps */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetAgentResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - patch_agent_api_v1_agents__agent_name__patch: { - parameters: { - query?: never; - header?: never; - path: { - agent_name: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["PatchAgentRequest"]; - }; - }; - responses: { - /** @description Lists of removed items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["PatchAgentResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - list_agent_controls_api_v1_agents__agent_name__controls_get: { - parameters: { - query?: never; - header?: never; - path: { - agent_name: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of controls from agent's policy */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["AgentControlsResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - list_agent_evaluators_api_v1_agents__agent_name__evaluators_get: { - parameters: { - query?: { - cursor?: string | null; - limit?: number; - }; - header?: never; - path: { - agent_name: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Evaluator schemas registered with this agent */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ListEvaluatorsResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_agent_evaluator_api_v1_agents__agent_name__evaluators__evaluator_name__get: { - parameters: { - query?: never; - header?: never; - path: { - agent_name: string; - evaluator_name: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Evaluator schema details */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["EvaluatorSchemaItem"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_agent_policy_api_v1_agents__agent_name__policy_get: { - parameters: { - query?: never; - header?: never; - path: { - agent_name: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Policy ID */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetPolicyResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - delete_agent_policy_api_v1_agents__agent_name__policy_delete: { - parameters: { - query?: never; - header?: never; - path: { - agent_name: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Success confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DeletePolicyResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - set_agent_policy_api_v1_agents__agent_name__policy__policy_id__post: { - parameters: { - query?: never; - header?: never; - path: { - agent_name: string; - policy_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Success status with previous policy ID */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["SetPolicyResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - list_controls_api_v1_controls_get: { - parameters: { - query?: { - /** @description Control ID to start after */ - cursor?: number | null; - limit?: number; - /** @description Filter by name (partial, case-insensitive) */ - name?: string | null; - /** @description Filter by enabled status */ - enabled?: boolean | null; - /** @description Filter by step type (built-ins: 'tool', 'llm') */ - step_type?: string | null; - /** @description Filter by stage ('pre' or 'post') */ - stage?: string | null; - /** @description Filter by execution ('server' or 'sdk') */ - execution?: string | null; - /** @description Filter by tag */ - tag?: string | null; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of controls */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ListControlsResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - create_control_api_v1_controls_put: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["CreateControlRequest"]; - }; - }; - responses: { - /** @description Created control ID */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["CreateControlResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - validate_control_data_api_v1_controls_validate_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["ValidateControlDataRequest"]; - }; - }; - responses: { - /** @description Validation result */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ValidateControlDataResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_control_api_v1_controls__control_id__get: { - parameters: { - query?: never; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Control metadata and configuration */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetControlResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - delete_control_api_v1_controls__control_id__delete: { - parameters: { - query?: { - /** @description If true, dissociate from all policies before deleting. If false, fail if control is associated with any policy. */ - force?: boolean; - }; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Deletion confirmation with dissociation info */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DeleteControlResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - patch_control_api_v1_controls__control_id__patch: { - parameters: { - query?: never; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["PatchControlRequest"]; - }; - }; - responses: { - /** @description Updated control information */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["PatchControlResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_control_data_api_v1_controls__control_id__data_get: { - parameters: { - query?: never; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Control data payload */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetControlDataResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - set_control_data_api_v1_controls__control_id__data_put: { - parameters: { - query?: never; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["SetControlDataRequest"]; - }; - }; - responses: { - /** @description Success confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["SetControlDataResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - evaluate_api_v1_evaluation_post: { - parameters: { - query?: never; - header?: { - "X-Trace-Id"?: string | null; - "X-Span-Id"?: string | null; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["EvaluationRequest"]; - }; - }; - responses: { - /** @description Safety analysis result */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["EvaluationResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - list_evaluator_configs_api_v1_evaluator_configs_get: { - parameters: { - query?: { - /** @description Evaluator config ID to start after */ - cursor?: number | null; - limit?: number; - /** @description Filter by name (partial, case-insensitive) */ - name?: string | null; - /** @description Filter by evaluator name */ - evaluator?: string | null; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of evaluator configs */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ListEvaluatorConfigsResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - create_evaluator_config_api_v1_evaluator_configs_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["CreateEvaluatorConfigRequest"]; - }; - }; - responses: { - /** @description Created evaluator config */ - 201: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["EvaluatorConfigItem"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_evaluator_config_api_v1_evaluator_configs__config_id__get: { - parameters: { - query?: never; - header?: never; - path: { - config_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Evaluator config details */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["EvaluatorConfigItem"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - update_evaluator_config_api_v1_evaluator_configs__config_id__put: { - parameters: { - query?: never; - header?: never; - path: { - config_id: number; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["UpdateEvaluatorConfigRequest"]; - }; - }; - responses: { - /** @description Updated evaluator config */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["EvaluatorConfigItem"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - delete_evaluator_config_api_v1_evaluator_configs__config_id__delete: { - parameters: { - query?: never; - header?: never; - path: { - config_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Deletion confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DeleteEvaluatorConfigResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_evaluators_api_v1_evaluators_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Dictionary of evaluator name to evaluator info */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - [key: string]: components["schemas"]["EvaluatorInfo"]; - }; - }; - }; - }; - }; - ingest_events_api_v1_observability_events_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["BatchEventsRequest"]; - }; - }; - responses: { - /** @description Successful Response */ - 202: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["BatchEventsResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - query_events_api_v1_observability_events_query_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["EventQueryRequest"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["EventQueryResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_stats_api_v1_observability_stats_get: { - parameters: { - query: { - agent_name: string; - time_range?: "1m" | "5m" | "15m" | "1h" | "24h" | "7d" | "30d" | "180d" | "365d"; - include_timeseries?: boolean; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["StatsResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_control_stats_api_v1_observability_stats_controls__control_id__get: { - parameters: { - query: { - agent_name: string; - time_range?: "1m" | "5m" | "15m" | "1h" | "24h" | "7d" | "30d" | "180d" | "365d"; - include_timeseries?: boolean; - }; - header?: never; - path: { - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ControlStatsResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_status_api_v1_observability_status_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; - }; - }; - create_policy_api_v1_policies_put: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["CreatePolicyRequest"]; - }; - }; - responses: { - /** @description Created policy ID */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["CreatePolicyResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - list_policy_controls_api_v1_policies__policy_id__controls_get: { - parameters: { - query?: never; - header?: never; - path: { - policy_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of control IDs */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetPolicyControlsResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - add_control_to_policy_api_v1_policies__policy_id__controls__control_id__post: { - parameters: { - query?: never; - header?: never; - path: { - policy_id: number; - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Success confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["AssocResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - remove_control_from_policy_api_v1_policies__policy_id__controls__control_id__delete: { - parameters: { - query?: never; - header?: never; - path: { - policy_id: number; - control_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Success confirmation */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["AssocResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - health_check_health_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Server health status */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HealthResponse"]; - }; - }; + list_agents_api_v1_agents_get: { + parameters: { + query?: { + cursor?: string | null; + limit?: number; + name?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of agent summaries */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ListAgentsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + init_agent_api_v1_agents_initAgent_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['InitAgentRequest']; + }; + }; + responses: { + /** @description Agent registration status with active controls */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['InitAgentResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_agent_api_v1_agents__agent_name__get: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Agent metadata and registered steps */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['GetAgentResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + patch_agent_api_v1_agents__agent_name__patch: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PatchAgentRequest']; + }; + }; + responses: { + /** @description Lists of removed items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PatchAgentResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + list_agent_controls_api_v1_agents__agent_name__controls_get: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of controls from agent's policy */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['AgentControlsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + list_agent_evaluators_api_v1_agents__agent_name__evaluators_get: { + parameters: { + query?: { + cursor?: string | null; + limit?: number; + }; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Evaluator schemas registered with this agent */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ListEvaluatorsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_agent_evaluator_api_v1_agents__agent_name__evaluators__evaluator_name__get: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + evaluator_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Evaluator schema details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EvaluatorSchemaItem']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_agent_policy_api_v1_agents__agent_name__policy_get: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Policy ID */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['GetPolicyResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + delete_agent_policy_api_v1_agents__agent_name__policy_delete: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['DeletePolicyResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + set_agent_policy_api_v1_agents__agent_name__policy__policy_id__post: { + parameters: { + query?: never; + header?: never; + path: { + agent_name: string; + policy_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success status with previous policy ID */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SetPolicyResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + list_controls_api_v1_controls_get: { + parameters: { + query?: { + /** @description Control ID to start after */ + cursor?: number | null; + limit?: number; + /** @description Filter by name (partial, case-insensitive) */ + name?: string | null; + /** @description Filter by enabled status */ + enabled?: boolean | null; + /** @description Filter by step type (built-ins: 'tool', 'llm') */ + step_type?: string | null; + /** @description Filter by stage ('pre' or 'post') */ + stage?: string | null; + /** @description Filter by execution ('server' or 'sdk') */ + execution?: string | null; + /** @description Filter by tag */ + tag?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of controls */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ListControlsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + create_control_api_v1_controls_put: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['CreateControlRequest']; + }; + }; + responses: { + /** @description Created control ID */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CreateControlResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + validate_control_data_api_v1_controls_validate_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ValidateControlDataRequest']; + }; + }; + responses: { + /** @description Validation result */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ValidateControlDataResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_control_api_v1_controls__control_id__get: { + parameters: { + query?: never; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Control metadata and configuration */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['GetControlResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + delete_control_api_v1_controls__control_id__delete: { + parameters: { + query?: { + /** @description If true, dissociate from all policies before deleting. If false, fail if control is associated with any policy. */ + force?: boolean; + }; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Deletion confirmation with dissociation info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['DeleteControlResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + patch_control_api_v1_controls__control_id__patch: { + parameters: { + query?: never; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PatchControlRequest']; + }; + }; + responses: { + /** @description Updated control information */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PatchControlResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_control_data_api_v1_controls__control_id__data_get: { + parameters: { + query?: never; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Control data payload */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['GetControlDataResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + set_control_data_api_v1_controls__control_id__data_put: { + parameters: { + query?: never; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['SetControlDataRequest']; + }; + }; + responses: { + /** @description Success confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SetControlDataResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + evaluate_api_v1_evaluation_post: { + parameters: { + query?: never; + header?: { + 'X-Trace-Id'?: string | null; + 'X-Span-Id'?: string | null; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['EvaluationRequest']; + }; + }; + responses: { + /** @description Safety analysis result */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EvaluationResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + list_evaluator_configs_api_v1_evaluator_configs_get: { + parameters: { + query?: { + /** @description Evaluator config ID to start after */ + cursor?: number | null; + limit?: number; + /** @description Filter by name (partial, case-insensitive) */ + name?: string | null; + /** @description Filter by evaluator name */ + evaluator?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of evaluator configs */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ListEvaluatorConfigsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + create_evaluator_config_api_v1_evaluator_configs_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['CreateEvaluatorConfigRequest']; + }; + }; + responses: { + /** @description Created evaluator config */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EvaluatorConfigItem']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_evaluator_config_api_v1_evaluator_configs__config_id__get: { + parameters: { + query?: never; + header?: never; + path: { + config_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Evaluator config details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EvaluatorConfigItem']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + update_evaluator_config_api_v1_evaluator_configs__config_id__put: { + parameters: { + query?: never; + header?: never; + path: { + config_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['UpdateEvaluatorConfigRequest']; + }; + }; + responses: { + /** @description Updated evaluator config */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EvaluatorConfigItem']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + delete_evaluator_config_api_v1_evaluator_configs__config_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + config_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Deletion confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['DeleteEvaluatorConfigResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_evaluators_api_v1_evaluators_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Dictionary of evaluator name to evaluator info */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + [key: string]: components['schemas']['EvaluatorInfo']; + }; + }; + }; + }; + }; + ingest_events_api_v1_observability_events_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['BatchEventsRequest']; + }; + }; + responses: { + /** @description Successful Response */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['BatchEventsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + query_events_api_v1_observability_events_query_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['EventQueryRequest']; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EventQueryResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_stats_api_v1_observability_stats_get: { + parameters: { + query: { + agent_name: string; + time_range?: + | '1m' + | '5m' + | '15m' + | '1h' + | '24h' + | '7d' + | '30d' + | '180d' + | '365d'; + include_timeseries?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['StatsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_control_stats_api_v1_observability_stats_controls__control_id__get: { + parameters: { + query: { + agent_name: string; + time_range?: + | '1m' + | '5m' + | '15m' + | '1h' + | '24h' + | '7d' + | '30d' + | '180d' + | '365d'; + include_timeseries?: boolean; + }; + header?: never; + path: { + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ControlStatsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_status_api_v1_observability_status_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + [key: string]: unknown; + }; + }; + }; + }; + }; + create_policy_api_v1_policies_put: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['CreatePolicyRequest']; + }; + }; + responses: { + /** @description Created policy ID */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CreatePolicyResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + list_policy_controls_api_v1_policies__policy_id__controls_get: { + parameters: { + query?: never; + header?: never; + path: { + policy_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of control IDs */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['GetPolicyControlsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + add_control_to_policy_api_v1_policies__policy_id__controls__control_id__post: { + parameters: { + query?: never; + header?: never; + path: { + policy_id: number; + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['AssocResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + remove_control_from_policy_api_v1_policies__policy_id__controls__control_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + policy_id: number; + control_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success confirmation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['AssocResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + health_check_health_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Server health status */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HealthResponse']; }; + }; }; + }; } From 8fe0df389252fa11a7fb6603dcfae4aceed97aa7 Mon Sep 17 00:00:00 2001 From: Lev Neiman Date: Wed, 25 Feb 2026 17:48:09 -0500 Subject: [PATCH 3/6] test(ui): align integration tests with name-native agent ids --- ui/tests/agent-detail.spec.ts | 4 ++-- ui/tests/agent-stats.spec.ts | 16 ++++++++-------- ui/tests/control-store.spec.ts | 10 +++++----- ui/tests/evaluators/helpers.ts | 2 +- ui/tests/search-input.spec.ts | 12 ++++++------ 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/ui/tests/agent-detail.spec.ts b/ui/tests/agent-detail.spec.ts index ffdc39cd..938dec8a 100644 --- a/ui/tests/agent-detail.spec.ts +++ b/ui/tests/agent-detail.spec.ts @@ -3,7 +3,7 @@ import type { AgentControlsResponse, GetAgentResponse } from '@/core/api/types'; import { expect, mockData, mockRoutes, test } from './fixtures'; test.describe('Agent Detail Page', () => { - const agentId = 'agent-1'; + const agentId = 'customer-support-bot'; const agentUrl = `/agents/${agentId}/controls`; // Type-safe access to mock agent data @@ -638,7 +638,7 @@ test.describe('Agent Detail - Empty State', () => { }); }); - await page.goto('/agents/agent-1/controls'); + await page.goto('/agents/customer-support-bot/controls'); // Check for empty state message await expect(page.getByText('No controls configured')).toBeVisible(); diff --git a/ui/tests/agent-stats.spec.ts b/ui/tests/agent-stats.spec.ts index 4d293fba..5ebac0e7 100644 --- a/ui/tests/agent-stats.spec.ts +++ b/ui/tests/agent-stats.spec.ts @@ -3,10 +3,10 @@ import { expect, mockData, mockRoutes, test } from './fixtures'; test.describe('Agent Monitor Tab', () => { test.beforeEach(async ({ mockedPage }) => { // Navigate to agent detail page - await mockedPage.goto('/agents/agent-1/monitor'); + await mockedPage.goto('/agents/customer-support-bot/monitor'); // Wait for the page to load await expect( - mockedPage.getByRole('heading', { name: 'Customer Support Bot' }) + mockedPage.getByRole('heading', { name: 'customer-support-bot' }) ).toBeVisible(); }); @@ -148,9 +148,9 @@ test.describe('Agent Monitor Tab - Empty State', () => { await mockRoutes.stats(page, { data: mockData.emptyStats }); // Navigate to agent detail page - await page.goto('/agents/agent-1/monitor'); + await page.goto('/agents/customer-support-bot/monitor'); await expect( - page.getByRole('heading', { name: 'Customer Support Bot' }) + page.getByRole('heading', { name: 'customer-support-bot' }) ).toBeVisible(); // Navigate to stats tab @@ -235,9 +235,9 @@ test.describe('Agent Monitor Tab - Refetch Flow', () => { }); // Navigate to agent detail page - await page.goto('/agents/agent-1/monitor'); + await page.goto('/agents/customer-support-bot/monitor'); await expect( - page.getByRole('heading', { name: 'Customer Support Bot' }) + page.getByRole('heading', { name: 'customer-support-bot' }) ).toBeVisible(); // Navigate to stats tab @@ -272,9 +272,9 @@ test.describe('Agent Monitor Tab - Error State', () => { }); // Navigate to agent detail page - await page.goto('/agents/agent-1/monitor'); + await page.goto('/agents/customer-support-bot/monitor'); await expect( - page.getByRole('heading', { name: 'Customer Support Bot' }) + page.getByRole('heading', { name: 'customer-support-bot' }) ).toBeVisible(); // Navigate to stats tab diff --git a/ui/tests/control-store.spec.ts b/ui/tests/control-store.spec.ts index 03f9c2bf..ed984ed4 100644 --- a/ui/tests/control-store.spec.ts +++ b/ui/tests/control-store.spec.ts @@ -2,7 +2,7 @@ import type { Page } from '@playwright/test'; import { expect, mockData, test } from './fixtures'; -const agentUrl = '/agents/agent-1/controls'; +const agentUrl = '/agents/customer-support-bot/controls'; async function openControlStoreModal(page: Page) { await page.goto(agentUrl); @@ -61,15 +61,15 @@ test.describe('Control Store Modal', () => { test('displays agent links in Agent column', async ({ mockedPage }) => { const modal = await openControlStoreModal(mockedPage); - // PII Detection is used by Customer Support Bot + // PII Detection is used by customer-support-bot const agentLink = modal - .getByRole('link', { name: 'Customer Support Bot' }) + .getByRole('link', { name: 'customer-support-bot' }) .first(); await expect(agentLink).toBeVisible(); // Link includes query param to filter by control name await expect(agentLink).toHaveAttribute( 'href', - '/agents/agent-1/controls?q=PII%20Detection' + '/agents/customer-support-bot/controls?q=PII%20Detection' ); }); @@ -720,7 +720,7 @@ test.describe('Control Store - Loading States', () => { }); }); - await page.goto('/agents/agent-1/controls'); + await page.goto('/agents/customer-support-bot/controls'); // Open the control store modal await page.getByTestId('add-control-button').first().click(); diff --git a/ui/tests/evaluators/helpers.ts b/ui/tests/evaluators/helpers.ts index 40e38037..0ddd940a 100644 --- a/ui/tests/evaluators/helpers.ts +++ b/ui/tests/evaluators/helpers.ts @@ -4,7 +4,7 @@ import { expect, type Page } from '@playwright/test'; -const AGENT_URL = '/agents/agent-1/controls'; +const AGENT_URL = '/agents/customer-support-bot/controls'; /** * Opens the control store and selects an evaluator to create a new control diff --git a/ui/tests/search-input.spec.ts b/ui/tests/search-input.spec.ts index 61a6bdeb..4ec1f8d2 100644 --- a/ui/tests/search-input.spec.ts +++ b/ui/tests/search-input.spec.ts @@ -38,7 +38,7 @@ test.describe('SearchInput - Query Param Syncing', () => { await expect(searchInput).toHaveValue('Customer'); // Verify filtered results are shown - await expect(mockedPage.getByText('Customer Support Bot')).toBeVisible(); + await expect(mockedPage.getByText('customer-support-bot')).toBeVisible(); }); test('clear button removes query param from URL', async ({ mockedPage }) => { @@ -87,8 +87,8 @@ test.describe('SearchInput - Query Param Syncing', () => { ); // Navigate away - await mockedPage.getByText('Customer Support Bot').click(); - await expect(mockedPage).toHaveURL(/\/agents\/agent-1/); + await mockedPage.getByText('customer-support-bot').click(); + await expect(mockedPage).toHaveURL(/\/agents\/customer-support-bot/); // Go back await mockedPage.goBack(); @@ -105,13 +105,13 @@ test.describe('SearchInput - Query Param Syncing', () => { await expect(searchInputAfterBack).toHaveValue('Customer'); // Verify filtered results are still shown - await expect(mockedPage.getByText('Customer Support Bot')).toBeVisible(); + await expect(mockedPage.getByText('customer-support-bot')).toBeVisible(); }); }); test.describe('SearchInput - Agent Detail Page', () => { test('syncs search value to URL query param (q)', async ({ mockedPage }) => { - await mockedPage.goto('/agents/agent-1/controls'); + await mockedPage.goto('/agents/customer-support-bot/controls'); const searchInput = mockedPage.getByPlaceholder('Search controls...'); await searchInput.fill('PII'); @@ -124,7 +124,7 @@ test.describe('SearchInput - Agent Detail Page', () => { }); test('reads search value from URL on page load', async ({ mockedPage }) => { - await mockedPage.goto('/agents/agent-1/controls?q=PII'); + await mockedPage.goto('/agents/customer-support-bot/controls?q=PII'); // Wait for page to load await expect(mockedPage.getByRole('table')).toBeVisible(); From b48a61f2da8ad99658ec9c94abb53bb8791660e6 Mon Sep 17 00:00:00 2001 From: Lev Neiman Date: Wed, 25 Feb 2026 17:58:07 -0500 Subject: [PATCH 4/6] refactor(server): replace edited history with autogenerated migration --- ...7fe_name_native_agent_identity_hard_cut.py | 39 +++++++++++++++++++ .../alembic/versions/9f2c5f7a722f_initial.py | 4 +- .../versions/simplify_observability_schema.py | 6 +-- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 server/alembic/versions/58920e6807fe_name_native_agent_identity_hard_cut.py diff --git a/server/alembic/versions/58920e6807fe_name_native_agent_identity_hard_cut.py b/server/alembic/versions/58920e6807fe_name_native_agent_identity_hard_cut.py new file mode 100644 index 00000000..eb83e9b6 --- /dev/null +++ b/server/alembic/versions/58920e6807fe_name_native_agent_identity_hard_cut.py @@ -0,0 +1,39 @@ +""" +Revision ID: 58920e6807fe +Revises: d2f4a6b8c9d0 +Create Date: 2026-02-25 17:56:34.057000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '58920e6807fe' +down_revision = 'd2f4a6b8c9d0' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(op.f('agents_name_key'), 'agents', type_='unique') + op.drop_column('agents', 'agent_uuid') + op.add_column('control_execution_events', sa.Column('agent_name', sa.String(length=255), nullable=False)) + op.drop_index(op.f('ix_events_data_control_id'), table_name='control_execution_events') + op.drop_index(op.f('ix_events_agent_time'), table_name='control_execution_events') + op.create_index('ix_events_agent_time', 'control_execution_events', ['agent_name', sa.literal_column('timestamp DESC')], unique=False) + op.drop_column('control_execution_events', 'agent_uuid') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('control_execution_events', sa.Column('agent_uuid', sa.UUID(), autoincrement=False, nullable=False)) + op.drop_index('ix_events_agent_time', table_name='control_execution_events') + op.create_index(op.f('ix_events_agent_time'), 'control_execution_events', ['agent_uuid', sa.literal_column('timestamp DESC')], unique=False) + op.create_index(op.f('ix_events_data_control_id'), 'control_execution_events', [sa.literal_column("(data ->> 'control_id'::text)")], unique=False) + op.drop_column('control_execution_events', 'agent_name') + op.add_column('agents', sa.Column('agent_uuid', sa.UUID(), autoincrement=False, nullable=False)) + op.create_unique_constraint(op.f('agents_name_key'), 'agents', ['name'], postgresql_nulls_not_distinct=False) + # ### end Alembic commands ### diff --git a/server/alembic/versions/9f2c5f7a722f_initial.py b/server/alembic/versions/9f2c5f7a722f_initial.py index d395074d..f7574331 100644 --- a/server/alembic/versions/9f2c5f7a722f_initial.py +++ b/server/alembic/versions/9f2c5f7a722f_initial.py @@ -37,12 +37,14 @@ def upgrade() -> None: sa.UniqueConstraint('name') ) op.create_table('agents', + sa.Column('agent_uuid', sa.UUID(), nullable=False), sa.Column('name', sa.String(length=255), nullable=False), sa.Column('data', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False), sa.Column('policy_id', sa.Integer(), nullable=True), sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), sa.ForeignKeyConstraint(['policy_id'], ['policies.id'], ), - sa.PrimaryKeyConstraint('name') + sa.PrimaryKeyConstraint('agent_uuid'), + sa.UniqueConstraint('name') ) op.create_index(op.f('ix_agents_created_at'), 'agents', ['created_at'], unique=False) op.create_index(op.f('ix_agents_policy_id'), 'agents', ['policy_id'], unique=False) diff --git a/server/alembic/versions/simplify_observability_schema.py b/server/alembic/versions/simplify_observability_schema.py index 0b58ca0d..84612a02 100644 --- a/server/alembic/versions/simplify_observability_schema.py +++ b/server/alembic/versions/simplify_observability_schema.py @@ -9,7 +9,7 @@ 2. Recreates control_execution_events with minimal schema: - control_execution_id (PK) - timestamp (indexed) - - agent_name (indexed) + - agent_uuid (indexed) - data (JSONB containing full event) 3. Creates optimized indexes for query-time aggregation @@ -53,7 +53,7 @@ def upgrade() -> None: server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False, ), - sa.Column("agent_name", sa.String(length=255), nullable=False), + sa.Column("agent_uuid", sa.UUID(), nullable=False), sa.Column("data", postgresql.JSONB(astext_type=sa.Text()), nullable=False), ) @@ -61,7 +61,7 @@ def upgrade() -> None: op.create_index( "ix_events_agent_time", "control_execution_events", - ["agent_name", sa.text("timestamp DESC")], + ["agent_uuid", sa.text("timestamp DESC")], ) # 5. Create expression index on control_id from JSONB for grouping From 4b8c91738b3b42851cd1f2e9aa23f4c3e83db89a Mon Sep 17 00:00:00 2001 From: Lev Neiman Date: Wed, 25 Feb 2026 18:05:09 -0500 Subject: [PATCH 5/6] test: cover remaining name-native patch branches --- sdks/python/tests/test_agent_id_validation.py | 36 +++++++++++++++++++ sdks/python/tests/test_evaluation.py | 36 +++++++++++++++++++ sdks/python/tests/test_init_step_merge.py | 24 +++++++++++++ server/tests/test_controls_additional.py | 35 ++++++++++++++++++ 4 files changed, 131 insertions(+) diff --git a/sdks/python/tests/test_agent_id_validation.py b/sdks/python/tests/test_agent_id_validation.py index 8bc3dd69..20277e22 100644 --- a/sdks/python/tests/test_agent_id_validation.py +++ b/sdks/python/tests/test_agent_id_validation.py @@ -50,6 +50,20 @@ async def test_remove_agent_policy_rejects_invalid_agent_name() -> None: client.http_client.delete.assert_not_called() +@pytest.mark.asyncio +async def test_list_agents_normalizes_cursor() -> None: + client = MagicMock() + client.http_client = MagicMock() + client.http_client.get = AsyncMock(return_value=DummyResponse()) + + await agents.list_agents(client, cursor="Agent-Example_01", limit=5) + + client.http_client.get.assert_awaited_once_with( + "/api/v1/agents", + params={"limit": 5, "cursor": "agent-example_01"}, + ) + + @pytest.mark.asyncio async def test_assign_policy_rejects_invalid_agent_name() -> None: client = MagicMock() @@ -72,3 +86,25 @@ async def test_get_agent_normalizes_agent_name() -> None: await agents.get_agent(client, agent_name) client.http_client.get.assert_awaited_once_with("/api/v1/agents/agent-example_01") + + +@pytest.mark.asyncio +async def test_get_agent_policy_normalizes_agent_name() -> None: + client = MagicMock() + client.http_client = MagicMock() + client.http_client.get = AsyncMock(return_value=DummyResponse()) + + await agents.get_agent_policy(client, "Agent-Example_01") + + client.http_client.get.assert_awaited_once_with("/api/v1/agents/agent-example_01/policy") + + +@pytest.mark.asyncio +async def test_remove_agent_policy_normalizes_agent_name() -> None: + client = MagicMock() + client.http_client = MagicMock() + client.http_client.delete = AsyncMock(return_value=DummyResponse()) + + await agents.remove_agent_policy(client, "Agent-Example_01") + + client.http_client.delete.assert_awaited_once_with("/api/v1/agents/agent-example_01/policy") diff --git a/sdks/python/tests/test_evaluation.py b/sdks/python/tests/test_evaluation.py index 7e1fd6a7..f1c8c8a5 100644 --- a/sdks/python/tests/test_evaluation.py +++ b/sdks/python/tests/test_evaluation.py @@ -26,3 +26,39 @@ async def test_check_evaluation_requires_step_name_without_models(monkeypatch): ) client.http_client.post.assert_not_called() + + +@pytest.mark.asyncio +async def test_check_evaluation_returns_runtime_fallback_without_models(monkeypatch): + """Fallback path should return an object with expected attributes.""" + monkeypatch.setattr(evaluation, "MODELS_AVAILABLE", False) + + class DummyResponse: + def raise_for_status(self) -> None: + return None + + def json(self) -> dict[str, object]: + return {"is_safe": True, "confidence": 0.75, "reason": "ok"} + + client = MagicMock() + client.http_client = MagicMock() + client.http_client.post = AsyncMock(return_value=DummyResponse()) + + result = await evaluation.check_evaluation( + client=client, + agent_name="Agent-Example_01", + step={"type": "llm", "name": "chat", "input": "hello"}, + stage="pre", + ) + + assert result.is_safe is True + assert result.confidence == 0.75 + assert result.reason == "ok" + client.http_client.post.assert_awaited_once_with( + "/api/v1/evaluation", + json={ + "agent_name": "agent-example_01", + "step": {"type": "llm", "name": "chat", "input": "hello"}, + "stage": "pre", + }, + ) diff --git a/sdks/python/tests/test_init_step_merge.py b/sdks/python/tests/test_init_step_merge.py index aab24fff..142c1fcf 100644 --- a/sdks/python/tests/test_init_step_merge.py +++ b/sdks/python/tests/test_init_step_merge.py @@ -162,6 +162,30 @@ async def unresolved(payload: DoesNotExist) -> str: assert "failed to resolve type hints" in caplog.text +def test_init_logs_agent_updated_when_registration_already_exists( + caplog: pytest.LogCaptureFixture, +) -> None: + # Given a server response indicating this init call updated an existing agent. + agent_name = f"agent-{uuid4().hex[:12]}" + register_agent_mock = AsyncMock(return_value={"created": False, "controls": []}) + health_check_mock = AsyncMock(return_value={"status": "healthy"}) + + # When init() runs registration. + with patch( + "agent_control.__init__.AgentControlClient.health_check", + new=health_check_mock, + ), patch( + "agent_control.__init__.agents.register_agent", + new=register_agent_mock, + ): + with caplog.at_level(logging.INFO): + agent_control.init(agent_name=agent_name) + + # Then the SDK emits the "updated" log branch. + assert "Agent updated" in caplog.text + assert agent_name in caplog.text + + @pytest.mark.asyncio async def test_refresh_controls_uses_strict_conflict_mode() -> None: # Given: an initialized SDK agent session with network-facing calls mocked. diff --git a/server/tests/test_controls_additional.py b/server/tests/test_controls_additional.py index 22967f17..4be88a31 100644 --- a/server/tests/test_controls_additional.py +++ b/server/tests/test_controls_additional.py @@ -358,6 +358,41 @@ def test_list_controls_cursor_with_name_and_enabled_filters(client: TestClient) assert page2["controls"][0]["enabled"] is True +def test_list_controls_includes_used_by_agent_mapping(client: TestClient) -> None: + # Given: one control linked through Policy -> Agent + control_id, control_name = _create_control(client, name=f"Mapped-{uuid.uuid4()}") + _set_control_data(client, control_id, deepcopy(VALID_CONTROL_PAYLOAD)) + + policy_name = f"pol-{uuid.uuid4()}" + policy_resp = client.put("/api/v1/policies", json={"name": policy_name}) + assert policy_resp.status_code == 200 + policy_id = policy_resp.json()["policy_id"] + + assoc_resp = client.post(f"/api/v1/policies/{policy_id}/controls/{control_id}") + assert assoc_resp.status_code == 200 + + agent_name = f"agent-{uuid.uuid4().hex[:12]}" + init_resp = client.post( + "/api/v1/agents/initAgent", + json={"agent": {"agent_name": agent_name}, "steps": []}, + ) + assert init_resp.status_code == 200 + + assign_resp = client.post(f"/api/v1/agents/{agent_name}/policy/{policy_id}") + assert assign_resp.status_code == 200 + + # When: listing controls + resp = client.get("/api/v1/controls", params={"name": "mapped"}) + assert resp.status_code == 200 + controls = resp.json()["controls"] + + # Then: used_by_agent is populated from the join traversal + assert len(controls) == 1 + assert controls[0]["id"] == control_id + assert controls[0]["name"] == control_name + assert controls[0]["used_by_agent"] == {"agent_name": agent_name} + + def test_delete_control_force_dissociates(client: TestClient) -> None: # Given: a control associated with a policy control_id, _ = _create_control(client) From 1c3b6c2b4115b605dae234f56771d0391a88da6b Mon Sep 17 00:00:00 2001 From: Lev Neiman Date: Wed, 25 Feb 2026 18:41:24 -0500 Subject: [PATCH 6/6] fix(server): harden name identity constraints and normalization --- ...7fe_name_native_agent_identity_hard_cut.py | 17 ++++- .../agent_control_server/endpoints/agents.py | 11 ++- .../endpoints/observability.py | 3 + server/src/agent_control_server/models.py | 14 +++- .../services/agent_names.py | 35 ++++++++++ server/tests/test_init_agent.py | 68 +++++++++++++++++++ server/tests/test_observability_endpoints.py | 21 ++++++ 7 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 server/src/agent_control_server/services/agent_names.py diff --git a/server/alembic/versions/58920e6807fe_name_native_agent_identity_hard_cut.py b/server/alembic/versions/58920e6807fe_name_native_agent_identity_hard_cut.py index eb83e9b6..26952e4c 100644 --- a/server/alembic/versions/58920e6807fe_name_native_agent_identity_hard_cut.py +++ b/server/alembic/versions/58920e6807fe_name_native_agent_identity_hard_cut.py @@ -19,10 +19,16 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_constraint(op.f('agents_name_key'), 'agents', type_='unique') op.drop_column('agents', 'agent_uuid') + op.create_primary_key('agents_pkey', 'agents', ['name']) + op.create_check_constraint('ck_agents_name_min_length', 'agents', 'char_length(name) >= 10') + op.create_check_constraint('ck_agents_name_format', 'agents', "name ~ '^[a-z0-9:_-]+$'") op.add_column('control_execution_events', sa.Column('agent_name', sa.String(length=255), nullable=False)) - op.drop_index(op.f('ix_events_data_control_id'), table_name='control_execution_events') op.drop_index(op.f('ix_events_agent_time'), table_name='control_execution_events') op.create_index('ix_events_agent_time', 'control_execution_events', ['agent_name', sa.literal_column('timestamp DESC')], unique=False) + op.execute(""" + CREATE INDEX IF NOT EXISTS ix_events_data_control_id + ON control_execution_events ((data ->> 'control_id'::text)) + """) op.drop_column('control_execution_events', 'agent_uuid') # ### end Alembic commands ### @@ -32,8 +38,15 @@ def downgrade() -> None: op.add_column('control_execution_events', sa.Column('agent_uuid', sa.UUID(), autoincrement=False, nullable=False)) op.drop_index('ix_events_agent_time', table_name='control_execution_events') op.create_index(op.f('ix_events_agent_time'), 'control_execution_events', ['agent_uuid', sa.literal_column('timestamp DESC')], unique=False) - op.create_index(op.f('ix_events_data_control_id'), 'control_execution_events', [sa.literal_column("(data ->> 'control_id'::text)")], unique=False) op.drop_column('control_execution_events', 'agent_name') + op.drop_constraint('ck_agents_name_format', 'agents', type_='check') + op.drop_constraint('ck_agents_name_min_length', 'agents', type_='check') + op.drop_constraint('agents_pkey', 'agents', type_='primary') op.add_column('agents', sa.Column('agent_uuid', sa.UUID(), autoincrement=False, nullable=False)) + op.create_primary_key('agents_pkey', 'agents', ['agent_uuid']) op.create_unique_constraint(op.f('agents_name_key'), 'agents', ['name'], postgresql_nulls_not_distinct=False) + op.execute(""" + CREATE INDEX IF NOT EXISTS ix_events_data_control_id + ON control_execution_events ((data ->> 'control_id'::text)) + """) # ### end Alembic commands ### diff --git a/server/src/agent_control_server/endpoints/agents.py b/server/src/agent_control_server/endpoints/agents.py index 8c78bc28..82cecfc9 100644 --- a/server/src/agent_control_server/endpoints/agents.py +++ b/server/src/agent_control_server/endpoints/agents.py @@ -45,6 +45,7 @@ Policy, policy_controls, ) +from ..services.agent_names import normalize_agent_name_or_422 from ..services.controls import list_controls_for_agent, list_controls_for_policy from ..services.evaluator_utils import ( parse_evaluator_ref_full, @@ -273,7 +274,7 @@ async def list_agents( # If cursor provided, filter to get items after the cursor if cursor: - cursor_name = cursor.strip().lower() + cursor_name = normalize_agent_name_or_422(cursor, field_name="cursor") cursor_agent_result = await db.execute( select(Agent).where(Agent.name == cursor_name) ) @@ -764,6 +765,7 @@ async def get_agent(agent_name: str, db: AsyncSession = Depends(get_async_db)) - HTTPException 404: Agent not found HTTPException 422: Agent data is corrupted """ + agent_name = normalize_agent_name_or_422(agent_name) result = await db.execute(select(Agent).where(Agent.name == agent_name)) existing: Agent | None = result.scalars().first() if existing is None: @@ -842,6 +844,7 @@ async def set_agent_policy( HTTPException 404: Agent or policy not found HTTPException 500: Database error during assignment """ + agent_name = normalize_agent_name_or_422(agent_name) # Find agent result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() @@ -930,6 +933,7 @@ async def get_agent_policy( Raises: HTTPException 404: Agent not found or agent has no policy assigned """ + agent_name = normalize_agent_name_or_422(agent_name) # Find agent result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() @@ -994,6 +998,7 @@ async def delete_agent_policy( HTTPException 404: Agent not found or agent has no policy assigned HTTPException 500: Database error during removal """ + agent_name = normalize_agent_name_or_422(agent_name) # Find agent result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() @@ -1059,6 +1064,7 @@ async def list_agent_controls( Raises: HTTPException 404: Agent not found """ + agent_name = normalize_agent_name_or_422(agent_name) result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: @@ -1130,6 +1136,7 @@ async def list_agent_evaluators( Raises: HTTPException 404: Agent not found """ + agent_name = normalize_agent_name_or_422(agent_name) # Clamp limit limit = min(max(1, limit), _MAX_PAGINATION_LIMIT) @@ -1219,6 +1226,7 @@ async def get_agent_evaluator( Raises: HTTPException 404: Agent or evaluator not found """ + agent_name = normalize_agent_name_or_422(agent_name) result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: @@ -1287,6 +1295,7 @@ async def patch_agent( HTTPException 404: Agent not found HTTPException 500: Database error during update """ + agent_name = normalize_agent_name_or_422(agent_name) result = await db.execute(select(Agent).where(Agent.name == agent_name)) agent: Agent | None = result.scalars().first() if agent is None: diff --git a/server/src/agent_control_server/endpoints/observability.py b/server/src/agent_control_server/endpoints/observability.py index ceb928a3..ea127fc3 100644 --- a/server/src/agent_control_server/endpoints/observability.py +++ b/server/src/agent_control_server/endpoints/observability.py @@ -35,6 +35,7 @@ get_bucket_size, parse_time_range, ) +from ..services.agent_names import normalize_agent_name_or_422 logger = logging.getLogger(__name__) @@ -179,6 +180,7 @@ async def get_stats( Returns: StatsResponse with agent-level totals and per-control breakdown """ + agent_name = normalize_agent_name_or_422(agent_name) interval = parse_time_range(time_range) bucket_size = get_bucket_size(time_range) if include_timeseries else None @@ -228,6 +230,7 @@ async def get_control_stats( Returns: ControlStatsResponse with control stats and optional timeseries """ + agent_name = normalize_agent_name_or_422(agent_name) interval = parse_time_range(time_range) bucket_size = get_bucket_size(time_range) if include_timeseries else None diff --git a/server/src/agent_control_server/models.py b/server/src/agent_control_server/models.py index bf842b39..383c7681 100644 --- a/server/src/agent_control_server/models.py +++ b/server/src/agent_control_server/models.py @@ -1,11 +1,12 @@ import datetime as dt from typing import Any, Optional -from agent_control_models.agent import StepSchema +from agent_control_models.agent import StepSchema, normalize_agent_name from agent_control_models.base import BaseModel from agent_control_models.server import EvaluatorSchema from pydantic import Field from sqlalchemy import ( + CheckConstraint, Column, DateTime, ForeignKey, @@ -16,7 +17,7 @@ text, ) from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm import Mapped, mapped_column, relationship, validates from .db import Base @@ -87,6 +88,10 @@ class EvaluatorConfigDB(Base): class Agent(Base): __tablename__ = "agents" + __table_args__ = ( + CheckConstraint("char_length(name) >= 10", name="ck_agents_name_min_length"), + CheckConstraint("name ~ '^[a-z0-9:_-]+$'", name="ck_agents_name_format"), + ) name: Mapped[str] = mapped_column(String(255), primary_key=True) data: Mapped[dict[str, Any]] = mapped_column( @@ -100,6 +105,10 @@ class Agent(Base): DateTime(), server_default=text("CURRENT_TIMESTAMP"), nullable=False, index=True ) + @validates("name") + def _normalize_name(self, _key: str, value: str) -> str: + return normalize_agent_name(value) + # ============================================================================= # Observability Models @@ -143,4 +152,5 @@ class ControlExecutionEventDB(Base): # Composite index for agent + time queries (primary access pattern) __table_args__ = ( Index("ix_events_agent_time", "agent_name", timestamp.desc()), + Index("ix_events_data_control_id", text("(data ->> 'control_id'::text)")), ) diff --git a/server/src/agent_control_server/services/agent_names.py b/server/src/agent_control_server/services/agent_names.py new file mode 100644 index 00000000..606c3707 --- /dev/null +++ b/server/src/agent_control_server/services/agent_names.py @@ -0,0 +1,35 @@ +"""Agent name normalization helpers for server endpoints.""" + +from agent_control_models.agent import normalize_agent_name +from agent_control_models.errors import ErrorCode, ValidationErrorItem + +from ..errors import APIValidationError + + +def normalize_agent_name_or_422( + agent_name: str, + *, + field_name: str = "agent_name", +) -> str: + """Normalize an agent name or raise a standardized 422 validation error.""" + try: + return normalize_agent_name(agent_name) + except ValueError as exc: + raise APIValidationError( + error_code=ErrorCode.VALIDATION_ERROR, + detail="Invalid agent_name", + resource="Agent", + hint=( + "Agent names must be at least 10 characters and may only contain " + "letters, digits, ':', '_' or '-'." + ), + errors=[ + ValidationErrorItem( + resource="Agent", + field=field_name, + code="invalid_format", + message=str(exc), + value=agent_name, + ) + ], + ) from exc diff --git a/server/tests/test_init_agent.py b/server/tests/test_init_agent.py index 55f442b0..2bc46e5e 100644 --- a/server/tests/test_init_agent.py +++ b/server/tests/test_init_agent.py @@ -77,6 +77,49 @@ def test_init_agent_creates_and_gets_agent(client: TestClient) -> None: assert {s["name"] for s in data["steps"]} == {payload["steps"][0]["name"]} +def test_agent_endpoints_normalize_mixed_case_agent_name(client: TestClient) -> None: + # Given: an agent registered with mixed-case identifier + mixed_case_name = "Agent-PathNorm01" + payload = { + "agent": { + "agent_name": mixed_case_name, + "agent_description": "desc", + "agent_version": "1.0", + }, + "steps": [], + } + init_resp = client.post("/api/v1/agents/initAgent", json=payload) + assert init_resp.status_code == 200 + + # When: hitting path-based agent endpoints using mixed-case path params + get_resp = client.get(f"/api/v1/agents/{mixed_case_name}") + assert get_resp.status_code == 200 + assert get_resp.json()["agent"]["agent_name"] == mixed_case_name.lower() + + policy_id = _create_policy(client) + set_policy_resp = client.post(f"/api/v1/agents/{mixed_case_name}/policy/{policy_id}") + assert set_policy_resp.status_code == 200 + + get_policy_resp = client.get(f"/api/v1/agents/{mixed_case_name}/policy") + assert get_policy_resp.status_code == 200 + assert get_policy_resp.json()["policy_id"] == policy_id + + controls_resp = client.get(f"/api/v1/agents/{mixed_case_name}/controls") + assert controls_resp.status_code == 200 + + evaluators_resp = client.get(f"/api/v1/agents/{mixed_case_name}/evaluators") + assert evaluators_resp.status_code == 200 + + patch_resp = client.patch( + f"/api/v1/agents/{mixed_case_name}", + json={"remove_steps": [], "remove_evaluators": []}, + ) + assert patch_resp.status_code == 200 + + delete_policy_resp = client.delete(f"/api/v1/agents/{mixed_case_name}/policy") + assert delete_policy_resp.status_code == 200 + + def test_init_agent_idempotent_same_steps(client: TestClient) -> None: # Given: an init payload payload = make_agent_payload() @@ -571,6 +614,31 @@ def test_list_agents_pagination(client: TestClient) -> None: assert body3["pagination"]["next_cursor"] is None +def test_list_agents_accepts_mixed_case_cursor(client: TestClient) -> None: + # Given: three agents to paginate + for i in range(3): + payload = make_agent_payload(name=f"agent-cursor-{i:02d}") + resp = client.post("/api/v1/agents/initAgent", json=payload) + assert resp.status_code == 200 + + first_page = client.get("/api/v1/agents?limit=1") + assert first_page.status_code == 200 + first_body = first_page.json() + assert first_body["pagination"]["has_more"] is True + assert first_body["pagination"]["next_cursor"] is not None + + mixed_case_cursor = str(first_body["pagination"]["next_cursor"]).upper() + + # When: requesting second page with mixed-case cursor + second_page = client.get(f"/api/v1/agents?limit=1&cursor={mixed_case_cursor}") + assert second_page.status_code == 200 + second_body = second_page.json() + + # Then: cursor is normalized and pagination advances + assert len(second_body["agents"]) == 1 + assert second_body["agents"][0]["agent_name"] != first_body["agents"][0]["agent_name"] + + def test_list_agents_limit_clamping(client: TestClient) -> None: """Test that limit is clamped to valid range.""" # Given: one agent diff --git a/server/tests/test_observability_endpoints.py b/server/tests/test_observability_endpoints.py index aaa9274e..528b3846 100644 --- a/server/tests/test_observability_endpoints.py +++ b/server/tests/test_observability_endpoints.py @@ -220,6 +220,27 @@ async def test_ingest_via_direct_ingestor(self, setup_observability): class TestStatsTimeseries: """Tests for time-series stats functionality.""" + @pytest.mark.asyncio + async def test_stats_normalize_mixed_case_agent_name_query( + self, client: TestClient, setup_observability + ): + """Mixed-case agent_name query params are normalized.""" + store = setup_observability + normalized_name = "agent-statsnorm01" + + event = create_test_event(agent_name=normalized_name, matched=True) + await store.store([event]) + + response = client.get( + "/api/v1/observability/stats", + params={"agent_name": "Agent-StatsNorm01", "time_range": "1h"}, + ) + + assert response.status_code == 200 + body = response.json() + assert body["agent_name"] == normalized_name + assert body["totals"]["execution_count"] == 1 + @pytest.mark.asyncio async def test_stats_without_timeseries(self, client: TestClient, setup_observability): """Default response has no timeseries."""