Promptic

External Agent Submissions

External submissions let you run any agent in your own environment while Promptic owns the immutable benchmark snapshot, hidden evaluation evidence, scoring, insights, and leaderboard.

Workflow

Authenticate the external process

Sign in with the Promptic CLI or configure an AI Application API key in the trusted process that coordinates the workflow. Authentication is required whether the process only submits runs or also configures the Agent.

Create or select the Agent

Use an existing Agent component, or let a coding agent, CI job, or other external process configure the complete Agent through the CLI or SDK. External configuration can define the Agent's name and goal, input and output contract, cases and files, and evaluators without using the dashboard.

Publish an immutable version

Publish the ready configuration from the dashboard, CLI, or SDK. Every external run targets one immutable version of its goal, schemas, cases, and evaluation plan.

Execute every case

Run the candidate once for every case. The SDK materializes input files locally and passes the case input to your callback.

Upload durable results

Return each prediction, generated artifact, and trace reference as soon as the case completes. The SDK retries transient failures and keeps requests within the supported size and case-count limits.

Submit for scoring

Submit the completed session. Promptic verifies exact case coverage, queues scoring, and exposes the evaluated run on the Agent leaderboard.

The dashboard is therefore optional for Agent configuration. A coding agent can authenticate, create the complete definition, upload cases and files, publish a revision, execute the candidate, and submit the run for scoring as one automated workflow. Teams can still configure the Agent in the dashboard and use external automation only for execution and submission.

The manifest contains task instructions, public success criteria, case inputs, and short-lived input-file URLs. It never exposes expected outputs, reference files, private rubrics, judge prompts, or evaluator configuration.

Configure authentication and environment

If the SDK is not installed or authenticated yet, start with SDK installation and authentication. The setup below adds the benchmark-specific values needed by an external Agent runner.

Create an AI Application API key

Open the AI Application that owns the Agent component, go to Settings → API Keys, and create a key for the external runner. Copy it immediately; the complete value is shown only once. API keys are scoped to one AI Application, so no separate AI Application ID is required.

Copy the benchmark ID

Open the Agent component and use Copy benchmark ID in the header. This control remains available whether the leaderboard is empty or already contains runs. It identifies the benchmark whose cases the runner will receive. When the runner creates a submission session, Promptic automatically creates or reuses an immutable snapshot of the current task, cases, and scoring setup.

Export the variables

For Promptic-hosted production:

Terminal
export PROMPTIC_ENDPOINT="https://promptic.eu"
export PROMPTIC_API_KEY="ptc_..."
export BENCHMARK_ID="<benchmark-uuid>"

For another Promptic environment, also set the dashboard origin. Do not append /api or /api/v1:

Terminal
export PROMPTIC_ENDPOINT="https://your-promptic-host"
export PROMPTIC_API_KEY="ptc_..."
export BENCHMARK_ID="<benchmark-uuid>"

BENCHMARK_ID is a shell variable used by the examples on this page. The SDK reads PROMPTIC_API_KEY and PROMPTIC_ENDPOINT automatically. The SDK defaults to https://promptic.eu when PROMPTIC_ENDPOINT is absent, but the curl examples require it.

You can use an interactive CLI login instead of an API key:

Terminal
export PROMPTIC_ENDPOINT="https://promptic.eu" # or your other Promptic environment
promptic login
export BENCHMARK_ID="<benchmark-uuid>"

The login flow stores the access token and selected AI Application in ~/.promptic/config.toml. Keep PROMPTIC_ENDPOINT set when the target is not https://promptic.eu.

Configure the candidate's provider credentials

Promptic authentication and model-provider authentication are separate. If the candidate calls OpenAI, Anthropic, or another provider directly, configure that provider's normal environment variable in the external runtime:

Terminal
export OPENAI_API_KEY="..."

Provider credentials are for the candidate process only. Do not put them in PROMPTIC_API_KEY, prediction data, traces, or uploaded artifacts.

Configure and run with the CLI or Python SDK

Configure the complete Agent component

After authenticating, coding agents and CI can configure the complete Agent through either interface. They do not need to create it in the dashboard first.

promptic agent-gym create agent.json

The file follows the agent.json contract. A file field stays inside its schema value; the CLI uploads local files and binds them to that field. create always creates a new Agent. Use the returned benchmark ID for later commands; it does not silently modify an existing Agent.

from pathlib import Path

from promptic_sdk import AgentGymClient, BenchmarkFile, ClassificationF1

