Traigent Hybrid Mode API (hybrid_api): your agent as an external service¶
This document defines the REST API contract for external services integrating with Traigent's Hybrid Mode optimization. hybrid_api is one of Traigent's three execution modes — see the Execution Modes matrix for how it compares to local and cloud.
Machine-Readable Specifications¶
For code generation and validation, use these schema files:
| Format | File | Description |
|---|---|---|
| OpenAPI 3.0 | hybrid-mode-openapi.yaml | Full API specification with examples |
| JSON Schema | hybrid-mode-schemas.json | DTO definitions for validation |
Using the OpenAPI Spec¶
Generate client code:
# Python client
openapi-generator generate -i docs/hybrid-mode-openapi.yaml -g python -o client/
# TypeScript client
openapi-generator generate -i docs/hybrid-mode-openapi.yaml -g typescript-fetch -o client/
Validate requests/responses:
from jsonschema import validate
import json
with open("docs/hybrid-mode-schemas.json") as f:
schemas = json.load(f)
# Validate an execute response
execute_response_schema = {
"$schema": schemas.get("$schema", "https://json-schema.org/draft/2020-12/schema"),
"$ref": "#/definitions/ExecuteResponse",
"definitions": schemas["definitions"],
}
validate(response_data, execute_response_schema)
Publishing and Versioning (Swagger)¶
Use docs/hybrid-mode-openapi.yaml as the single source of truth and publish it via Swagger UI (or Redoc) instead of ad-hoc zip bundles.
- Keep
info.versionin the OpenAPI file under semantic versioning (MAJOR.MINOR.PATCH). - Publish each released spec from git tags (for example
v1.0.0,v1.0.1). - Run validation in CI before publish:
npx @apidevtools/swagger-cli validate docs/hybrid-mode-openapi.yamlnpx @redocly/cli lint docs/hybrid-mode-openapi.yaml
Overview¶
Hybrid Mode allows Traigent to optimize external agentic services by: 1. Discovering tunable parameters via REST API 2. Executing the agent with different configurations 3. Evaluating outputs to measure quality 4. Finding optimal configurations based on objectives
Base URL¶
All endpoints use the prefix /traigent/v1/.
Example: http://your-service:8080/traigent/v1/capabilities
Authentication (Optional)¶
If your service requires authentication, accept the configured token in both
Authorization and x-api-key headers. The SDK sets both headers from
HybridAPIOptions.auth_header for compatibility.
Configure the Traigent SDK client with:
- ExternalServiceEvaluator(hybrid_api=HybridAPIOptions(auth_header="Bearer <token>"))
For current contract compatibility, keep auth policy consistent across all endpoints. RBAC and stronger policy models can be layered later without changing endpoint shapes.
Privacy-Preserving Mode (Default)¶
Traigent Hybrid Mode is designed with privacy as the default. Only configuration values and metrics are observed during optimization - the actual content of inputs, outputs, and targets is never transmitted to Traigent.
How It Works¶
- Inputs: Only
example_idis required. Your service stores input data locally and looks it up by ID. - Outputs: Return
output_idinstead ofoutputcontent. Your service stores outputs locally. - Evaluation: Use
output_idandtarget_idinstead of actual content. Your service performs evaluation locally.
What Traigent Observes¶
| Data | Observed by Traigent |
|---|---|
| Tunable definitions (config-space) | ✓ Yes |
| Configuration values per trial | ✓ Yes |
| Operational metrics (cost, latency, tokens) | ✓ Yes |
| Quality metrics (accuracy, relevance, etc.) | ✓ Yes |
| Input data content | ✗ No (only IDs) |
| Output data content | ✗ No (only IDs) |
| Target/expected output content | ✗ No (only IDs) |
Session and ID Management¶
For privacy-preserving mode, your service must:
- Generate unique IDs: Create stable identifiers for inputs, outputs, and targets
- Track session context: Use
session_idto scope outputs to specific optimization runs - Store data locally: Maintain a local mapping of IDs to actual content
- Handle ID collisions: Different optimization runs may reuse example IDs but produce different outputs - use
session_idto distinguish
Example ID format:
example_id: "example_001" # Stable across runs
output_id: "out_example_001_run_abc123" # Scoped to run
target_id: "target_001" # Stable across runs
Full-Content Datasets (Optional)¶
If privacy is not a concern, you can send full content instead of IDs:
- Include data in ExampleItem
- Include output in OutputItem
- Include output and target in EvaluationItem
Both modes can be mixed — for example, send input data but return only output_id.
Evaluation Mode Selection: Two-Phase vs Combined¶
The SDK automatically selects how evaluation happens based on the execute response. Your service controls this by what it returns in quality_metrics and declares in supports_evaluate.
| Execute Response | Capabilities | SDK Behavior |
|---|---|---|
quality_metrics is non-null (contains data) |
Any | Combined Mode — SDK uses the returned quality metrics directly. /evaluate is NOT called. |
quality_metrics is null or absent |
supports_evaluate: true |
Two-Phase Mode — SDK calls POST /evaluate separately to obtain quality metrics. |
quality_metrics is null or absent |
supports_evaluate: false |
Execute-only — Only operational metrics (cost, latency) are collected. No quality evaluation. |
Combined Mode¶
Return quality metrics directly in the execute response to skip the separate evaluate call:
{
"status": "completed",
"outputs": [
{
"example_id": "ex_001",
"output_id": "out_001_run_abc",
"cost_usd": 0.005,
"metrics": {"accuracy": 0.92, "relevance": 0.88}
}
],
"operational_metrics": {"total_cost_usd": 0.005},
"quality_metrics": {"accuracy": 0.92, "relevance": 0.88}
}
Two-Phase Mode¶
Return quality_metrics: null (or omit it) and set supports_evaluate: true in capabilities. The SDK will call /evaluate after /execute:
{
"status": "completed",
"outputs": [
{
"example_id": "ex_001",
"output_id": "out_001_run_abc",
"cost_usd": 0.005
}
],
"operational_metrics": {"total_cost_usd": 0.005},
"quality_metrics": null
}
The SDK then sends the outputs to POST /evaluate for quality scoring.
Endpoints¶
GET /traigent/v1/capabilities¶
Required: Yes
Returns service capabilities for the initial handshake.
Response (200 OK)¶
{
"version": "1.0",
"supports_evaluate": true,
"supports_keep_alive": false,
"supports_streaming": false,
"max_batch_size": 100,
"max_payload_bytes": null,
"tunable_ids": ["my_agent"]
}
| Field | Type | Required | Description |
|---|---|---|---|
version |
string | Yes | API version (currently "1.0") |
supports_evaluate |
boolean | No | Whether /evaluate endpoint is available (default: false) |
supports_keep_alive |
boolean | No | Whether session heartbeat is supported |
supports_streaming |
boolean | No | Whether streaming responses are supported |
max_batch_size |
integer | No | Maximum inputs per execute request (default: 100) |
max_payload_bytes |
integer | No | Maximum request payload size (null = unlimited) |
tunable_ids |
array[string] | No | Optional list of tunable IDs supported by this service |
Notes:
- tunable_ids is optional and useful for multi-tunable services.
- The canonical tunable used for execution/evaluation remains tunable_id returned by GET /traigent/v1/config-space.
GET /traigent/v1/config-space¶
Required: Yes
Returns tunable variable definitions. Traigent uses these to understand what parameters it can optimize.
Response (200 OK)¶
{
"schema_version": "0.9",
"tunable_id": "my_agent",
"tunables": [
{
"name": "model",
"type": "enum",
"domain": {"values": ["gpt-4o", "claude-3-sonnet", "llama-70b"]},
"default": "gpt-4o"
},
{
"name": "temperature",
"type": "float",
"domain": {"range": [0.0, 2.0], "resolution": 0.1},
"default": 0.7
},
{
"name": "max_tokens",
"type": "int",
"domain": {"range": [100, 4096]},
"default": 1024
},
{
"name": "use_cache",
"type": "bool",
"default": true
}
],
"objectives": [
{"name": "accuracy", "direction": "maximize", "weight": 2.0},
{"name": "cost", "direction": "minimize", "weight": 1.0}
],
"exploration": {
"strategy": "nsga2",
"budgets": {"max_trials": 50, "max_spend_usd": 10.0}
},
"promotion_policy": {
"dominance": "epsilon_pareto",
"alpha": 0.05,
"min_effect": {"accuracy": 0.02}
},
"defaults": {"model": "gpt-4o"},
"measures": ["accuracy", "cost", "latency"]
}
| Field | Type | Required | Description |
|---|---|---|---|
schema_version |
string | Yes | TVL schema version (currently "0.9") |
tunable_id |
string | Yes | Unique identifier for this tunable |
tunables |
array | Yes | List of tunable variable definitions |
constraints |
object | array | No | Structural and behavioral constraints |
objectives |
array | No | Optional objective definitions (TVL 0.9 JSON format) |
exploration |
object | No | Optional exploration strategy, budgets, and convergence |
promotion_policy |
object | No | Optional promotion policy (epsilon-Pareto) |
defaults |
object | No | Optional default configuration values |
measures |
array[string] | No | Optional declared metric names produced by service |
Tunable Definition¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Variable name (valid Python identifier) |
type |
string | Yes | One of: enum, float, int, bool, str |
domain |
object | Conditional | Domain specification (required for enum, float, int, str) |
default |
any | No | Default value |
agent |
string | No | Agent name for multi-agent grouping |
is_tool |
boolean | No | True if this tunable configures an MCP tool |
constraints |
array | No | Conditional constraints (e.g., "requires model == gpt-4.1") |
Tunable Types¶
Enum - Discrete choices:
{
"name": "model",
"type": "enum",
"domain": {"values": ["option_a", "option_b", "option_c"]},
"default": "option_a"
}
Float - Continuous range:
{
"name": "temperature",
"type": "float",
"domain": {
"range": [0.0, 2.0],
"resolution": 0.1
},
"default": 0.7
}
Integer - Discrete range:
{
"name": "max_tokens",
"type": "int",
"domain": {"range": [100, 4096]},
"default": 1024
}
Boolean:
{
"name": "use_cache",
"type": "bool",
"default": true
}
String - Discrete string values:
{
"name": "prompt_style",
"type": "str",
"domain": {"values": ["concise", "detailed", "technical"]},
"default": "concise"
}
POST /traigent/v1/execute¶
Required: Yes
Execute the agent with a specific configuration on a batch of inputs.
Request¶
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"tunable_id": "my_agent",
"config": {
"model": "gpt-4o",
"temperature": 0.5,
"max_tokens": 1024,
"use_cache": true
},
"examples": [
{
"example_id": "ex_001",
"data": {"query": "What is machine learning?"}
},
{
"example_id": "ex_002",
"data": {"query": "Explain neural networks"}
}
],
"session_id": null,
"batch_options": {
"parallelism": 1,
"fail_fast": false,
"timeout_per_item_ms": 0
},
"timeout_ms": 30000
}
| Field | Type | Required | Description |
|---|---|---|---|
request_id |
string | No | Idempotency key (UUID). Auto-generated if not provided |
tunable_id |
string | Yes | Identifier for the tunable to invoke |
config |
object | Yes | Configuration parameters (tunable values) |
examples |
array | Yes | List of input examples to process |
session_id |
string | No | Session ID for stateful agents |
batch_options |
object | No | Batch execution control |
timeout_ms |
integer | No | Request timeout in milliseconds (default: 30000) |
Example Item:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| example_id | string | Yes | Unique identifier for this input |
| data | object | No | Input data (optional in privacy-preserving mode) |
Batch Options:
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| parallelism | integer | 1 | Maximum concurrent executions |
| fail_fast | boolean | false | Stop batch on first failure |
| timeout_per_item_ms | integer | 0 | Per-item timeout (0 = use request timeout) |
Response (200 OK)¶
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"execution_id": "exec_123456",
"status": "completed",
"outputs": [
{
"example_id": "ex_001",
"output": {
"response": "Machine learning is a subset of AI...",
"model_used": "gpt-4o-2024-01-01"
},
"cost_usd": 0.002,
"latency_ms": 150
},
{
"example_id": "ex_002",
"output": {
"response": "Neural networks are computing systems...",
"model_used": "gpt-4o-2024-01-01"
},
"cost_usd": 0.003,
"latency_ms": 180
}
],
"operational_metrics": {
"total_cost_usd": 0.005,
"cost_usd": 0.005,
"latency_ms": 330,
"tokens_used": 450,
"cache_hits": 0,
"retry_count": 0
},
"quality_metrics": null,
"session_id": null,
"error": null
}
| Field | Type | Required | Description |
|---|---|---|---|
request_id |
string | Yes | Echoed request ID for correlation |
execution_id |
string | Yes | Unique ID for this execution (used in evaluate) |
status |
string | Yes | "completed", "partial", or "failed" |
outputs |
array | Yes | Per-input results |
operational_metrics |
object | Yes | Aggregate operational metrics |
quality_metrics |
object | No | Quality metrics (if using combined mode) |
session_id |
string | No | Session ID for stateful agents |
error |
object | No | Error details if status is failed/partial |
Output Item:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| example_id | string | Yes | Matching input identifier |
| output | object | No | Agent output (optional in privacy-preserving mode) |
| output_id | string | No | Output identifier (for privacy-preserving mode) |
| cost_usd | number | No | Cost for this input |
| latency_ms | number | No | Latency for this input |
| metrics | object | No | Per-input quality metrics (combined execute+evaluate mode) |
| error | string | No | Error message for this input when execution partially fails |
Operational Metrics:
| Field | Type | Description |
|-------|------|-------------|
| total_cost_usd | number | Total cost in USD |
| cost_usd | number | Alias for total_cost_usd |
| latency_ms | number | Total latency in milliseconds |
Reserved custom metric cost_unmeasured: set "cost_unmeasured": 1.0 when some
executed examples could not be priced. total_cost_usd is then only a lower bound
(the spend that was reported; 0.0 if none was), never the total. A consumer that
honours the flag treats the number as "at least"; the flag, not the number, is the
signal. Omit the flag only when every example reported its cost.
You can add any custom operational metrics:
{
"operational_metrics": {
"total_cost_usd": 0.005,
"latency_ms": 330,
"tokens_input": 100,
"tokens_output": 350,
"api_calls": 2,
"cache_hit_rate": 0.25,
"retry_count": 0,
"model_version": "gpt-4o-2024-01-01"
}
}
POST /traigent/v1/evaluate¶
Required: No (only if supports_evaluate: true in capabilities)
Evaluate outputs against expected targets to measure quality.
Request¶
{
"request_id": "660e8400-e29b-41d4-a716-446655440001",
"tunable_id": "my_agent",
"execution_id": "exec_123456",
"evaluations": [
{
"example_id": "ex_001",
"output": {"response": "Machine learning is a subset of AI..."},
"target": {"expected": "Machine learning is a branch of artificial intelligence..."}
},
{
"example_id": "ex_002",
"output": {"response": "Neural networks are computing systems..."},
"target": {"expected": "Neural networks are computer systems inspired by the brain..."}
}
],
"config": null,
"session_id": null,
"timeout_ms": 30000
}
| Field | Type | Required | Description |
|---|---|---|---|
request_id |
string | No | Idempotency key (UUID) |
tunable_id |
string | Yes | Identifier for the evaluation tunable |
execution_id |
string | No | Reference to previous execute (for caching) |
evaluations |
array | No | List of output+target pairs (optional when using execution_id-only workflows) |
config |
object | No | Optional config for evaluation parameters |
session_id |
string | No | Session ID for stateful agents |
timeout_ms |
integer | No | Optional request timeout in milliseconds (wrapper returns 408 on timeout) |
Evaluation Item:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| example_id | string | Yes | Matching input identifier |
| output | object | No | Agent output (optional in privacy-preserving mode) |
| output_id | string | No | Output identifier (for privacy-preserving mode) |
| target | object | No | Expected/reference output (optional in privacy-preserving mode) |
| target_id | string | No | Target identifier (for privacy-preserving mode) |
Response (200 OK)¶
{
"request_id": "660e8400-e29b-41d4-a716-446655440001",
"status": "completed",
"results": [
{
"example_id": "ex_001",
"metrics": {
"accuracy": 0.92,
"relevance": 0.88,
"fluency": 0.95,
"semantic_similarity": 0.87,
"hallucination_score": 0.02
}
},
{
"example_id": "ex_002",
"metrics": {
"accuracy": 0.89,
"relevance": 0.91,
"fluency": 0.93,
"semantic_similarity": 0.85,
"hallucination_score": 0.05
}
}
],
"aggregate_metrics": {
"accuracy": {"mean": 0.905, "std": 0.021, "n": 2},
"relevance": {"mean": 0.895, "std": 0.021, "n": 2},
"fluency": {"mean": 0.94, "std": 0.014, "n": 2},
"semantic_similarity": {"mean": 0.86, "std": 0.014, "n": 2},
"hallucination_score": {"mean": 0.035, "std": 0.021, "n": 2}
}
}
| Field | Type | Required | Description |
|---|---|---|---|
request_id |
string | Yes | Echoed request ID |
status |
string | Yes | "completed", "partial", or "failed" |
results |
array | Yes | Per-example evaluation results |
aggregate_metrics |
object | Yes | Aggregated statistics per metric |
error |
object | No | Error details for partial/failed evaluations (code, message, failed_examples) |
Result Item:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| example_id | string | Yes | Matching input identifier |
| metrics | object | Yes | Quality metrics for this example |
| error | string | No | Error message for this example when evaluation partially fails |
Aggregate Metric:
| Field | Type | Description |
|-------|------|-------------|
| mean | number | Mean value across all examples |
| std | number | Standard deviation |
| n | integer | Number of examples |
Custom Quality Metrics - add any metrics specific to your use case:
{
"results": [
{
"example_id": "ex_001",
"metrics": {
"accuracy": 0.92,
"semantic_similarity": 0.87,
"hallucination_score": 0.02,
"citation_coverage": 0.75,
"safety_score": 1.0,
"factual_correctness": 0.90,
"response_relevance": 0.88,
"instruction_following": 0.95
}
}
]
}
GET /traigent/v1/health¶
Required: No
Health check endpoint for monitoring.
Response (200 OK)¶
{
"status": "healthy",
"version": "1.0.0",
"uptime_seconds": 3600.5,
"details": {
"database": "connected",
"cache": "connected"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | Yes | "healthy", "degraded", or "unhealthy" |
version |
string | No | Service version |
uptime_seconds |
number | No | Service uptime |
details |
object | No | Additional health details |
POST /traigent/v1/keep-alive¶
Required: No (only if supports_keep_alive: true)
Session heartbeat for stateful agents.
Request¶
{
"session_id": "session_abc123"
}
Response (200 OK)¶
{
"status": "alive",
"session_id": "session_abc123"
}
Response (404 Not Found)¶
{
"error": {
"code": "SESSION_NOT_FOUND",
"message": "Session not found: session_abc123"
}
}
Error Responses¶
All endpoints should return structured errors with explicit retry behavior:
| Status | Meaning | Client behavior |
|---|---|---|
| 400 | Bad request / validation failure | Do not retry until payload is fixed |
| 401 | Unauthorized | Refresh/reconfigure credentials, then retry |
| 404 | Not found (route/session) | Do not retry blindly; re-discover or recreate state |
| 408 | Request timeout (timeout_ms budget exceeded) |
Safe to retry with backoff and/or higher timeout if idempotent |
| 429 | Rate limited | Retry with backoff, honor Retry-After when present |
| 500 | Internal server error | Retry with bounded backoff; alert on repeated failures |
| 503 | Temporary service unavailability | Retry with backoff, honor Retry-After when present |
Wrapper behavior notes:
- Wrapper server enforces timeout_ms and emits 408 REQUEST_TIMEOUT for POST /execute and optional timeout_ms on POST /evaluate.
- Services can intentionally emit 401, 429, or 503 by raising wrapper error types (UnauthorizedError, RateLimitError, ServiceUnavailableError).
- OpenAPI operation-level response lists are normative for each endpoint.
Error response format:
{
"error": {
"code": "INVALID_CONFIG",
"message": "Unknown tunable: invalid_param",
"details": {
"invalid_params": ["invalid_param"]
}
}
}
Observability and Tracing¶
For distributed tracing, clients may send W3C trace headers:
traceparenttracestate
Wrapper behavior:
- Incoming trace headers are echoed in responses.
- Wrapper adds x-traigent-request-id when a request includes/produces request_id.
- request_id is required for business-level correlation; trace headers are complementary for span-level correlation.
Transport note:
- Production contract target is HTTPS + HTTP/2.
- Traigent HTTP transport supports strict enforcement via:
- create_transport(..., require_http2=True) (rejects non-HTTPS endpoints and non-HTTP/2 responses)
- Contract v1 remains synchronous request/response; async job APIs and gRPC are future roadmap items.
Data Types Summary¶
Stop Reasons¶
When optimization completes, the SDK reports a stop_reason indicating why it stopped:
| Value | Description |
|---|---|
max_trials_reached |
Maximum number of trials completed |
max_samples_reached |
Maximum dataset samples consumed |
cost_limit |
Budget limit exceeded |
timeout |
Wall-clock time limit exceeded |
plateau |
Convergence criterion met (no improvement) |
convergence |
Hypervolume convergence criterion met |
optimizer |
Optimizer signaled completion |
condition |
Generic stop condition triggered |
user_cancelled |
User cancelled the run |
error |
Optimization failed due to an exception |
vendor_error |
Provider error (rate limit, quota, or service failure) |
network_error |
Connectivity failure |
Status Values¶
| Value | Description |
|---|---|
completed |
All items processed successfully |
partial |
Some items failed, others succeeded |
failed |
All items failed |
Tunable Types¶
| Type | Domain Format | Example |
|---|---|---|
enum |
{"values": [...]} |
["a", "b", "c"] |
float |
{"range": [min, max], "resolution": step} |
[0.0, 1.0] |
int |
{"range": [min, max]} |
[0, 100] |
bool |
N/A | true / false |
str |
{"values": [...]} |
["opt1", "opt2"] |
Idempotency¶
All GET endpoints (/capabilities, /config-space, /health) must be side-effect free and idempotent by contract.
POST /execute and POST /evaluate support idempotency via the request_id field:
- If a request with the same
request_idand the same payload is received again, the service should return the cached response. - If the same
request_idis reused with a different payload, the service should return400 INVALID_REQUEST. - This enables safe retries without duplicate processing
- Generate
request_idas a UUID
POST /keep-alive is session-heartbeat semantics and does not use request_id.
Wrapper behavior:
- TraigentService implements in-process idempotency caches for execute and evaluate.
- If your server does not implement idempotency, retrying the same request can run it twice and duplicate side effects/costs.
Optional extension:
- OPTIONS can be implemented for method discovery/CORS convenience, but it is not required by the core hybrid contract.
Related Documentation¶
- Client Integration Guide - Implementation patterns and examples
- TraigentService wrapper: see the inline wrapper example in the Client Integration Guide.