> ## Documentation Index
> Fetch the complete documentation index at: https://veryfront.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect your own agent runtime

> Run a Veryfront agent in your own code, in any language, and record its runs and events.

Run a Veryfront agent in your own process, in any language, while Veryfront records the run, its events, and its result.

Your process acts as an **agent worker**: it registers once, claims runs that Veryfront admits for it, appends events with a run-scoped token, and finishes the run.

## Before you start

You need `curl`, `jq`, `uuidgen`, and an [API credential](/docs/cloud/authentication) with permission to manage agents, conversations, and runs, plus `project.runtime.manage` to register the worker. Set `PROJECT` to your project reference and `PROJECT_ID` to the project's UUID.

[Create the agent definition](/docs/cloud/agents) in the project first. Set `AGENT_ID` to its ID. The definition names the agent; your worker executes it.

## 1. Register the worker

Call `POST /agent-workers/projects/{project_reference}/workers`. Choose an `implementation_kind` that names your runtime and a stable `worker_key` for this process:

```bash title="register-worker.sh" theme={null}
curl --fail-with-body "https://api.veryfront.com/agent-workers/projects/$PROJECT/workers" \
  -H "Authorization: Bearer $VERYFRONT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"implementation_kind":"python-openai","worker_key":"python-openai-1"}' > worker.json

WORKER_ID=$(jq -er '.worker.id' worker.json)
WORKER_TOKEN=$(jq -er '.token' worker.json)
```

The response contains the worker and its token. Store the token: Veryfront returns it only once.

## 2. Store the user message

A worker run starts from a persisted user message. Create a conversation with one. Omit `type`: the API creates a `chat` conversation, which is the type for top-level runs.

```bash title="create-conversation.sh" theme={null}
curl --fail-with-body https://api.veryfront.com/conversations \
  -H "Authorization: Bearer $VERYFRONT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"project_reference\": \"$PROJECT\",
    \"initial_message\": {\"role\":\"user\",\"parts\":[{\"type\":\"text\",\"text\":\"Summarize my open tickets.\"}]}
  }" > conversation.json

CONVERSATION_ID=$(jq -er '.id' conversation.json)
```

For a later turn, append the user message with `POST /conversations/{conversation_id}/messages` before you start the run.

## 3. Start the run on your worker

Call `POST /runs` with `conversation_id` and `config.agent_admission`. Set `mode` to `worker` and `implementation_kind` to the value your worker registered with:

```bash title="start-run.sh" theme={null}
curl --fail-with-body https://api.veryfront.com/runs \
  -H "Authorization: Bearer $VERYFRONT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "{
    \"project_id\": \"$PROJECT_ID\",
    \"conversation_id\": \"$CONVERSATION_ID\",
    \"target\": {\"type\":\"agent\",\"id\":\"$AGENT_ID\"},
    \"config\": {\"agent_admission\": {\"mode\":\"worker\",\"implementation_kind\":\"python-openai\"}}
  }"
```

Veryfront accepts the run with `202` and holds it for a matching worker. Add `worker_key` to `agent_admission` to pin the run to one worker.

`execution.runtime` overrides don't apply to agents. Use `agent_admission` instead.

## 4. Claim the run

Poll `POST /agent-workers/workers/{worker_id}/claim` with the worker token:

```bash title="claim.sh" theme={null}
while true; do
  curl --fail-with-body "https://api.veryfront.com/agent-workers/workers/$WORKER_ID/claim" \
    -H "Authorization: Bearer $WORKER_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"lease_duration_seconds":60}' > claim.json || { cat claim.json >&2; exit 1; }
  jq -e '.run != null' claim.json > /dev/null && break
  sleep 2
done

WORKER_RUN_ID=$(jq -er '.run.run_id' claim.json)
RUN_ID=$(jq -er '.run.canonical_run_id' claim.json)
EVENT_TOKEN=$(jq -er '.credentials.run_event_token' claim.json)
TERMINAL_TOKEN=$(jq -er '.credentials.run_terminal_token' claim.json)
AUTH_TOKEN=$(jq -er '.credentials.auth_token' claim.json)
```