with AgentGymClient(ai_application_id="<ai-application-id>") as gym:
    agent = gym.benchmarks.create(
        name="Invoice classifier",
        goal="Classify the supplied invoice and return the requested fields.",
        input_schema={
            "type": "object",
            "properties": {
                "document": {"type": "array", "x-promptic-type": "file"},
            },
            "required": ["document"],
        },
        output_schema={
            "type": "object",
            "properties": {
                "label": {"type": "string", "enum": ["invoice", "other"]},
            },
            "required": ["label"],
        },
        evaluators=[ClassificationF1(field_paths=("label",))],
    )
    agent.cases.add(
        input={"document": [BenchmarkFile(Path("fixtures/invoice.pdf"))]},
        output={"label": "invoice"},
        expected_behavior="Inspect the complete document before classifying it.",
    )
    agent.refresh()
    if not agent.ready_for_submission:
        raise RuntimeError(agent.data["configuration"])
    revision = agent.publish()

The definition response contains readiness, the current evaluation plan, and evaluator recommendations. Publishing snapshots the goal, schemas, cases, and evaluators together; external runs are always evaluated against that immutable revision.

Execute the external Agent

Both interfaces execute the same trusted callback. Save this example as my_agent.py; the callback receives one AgentGymCase at a time:

from pathlib import Path

from promptic_sdk import AgentGymCase, AgentGymCaseResult, AgentGymOutputArtifact


def run(case: AgentGymCase) -> AgentGymCaseResult:
    deck_path = build_deck(case.instructions, case.input)
    return AgentGymCaseResult.artifact(
        AgentGymOutputArtifact(Path(deck_path), field_path="deck"),
    )

Choose the invocation that fits the external process:

promptic agent-gym run "$BENCHMARK_ID" my_agent:run \
  --name pitch-deck-agent \
  --version 1.2.0 \
  --architecture architecture.md
import os

from my_agent import run
from promptic_sdk import AgentGymClient


with AgentGymClient() as gym:
    result = gym.run_and_submit(
        benchmark_id=os.environ["BENCHMARK_ID"],
        executor=run,
        name="pitch-deck-agent",
        version="1.2.0",
        architecture_description="""
## Workflow
1. Extracts the brief and source evidence into a slide outline.
2. Generates each slide from the approved outline and renders a preview.
3. Checks overflow, missing citations, and visual consistency before export.

## Models and tools
Uses one planning call, bounded per-slide generation, and the local presentation renderer.

## Validation
Retries only failed slides, then exports the reviewed deck as a PPTX artifact.
""".strip(),
    )

print(result.run_id)

architecture_description accepts Markdown. When a processing flow is easier to understand visually, add a focused Mermaid diagram with a fenced mermaid block. Keep a short written explanation as well, and prefer several small diagrams over one oversized diagram.

## Processing flow

```mermaid
flowchart LR
    Brief --> Plan
    Plan --> Render
    Render --> Validate
    Validate --> Export
```

Validation retries only failed slides before the reviewed deck is exported.

AgentGymClient uses the saved CLI login by default. It also accepts a normal Promptic API key through PROMPTIC_API_KEY or the api_key argument. The SDK uploads each completed case prediction immediately, retries transient upload failures idempotently, and dynamically bounds lower-level upload batches by serialized size and case count.

The high-level callback API executes the callback in the authenticated process and is therefore intended for code you trust. Do not pass platform credentials into generated or untrusted code. Run that code in an isolated environment, collect its outputs in a trusted runner, and use the lower-level session API from that runner to upload artifacts and submit the completed session for scoring.

Inspect a scored run

Use the returned run ID to retrieve aggregate metrics and the weakest cases.

promptic agent-gym results "$BENCHMARK_ID" "$RUN_ID"
promptic agent-gym case-results "$BENCHMARK_ID" "$RUN_ID" \
  --sort score \
  --limit 5
promptic agent-gym case-result "$BENCHMARK_ID" "$RUN_ID" 42
promptic agent-gym artifact-download \
  "$BENCHMARK_ID" "$RUN_ID" 42 0 \
  --output review/report.pdf

case-results returns one page and accepts --cursor to continue pagination. Supported sorts are score, score_desc, latency, and case. The artifact index is zero-based and matches the artifacts array printed by case-result. Downloads are bounded and refuse to overwrite files unless --overwrite is supplied.

with AgentGymClient() as gym:
    summary = gym.get_run_results(os.environ["BENCHMARK_ID"], result.run_id)
    weakest = gym.list_case_results(
        os.environ["BENCHMARK_ID"],
        result.run_id,
        sort="score",
        limit=5,
    )

    print(summary["aggregates"])
    for case_result in weakest["data"]:
        print(case_result["case_id"], case_result["overall_score"])
        print(case_result["judgements"])
        print(case_result["traces"])

        for artifact in case_result["artifacts"]:
            destination = Path("review") / str(case_result["case_id"]) / (
                artifact["path"] or "artifact.bin"
            )
            gym.download_prediction_artifact(artifact, destination)

