Skip to content
Back to skills

1178 Name Skill 52f24936

ASecurity

Use the Synth AI SDK end-to-end for policy optimization (GEPA), graph optimization, eval, and inference

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 11, 2026
businesspythongobashfastapidockerapibackend

Works with

  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 1178-name-skill_52f24936 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1178 Name Skill 52f24936?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 1178 Name Skill 52f24936
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1178-name-skill-52f24936/badge)](https://www.skillsdirectory.com/skills/tools-only-1178-name-skill-52f24936)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
name: synth-api
version: "0.7.15"
description: Use the Synth AI SDK end-to-end for policy optimization (GEPA), graph optimization, eval, and inference
---

# Synth API (SDK v0.7.15)

This skill explains how to run Synth end-to-end with:
- a **container** (container) exposed via **SynthTunnel** or **Cloudflare tunnel**
- **PolicyOptimizationJob** (GEPA / MIPRO prompt optimization)
- **GraphOptimizationJob** (verifier graph training)
- **GEPA compat layer** (drop-in `gepa.optimize()` interface)
- **Eval jobs** on held-out seeds
- **InProcessContainer** for all-in-one local development

Reference demos have moved to the sibling `Benchmarking` repo.

## Required env

- `SYNTH_API_KEY`: your API key (or mint a demo key below)
- `SYNTH_BACKEND_URL` (optional): backend base URL, default `https://api.usesynth.ai`

## Auth (three keys)

Synth uses **three** distinct keys. Do not mix them:

| Key | Purpose | Header | When needed |
|-----|---------|--------|-------------|
| `SYNTH_API_KEY` | Authenticates SDK/CLI calls to the **Synth backend** | `Authorization: Bearer <key>` | Always |
| `ENVIRONMENT_API_KEY` | Authenticates backend-to-**container** requests | `x-api-key: <key>` | Cloudflare tunnels |
| SynthTunnel `worker_token` | Authenticates tunnel **relay to container** | Passed as `container_worker_token` in job config | SynthTunnel (default) |

Common failures:
- `Invalid API key` on `/api/jobs/*` = wrong key sent to backend.
- `SYNTH_TUNNEL_ERROR: Invalid worker token` = wrong tunnel relay token.

### Mint a demo Synth API key (optional)

Demo keys are short-lived (default 4 hours) and are great for notebooks or quick starts.

```python
import os
from synth_ai.core.utils.env import mint_demo_api_key

SYNTH_API_BASE = os.environ.get("SYNTH_BACKEND_URL", "https://api.usesynth.ai")
SYNTH_API_KEY = os.environ.get("SYNTH_API_KEY") or mint_demo_api_key(SYNTH_API_BASE)
os.environ["SYNTH_API_KEY"] = SYNTH_API_KEY
```

### Mint + upload an Environment API key (Cloudflare tunnels only)

Only needed when using Cloudflare tunnels. SynthTunnel handles auth automatically.

```python
import os
from synth_ai.sdk.container.auth import mint_environment_api_key, setup_environment_api_key

SYNTH_API_BASE = os.environ.get("SYNTH_BACKEND_URL", "https://api.usesynth.ai")
SYNTH_API_KEY = os.environ["SYNTH_API_KEY"]

ENVIRONMENT_API_KEY = mint_environment_api_key()
os.environ["ENVIRONMENT_API_KEY"] = ENVIRONMENT_API_KEY
setup_environment_api_key(SYNTH_API_BASE, SYNTH_API_KEY, token=ENVIRONMENT_API_KEY)
```

## Core concepts

- **Container**: Your container runs locally and exposes `/rollout` + `/task_info`.
- **Tunnel**: SynthTunnel (default) or Cloudflare Quick Tunnel makes the container reachable by Synth.
- **GEPA**: Evolutionary prompt optimizer that mutates prompts to maximize reward.
- **MIPRO**: Systematic instruction proposal optimizer.
- **Graph Optimize**: Train verifier graphs (RLM-based) on your evaluation data.
- **Eval jobs**: Formal evaluation on held-out seeds after optimization.

## Version check

```python
import synth_ai
print(synth_ai.__version__)  # "0.7.15"
```

## 1) Define a container

Minimum container shape:
- `provide_taskset_description()`
- `provide_task_instances(seeds)`
- `rollout(request) -> RolloutResponse`

```python
from synth_ai import ContainerConfig, create_container
from synth_ai.sdk.container._impl.contracts import (
    RolloutMetrics, RolloutRequest, RolloutResponse, TaskInfo,
)


def create_banking77_container(system_prompt: str):
    async def run_rollout(request: RolloutRequest, fastapi_request) -> RolloutResponse:
        reward = 1.0  # Your task logic here; return reward in [0, 1]
        return RolloutResponse(
            trace_correlation_id=request.trace_correlation_id,
            reward_info=RolloutMetrics(outcome_reward=reward),
            trace=None,
        )

    def provide_taskset_description():
        return {"splits": ["train", "test"], "sizes": {"train": 1000, "test": 1000}}

    def provide_task_instances(seeds):
        for seed in seeds:
            yield TaskInfo(
                task={"id": "banking77", "name": "Banking77 Intent Classification"},
                dataset={"id": "banking77", "split": "train", "index": seed},
                inference={"tool": "banking77_classify"},
                limits={"max_turns": 1},
                task_metadata={"seed": seed},
            )

    return create_container(
        ContainerConfig(
            app_id="banking77",
            name="Banking77 Intent Classification",
            description="Classify customer queries into intents.",
            provide_taskset_description=provide_taskset_description,
            provide_task_instances=provide_task_instances,
            rollout=run_rollout,
            cors_origins=["*"],
        )
    )
```

## 2) Expose with a tunnel

### SynthTunnel (recommended)

Relay-based tunnel — no external binary required, supports 128 concurrent requests:

```python
from synth_ai.core.tunnels import TunneledContainer

app = create_banking77_container("baseline prompt")
tunnel = await TunneledContainer.create_for_app(
    app=app,
    local_port=None,  # auto-select
    api_key=os.environ["SYNTH_API_KEY"],
)
CONTAINER_URL = tunnel.url            # https://st.usesynth.ai/s/rt_...
WORKER_TOKEN = tunnel.worker_token   # pass to job config
print("Container URL:", CONTAINER_URL)
```

### Cloudflare Quick Tunnel (alternative)

Requires `cloudflared` installed (`brew install cloudflared`):

```python
from synth_ai.core.tunnels import TunneledContainer, TunnelBackend

app = create_banking77_container("baseline prompt")
tunnel = await TunneledContainer.create_for_app(
    app=app,
    local_port=None,
    backend=TunnelBackend.CloudflareQuickTunnel,
)
CONTAINER_URL = tunnel.url  # https://....trycloudflare.com
```

When using Cloudflare tunnels, pass `container_api_key` instead of `container_worker_token` in job configs.

## 3) Run GEPA (policy optimization)

GEPA mutates prompt candidates and evaluates them via rollouts. Use a config dict
or a TOML file.

```python
import os
from synth_ai import PolicyOptimizationJob

config_body = {
    "policy_optimization": {
        "algorithm": "gepa",
        "container_url": CONTAINER_URL,
        "env_name": "banking77",
        "initial_prompt": {
            "messages": [
                {"role": "system", "order": 0, "pattern": "Baseline system prompt"},
                {"role": "user", "order": 1, "pattern": "Customer Query: {query}\n\nAvailable Intents:\n{available_intents}"},
            ],
            "wildcards": {"query": "REQUIRED", "available_intents": "OPTIONAL"},
        },
        "policy": {
            "model": "gpt-4.1-nano",
            "provider": "openai",
            "inference_mode": "synth_hosted",
            "temperature": 0.0,
            "max_completion_tokens": 256,
        },
        "gepa": {
            "env_name": "banking77",
            "evaluation": {
                "seeds": list(range(50)),
                "validation_seeds": list(range(50, 60)),
            },
            "rollout": {"budget": 80, "max_concurrent": 8, "minibatch_size": 8},
            "mutation": {"rate": 0.3},
            "population": {"initial_size": 4, "num_generations": 3, "children_per_generation": 3},
            "archive": {"size": 5, "pareto_set_size": 10},
        },
    },
}

job = PolicyOptimizationJob.from_dict(
    config_dict=config_body,
    container_worker_token=WORKER_TOKEN,  # from SynthTunnel
    skip_health_check=True,
)
job_id = job.submit()
result = job.stream_until_complete(timeout=3600.0)
print(f"Best score: {result.best_score}")
```

### From a TOML config file

```python
from synth_ai import PolicyOptimizationJob

job = PolicyOptimizationJob.from_config(
    config_path="gepa_config.toml",
    container_worker_token=WORKER_TOKEN,
)
job.submit()
result = job.stream_until_complete(timeout=3600.0)
```

## 4) GEPA compat layer (drop-in)

For a simpler interface that handles container + tunnel setup automatically:

```python
from synth_ai import gepa

trainset, valset, _ = gepa.examples.banking77.init_dataset()
result = gepa.optimize(
    seed_candidate={"system_prompt": "You are a helpful assistant."},
    trainset=trainset,
    valset=valset,
    task_lm="openai/gpt-4.1-mini",
    max_metric_calls=150,
    reflection_lm="openai/gpt-5",
)
print(result.best_candidate["system_prompt"])
```

Key parameters:
- `seed_candidate`: dict with a `system_prompt` (or `instruction`/`prompt`) key
- `trainset` / `valset`: lists of dicts with `input` and `answer` keys
- `task_lm`: `"provider/model"` string
- `reflection_lm`: model for GEPA proposer (controls proposer effort)
- `max_metric_calls`: budget cap

## 5) InProcessContainer (all-in-one)

Combines container + tunnel + lifecycle in a single async context manager:

```python
from synth_ai import InProcessContainer, PolicyOptimizationJob

async with InProcessContainer(
    app=create_banking77_container("baseline prompt"),
    port=8114,
    tunnel_mode="synthtunnel",  # default; also "quick", "local", "preconfigured"
    api_key=os.environ["SYNTH_API_KEY"],
) as container:
    print(f"Running at: {container.url}")

    job = PolicyOptimizationJob.from_dict(
        config_dict=config_body,
        container_worker_token=container.worker_token,
    )
    job.submit()
    result = job.stream_until_complete(timeout=3600.0)
```

## 6) Graph optimization (verifier training)

Train a verifier graph with an RLM backbone:

```python
from synth_ai import GraphOptimizationJob

job = GraphOptimizationJob.from_dataset(
    dataset="verifier_dataset.json",
    graph_type="rlm",
    policy_models=["gpt-4.1"],
    proposer_effort="medium",
    rollout_budget=200,
)
job.submit()
result = job.stream_until_complete(timeout=3600.0)
```

## 7) Run Eval jobs (held-out seeds)

Eval jobs score a fixed set of held-out seeds for a final report.

```python
import os
from synth_ai import EvalJob, EvalJobConfig

config = EvalJobConfig(
    container_url=CONTAINER_URL,
    backend_url=os.environ.get("SYNTH_BACKEND_URL", "https://api.usesynth.ai"),
    api_key=os.environ["SYNTH_API_KEY"],
    container_worker_token=WORKER_TOKEN,  # for SynthTunnel
    env_name="banking77",
    seeds=list(range(100, 150)),
    policy_config={"model": "gpt-4.1-nano", "provider": "openai"},
    env_config={"split": "test"},
    concurrency=10,
)
job = EvalJob(config)
job.submit()
result = job.poll_until_complete(timeout=600.0, interval=2.0, progress=True)
print(f"Mean reward: {result.mean_reward}")
```

## 8) Zero-shot verifiers

Run a built-in verifier graph with rubric criteria passed at runtime:

```python
import os
from synth_ai import VerifierClient

client = VerifierClient(
    base_url=os.environ.get("SYNTH_BACKEND_URL", "https://api.usesynth.ai"),
    api_key=os.environ["SYNTH_API_KEY"],
)
result = await client.evaluate(
    job_id="zero_shot_verifier_single",
    trace={"session_id": "s", "session_time_steps": []},
    rubric={
        "event": [{"id": "accuracy", "weight": 1.0, "description": "Correctness"}],
        "outcome": [{"id": "task_completion", "weight": 1.0, "description": "Completed task"}],
    },
    options={"event": True, "outcome": True, "model": "gpt-5-nano"},
    policy_name="my_policy",
    container_id="my_task",
)
```

## CLI

```bash
# Install
pip install synth-ai==0.7.15
# or: uv add synth-ai

# Check version
synth-ai --version

# List packaged OpenCode skills
synth-ai skill list

# Install a skill to a custom directory
synth-ai skill install synth-api --dir ~/custom/opencode/skill

# Serve a container locally
synth-ai container serve my_module:app --port 8114

# Deploy a container to Synth Harbor
synth-ai container deploy --name my-app --app my_module:app --dockerfile ./Dockerfile --context . --wait
```

## HTTP example (raw)

```python
import os
import requests

base = os.environ.get("SYNTH_BACKEND_URL", "https://api.usesynth.ai")
resp = requests.get(
    f"{base}/api/health",
    headers={"Authorization": f"Bearer {os.environ['SYNTH_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

## API stability

Modules follow the [API Stability Lifecycle](../../specifications/api-stability-lifecycle.md):

| Module | Status | Import |
|--------|--------|--------|
| `PolicyOptimizationJob` | Stable | `from synth_ai import PolicyOptimizationJob` |
| `GraphOptimizationJob` | Stable | `from synth_ai import GraphOptimizationJob` |
| `EvalJob` | Stable | `from synth_ai import EvalJob` |
| `ContainerConfig` | Stable | `from synth_ai import ContainerConfig` |
| `create_container` | Stable | `from synth_ai import create_container` |
| `InProcessContainer` | Stable | `from synth_ai import InProcessContainer` |
| `TunneledContainer` | Stable | `from synth_ai.core.tunnels import TunneledContainer` |
| `VerifierClient` | Beta | `from synth_ai import VerifierClient` |
| `GraphCompletionsClient` | Beta | `from synth_ai import GraphCompletionsClient` |
| `InferenceClient` | Beta | `from synth_ai import InferenceClient` |
| `gepa.optimize` | Beta | `from synth_ai.gepa import optimize` |
| `dspy.GEPA` | Beta | `from synth_ai.dspy import GEPA` |
| `EnvironmentPoolsClient` | Alpha | `from synth_ai.sdk.environment_pools import EnvironmentPoolsClient` |
| `ManagedPools` | Alpha | `from synth_ai.sdk.managed_pools import ...` |

Legacy aliases (`PromptLearningJob`, `GraphEvolveJob`, `TunneledContainer`, `ContainerConfig`, `create_container`, `synth_ai.sdk.container.*`) still work but are deprecated.

## Troubleshooting checklist

- **SynthTunnel**: Expect a `st.usesynth.ai` URL. Pass `tunnel.worker_token` to job configs.
- **Cloudflare tunnel**: Expect a `trycloudflare.com` URL. Requires `cloudflared` binary.
- **Inference URL**: If using hosted inference, model requests go to `https://api.usesynth.ai/api/inference/v1`.
- **Auth**: Confirm `SYNTH_API_KEY` is set and valid. Do not confuse it with `ENVIRONMENT_API_KEY` or `worker_token`.
- **Container shape**: Ensure `/task_info` and `/rollout` return valid `RolloutResponse`.
- **Streaming errors**: If `stream_until_complete()` disconnects, it auto-reconnects via SSE. Check backend logs for job status.
- **Legacy import paths**: If you see `PromptLearningJob`, update to `PolicyOptimizationJob`. If you see `prompt_learning` config keys, update to `policy_optimization`. If you see `synth_ai.sdk.container`, update to `synth_ai.sdk.container`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.