`run` is `null` when nothing is waiting, so the loop polls until a run arrives. An HTTP error stops the loop and prints the response body. The claimed run includes the conversation and message IDs, so your code can read the user message. Renew the lease with `POST /agent-workers/workers/{worker_id}/runs/{run_id}/lease` while the run takes longer than the lease. That path takes the worker run ID (`WORKER_RUN_ID`), not `canonical_run_id`; the Runs endpoints below use `RUN_ID`. A renewal that returns `run: null` means the lease is gone.

## 5. Append events

Append events with `POST /runs/{run_id}/events` and the `run_event_token`. API keys can't append events.

```bash title="append-events.sh" theme={null}
curl --fail-with-body "https://api.veryfront.com/runs/$RUN_ID/events" \
  -H "Authorization: Bearer $EVENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"events":[
    {"type":"TEXT_MESSAGE_START","messageId":"m1","contentId":"c1","role":"assistant"},
    {"type":"TEXT_MESSAGE_CONTENT","messageId":"m1","contentId":"c1","delta":"You have 3 open tickets."},
    {"type":"TEXT_MESSAGE_END","messageId":"m1","contentId":"c1"}
  ]}'
```

Events follow the [run event format](/docs/cloud/execution/run-events). Veryfront writes `RUN_STARTED` and `RUN_FINISHED` itself.

## 6. Finish the run

Call `POST /runs/{run_id}/succeed` with the run credentials:

```bash title="succeed.sh" theme={null}
curl --fail-with-body "https://api.veryfront.com/runs/$RUN_ID/succeed" \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -H "X-Veryfront-Run-Terminal-Token: $TERMINAL_TOKEN" \
  -H "Idempotency-Key: $RUN_ID-succeed" \
  -H "Content-Type: application/json" \
  -d '{"output":{"text":"You have 3 open tickets."}}'
```

Both terminal calls require an `Idempotency-Key` header; reuse the same key when you retry. Call `POST /runs/{run_id}/fail` instead when your code fails.

## 7. Verify the result

Read the run with `GET /runs/{run_id}` and its events with `GET /runs/{run_id}/events`:

```bash title="verify.sh" theme={null}
curl --fail-with-body "https://api.veryfront.com/runs/$RUN_ID" \
  -H "Authorization: Bearer $VERYFRONT_API_KEY" | jq '{status, output}'
```

`status` is `completed` and `output` holds the result you sent. Studio shows the run and its events like any hosted run.

## Python example

The same loop with the OpenAI SDK, calling the [AI gateway](/docs/cloud/generate-response):

Install the dependencies and export the worker credentials from step 1 and a [model ID](/docs/cloud/generate-response):

```bash title="setup.sh" theme={null}
pip install requests openai
export WORKER_ID WORKER_TOKEN VERYFRONT_API_KEY
export MODEL="<model-id>"
```

Then run `python worker.py`:

```python title="worker.py" theme={null}
import os, time, uuid, requests
from openai import OpenAI

API = "https://api.veryfront.com"
LEASE_SECONDS = 120
worker_id, worker_token = os.environ["WORKER_ID"], os.environ["WORKER_TOKEN"]
worker_auth = {"Authorization": f"Bearer {worker_token}"}
# The gateway refuses a run's auth_token on /ai/v1, so model calls use an API key.
llm = OpenAI(base_url=f"{API}/ai/v1", api_key=os.environ["VERYFRONT_API_KEY"])

while True:
    claim = requests.post(f"{API}/agent-workers/workers/{worker_id}/claim",
        headers=worker_auth, json={"lease_duration_seconds": LEASE_SECONDS}).json()
    if not claim["run"]:
        time.sleep(2)
        continue
    worker_run_id, run_id = claim["run"]["run_id"], claim["run"]["canonical_run_id"]
    creds = claim["credentials"]
    terminal = {"Authorization": f"Bearer {creds['auth_token']}",
                "X-Veryfront-Run-Terminal-Token": creds["run_terminal_token"]}
    try:
        # Renew before the model call; this example assumes one call fits in the lease.
        lease = requests.post(f"{API}/agent-workers/workers/{worker_id}/runs/{worker_run_id}/lease",
            headers=worker_auth, json={"lease_duration_seconds": LEASE_SECONDS})
        lease.raise_for_status()
        if not lease.json()["run"]:
            raise RuntimeError("lease lost")
        reply = llm.chat.completions.create(model=os.environ["MODEL"],
            messages=[{"role": "user", "content": os.environ.get("PROMPT", "Hello")}])
        text, mid, cid = reply.choices[0].message.content, str(uuid.uuid4()), str(uuid.uuid4())
        requests.post(f"{API}/runs/{run_id}/events",
            headers={"Authorization": f"Bearer {creds['run_event_token']}"},
            json={"events": [
                {"type": "TEXT_MESSAGE_START", "messageId": mid, "contentId": cid, "role": "assistant"},
                {"type": "TEXT_MESSAGE_CONTENT", "messageId": mid, "contentId": cid, "delta": text},
                {"type": "TEXT_MESSAGE_END", "messageId": mid, "contentId": cid},
            ]}).raise_for_status()
        requests.post(f"{API}/runs/{run_id}/succeed",
            headers={**terminal, "Idempotency-Key": f"{run_id}-succeed"},
            json={"output": {"text": text}}).raise_for_status()
    except Exception as error:
        requests.post(f"{API}/runs/{run_id}/fail",
            headers={**terminal, "Idempotency-Key": f"{run_id}-fail"},
            json={"error": {"code": "worker_error", "message": str(error)[:4000] or "Worker failed"}}).raise_for_status()
```

The example renews the lease once before the model call and assumes that call finishes within `LEASE_SECONDS`. For longer work, renew the lease on a timer until the run ends. If the lease expires, another worker can claim the run and your `succeed` call fails.

The AI gateway doesn't accept the run's `auth_token`, so the example calls it with `VERYFRONT_API_KEY`. Gateway usage is billed to the API key's account and isn't attributed to the run.

Read the user message from the conversation in the claimed run instead of `PROMPT` in production code.

## API references

| Interface | Operations |
| - | - |
| REST | [Register a worker](/docs/cloud/rest/api-reference/execution-executors-agent-workers/register-worker) · [Claim a run](/docs/cloud/rest/api-reference/execution-executors-agent-workers/claim-run) · [Renew a run lease](/docs/cloud/rest/api-reference/execution-executors-agent-workers/renew-run-lease) · [Append run events](/docs/cloud/rest/api-reference/execution-runs-run-events/append-run-events) · [Succeed a run](/docs/cloud/rest/api-reference/execution-runs-run-actions/succeed-run) · [Fail a run](/docs/cloud/rest/api-reference/execution-runs-run-actions/fail-run) |
| GraphQL and MCP | [Execution GraphQL operations](/docs/cloud/graphql/apis/execution) and [MCP tools](/docs/cloud/mcp/apis/execution) create conversations and runs and read results. They also append run events ([GraphQL](/docs/cloud/graphql/operations/execution-api/mutations/appendrunevents), [MCP](/docs/cloud/mcp/tools/append-run-events)) and record run heartbeats ([GraphQL](/docs/cloud/graphql/operations/execution-api/mutations/createrunheartbeat), [MCP](/docs/cloud/mcp/tools/create-run-heartbeat)). Worker registration, claims and lease renewal are REST only. |

## Next steps

* [Inspect logs and traces](/docs/cloud/inspect-logs-and-traces) for the recorded run.
* [Run an agent](/docs/cloud/run-agent) on the hosted runtime instead.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.