Each case result contains the submitted output, official scores, normalized judge rationales, output artifact metadata, linked execution traces, metrics, and generated insights. Failed evaluator scores include a safe error message; a recognized verifier tool limit or timeout is named explicitly. Raw provider errors and internal diagnostics are withheld from API responses. Optional artifacts and traces are empty lists when the external runtime did not submit them.

sort="score" lists failed executions first and then successful cases from lowest to highest score. Use get_case_result() for one case or iter_case_results() to scan every case.

Compare a child architecture with its baseline:

promptic agent-gym compare-runs \
  "$BENCHMARK_ID" \
  "$BASELINE_RUN_ID" \
  "$CANDIDATE_RUN_ID"
with AgentGymClient() as gym:
    comparison = gym.compare_runs(
        os.environ["BENCHMARK_ID"],
        parent_run_id="<baseline-run-uuid>",
        candidate_run_id="<candidate-run-uuid>",
    )

print(comparison["summary"])
print([case for case in comparison["cases"] if case["classification"] == "regressed"])

Paired comparison requires the same immutable benchmark snapshot, scorer contract, evaluator configuration, and case set. Review case regressions, execution failures, latency, and token usage instead of selecting a variant from the mean score alone.

Monitor and recover an external submission

Custom runners that retain a submission ID can inspect or wait for its state from either interface:

promptic agent-gym submission-status "$BENCHMARK_ID" "$SUBMISSION_ID"
promptic agent-gym submission-wait \
  "$BENCHMARK_ID" "$SUBMISSION_ID" \
  --max-wait 600 \
  --poll-interval 2

If the terminal state is dispatch_failed, restore scoring delivery for the same immutable run:

promptic agent-gym retry-scoring "$BENCHMARK_ID" "$RUN_ID"

Cancel only an unfinalized session that should no longer accept predictions:

promptic agent-gym submission-cancel "$BENCHMARK_ID" "$SUBMISSION_ID" --yes
with AgentGymClient() as gym:
    status = gym.wait_for_submission(
        os.environ["BENCHMARK_ID"],
        submission_id,
        max_wait=600,
        poll_interval=2,
    )
    if status["status"] == "dispatch_failed" and status["run"] is not None:
        gym.retry_scoring(os.environ["BENCHMARK_ID"], status["run"]["id"])

Use get_submission_status() for a single request and cancel_submission() for an unfinalized session that should be abandoned.

Retrying scoring is idempotent and reuses the existing persisted predictions and run. It does not execute the Agent again or create a replacement leaderboard entry.

Submission protocol

The SDK handles the protocol below. Use it directly only when implementing a custom or isolated runner.

The curl examples use PROMPTIC_ENDPOINT, PROMPTIC_API_KEY, and BENCHMARK_ID as configured above.

Create a session

curl -X POST "$PROMPTIC_ENDPOINT/api/v1/benchmarks/$BENCHMARK_ID/submissions" \
  -H "Authorization: Bearer $PROMPTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: run-2026-07-17-001" \
  -d '{
    "ttl_seconds": 86400,
    "variant_identity": {
      "name": "pitch-deck-agent",
      "version": "1.2.0",
      "repository_url": "https://github.com/acme/pitch-deck-agent",
      "commit_hash": "6f1ed002ab5595859014ebf0951522d9d5f25a73",
      "architecture_description": "Extract evidence, generate and render the deck, then validate overflow and citations."
    }
  }'

The response identifies the immutable benchmark snapshot (revision in the API), creates the variant and its uploading run, and provides manifest, artifact, prediction-upload, submit, and status links. Repeating the request with the same key and parameters returns the same session. Within one Agent Optimization task, a variant name and version always identify the same architecture and source provenance. Reuse them for another run or benchmark revision when the provenance is unchanged. Independent AI Components in the same AI Application may use the same name and version. Changing provenance for an existing task-scoped identity returns 409.

Read and materialize cases

curl "$PROMPTIC_ENDPOINT/api/v1/benchmarks/$BENCHMARK_ID/submissions/$SUBMISSION_ID/manifest?limit=50" \
  -H "Authorization: Bearer $PROMPTIC_API_KEY"

Follow next_cursor until it is null. Download input files immediately; signed URLs expire after a few minutes. Preserve each dataset_case_id; it identifies the case inside the immutable dataset snapshot selected by the revision.

Upload an output artifact

Calculate the byte length and lowercase SHA-256 digest before reserving the artifact:

curl -X POST "$PROMPTIC_ENDPOINT/api/v1/benchmarks/$BENCHMARK_ID/submissions/$SUBMISSION_ID/artifacts" \
  -H "Authorization: Bearer $PROMPTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "deck.pptx",
    "role": "deliverable",
    "mime_type": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    "size_bytes": 42891,
    "sha256": "<64 lowercase hex characters>"
  }'

Upload the exact bytes with the returned upload request, then call the artifact's /complete endpoint. Completion checks object ownership, size, media type, and SHA-256. HTML, SVG, and script-like files are served as downloads unless an isolated content origin is configured.

Attach traces

The authenticated SDK can send OTLP JSON or protobuf to /api/v1/traces. After ingestion, resolve the OpenTelemetry trace ID to the database ID accepted by prediction uploads:

curl "$PROMPTIC_ENDPOINT/api/v1/benchmarks/$BENCHMARK_ID/submissions/$SUBMISSION_ID/traces?trace_id=$OTEL_TRACE_ID" \
  -H "Authorization: Bearer $PROMPTIC_API_KEY"

Use the returned trace_db_id in execution_refs.trace_ids. Inline trace artifacts ingested with the trace can likewise be attached through trace_artifact_ids when their IDs are known.

Upload predictions

{
  "predictions": [
    {
      "dataset_case_id": 1042,
      "status": "succeeded",
      "output": {
        "deck": ["promptic-artifact://<verified-artifact-id>"]
      },
      "artifact_ids": ["<verified-artifact-id>"],
      "execution_refs": {
        "trace_ids": ["<trace-id>"]
      },
      "token_usage": { "prompt": 1800, "completion": 950, "total": 2750 },
      "latency_ms": 18240
    },
    {
      "dataset_case_id": 1043,
      "status": "failed",
      "artifact_ids": [],
      "error_code": "render_failed",
      "error_category": "execution",
      "retryable": true,
      "error": "The renderer exited before producing a valid deck."
    }
  ]
}

Upload this body with PUT to the session's predictions link. Each batch may contain up to 500 cases. The Python SDK additionally keeps each serialized request at or below 1 MiB; custom runners should use a similar bound. Each accepted prediction is written directly to the run and becomes visible in its case results. Repeating a batch safely replaces the canonical value for each included case while the session is open, so a runner can retry interrupted uploads. The server immediately checks case membership, the frozen output contract, evidence ownership, implementation references, and artifact verification.

Submit for scoring

After every case has one terminal prediction, call the session's submit link with a new Idempotency-Key. Optional submission-wide evidence or metadata can be supplied here:

{
  "metadata": { "executor_region": "eu" }
}

Submit verifies exact frozen-case coverage and closes further prediction writes. Scoring is then authorized and queued for the existing run. The dashboard shows preparation separately from successful queue delivery. If coverage is incomplete, it returns 409 incomplete_submission with the missing case IDs; the run remains open and the client can upload the missing predictions before retrying. If queue dispatch fails, it returns 503 scoring_dispatch_failed; retry the same submit request and idempotency key. An unexpected authorization failure returns 503 billing_authorization_failed; the run shows a recoverable failure, and an identical retry resumes the same authorization attempt. A definite billing denial returns 402; correct billing and use Retry scoring. Uploaded predictions are preserved in all of these cases. Interrupted preparation also exposes a retry action rather than remaining indefinitely queued.

For a child architecture, add parent_name and parent_version to the variant_identity supplied when creating the session. When the name stays unchanged, parent_version alone is sufficient. Use rationale for the observed weakness and intent for the expected behavioral effect. Add repository_url and commit_hash to record the exact source revision used by the variant.

If an evaluator cannot run because required evidence is missing, its evaluation fails only for that prediction. Those unavailable evaluations are excluded from the score; other evaluated cases and metrics remain visible. The leaderboard marks the score as partially evaluated and shows how many cases lack required evidence. If no score is available, it shows “—” with a warning instead of 0%; the warning explains that the variant was not evaluated.

Other failed, skipped, and cancelled cases still count as zero. A technical failure of a required evaluator still makes the evaluation ineligible. Only eligible runs for the current benchmark snapshot appear on the official leaderboard.

Retries

  • Retry scoring reuses the immutable predictions only when no official scoring attempt completed.
  • Rerun agent creates a new empty submission session for the same benchmark snapshot.
  • Repeating session creation or submit with the same idempotency key and payload is safe. Reusing a key with different content returns 409.

Generated artifacts, trace links, evaluator reasoning, eligibility reasons, and deterministic failure insights remain available from the run drill-down. If scoring fails, run results include a failure object with a stable code and a readable message. For example, verifier_tool_call_budget means the verifier reached its tool-call limit before finishing. Submission status returns the same object under run.failure; case-result responses include it as run_failure. Raw verifier exception text is not included in these fields.

The same evidence is available to authenticated external runners:

  • GET /api/v1/benchmarks/{benchmark_id}/runs/{run_id}/results
  • GET /api/v1/benchmarks/{benchmark_id}/runs/{run_id}/case-results
  • GET /api/v1/benchmarks/{benchmark_id}/runs/{run_id}/case-results/{case_id}
  • GET /api/v1/benchmarks/{benchmark_id}/runs/compare?parent_run_id=...&candidate_run_id=...