Skip to content
Euler Docs

Euler Copilot

The Copilot is Euler’s agentic control plane. It is a real tool-calling loop, not a command parser: the model reads a grounded context pack describing your current project, decides which typed tool to call, the platform executes that tool in-process, the result feeds back, and the loop continues to a bounded step limit.

It replaces two earlier half-agents, the Agent Center screen and the Ask Euler dock, neither of which could actually operate the product.

What it can do

Thirty five tools are registered, covering the lifecycle end to end. Each one is a typed capability with a JSON schema, a minimum role, and a destructive-or-spend flag.

AreaTools
Workspaces and projectslist_workspaces, list_use_cases, create_workspace, list_projects, open_project, create_project, set_target_profile
Data and runsregister_source, list_sources, trigger_run, get_run_status, cancel_run
Diagnosisget_readiness_report, diagnose_failures
Annotationget_annotation_layers, set_annotation_layers, run_annotation_job
Readinessget_readiness_settings, set_readiness_settings
Search and slicessearch_episodes, create_slice, list_slices, curate_subset
Exportslist_exports, create_export, seal_export, get_export_download, delete_export
Models and budgetget_models, set_model, get_budget, set_budget
Teamlist_users, invite_user
Escalationraise_support_query

Tool implementations call the platform’s own service layer in-process rather than shelling out over HTTP, so a tool call inherits exactly the validation, audit trail and tenant isolation the REST surface already enforces.

The privacy boundary

Customer pixels never leave the platform boundary. The Copilot is a text agent. Everything it sends to the model provider is metadata: project and workspace names, readiness numbers, layer coverage, annotation text such as captions, labels and task strings, run stage summaries, error strings and schema. Frames, video bytes, base64 payloads and media file paths go to the self-hosted vision lane and the approved vision models only, never to the chat provider.

That guarantee is structural rather than a convention, enforced at three points:

  1. The context pack reads only named fields. It never serializes an episode, a source or a search result wholesale, so there is no field it could pick up by accident.
  2. Every tool implementation hand-builds its result the same way.
  3. A boundary assertion runs over the context pack and over every tool result before either is handed back to the model. It raises on raw bytes, on a data: URI, on a long unbroken base64 run, and on any string that resolves to a media file path. A regression that starts leaking frames fails loudly at the boundary instead of silently shipping pixels to a vendor.

Perception models are separately governed: those surfaces run self-hosted only, and no external label API is used. See models behind the layers.

Confirmations and autopilot

Nine tools are flagged destructive or spend-incurring: trigger_run, cancel_run, run_annotation_job, curate_subset, create_export, seal_export, delete_export, set_budget and invite_user.

By default, calling one of these does not execute it. The tool returns a confirmation envelope, the stream emits a confirmation event carrying the tool name, the arguments and a prompt, and nothing has happened yet. Re-send the same message with autopilot: true to execute.

In autopilot those tools execute directly. They are audited identically either way: autopilot changes whether a human says yes, never whether the action is recorded.

Role gating is enforced twice on purpose. The endpoint requires at least viewer, and the tool registry filters the catalog the model is even shown. A viewer’s model sees read-only tools and cannot call a mutation it never received. invite_user and set_budget are admin only.

Talking to it

curl -N -X POST -H "Authorization: Bearer $EULER_TOKEN" \
  -H "Content-Type: application/json" \
  "$EULER_BASE_URL/v1/copilot/chat" \
  -d '{
        "messages": [{"role": "user", "content": "Why did my last run fail?"}],
        "project_id": "acme-pilot"
      }'

The response is a Server-Sent Events stream. Event names and payloads:

EventPayload
startprovider, model, whether the provider is configured, degraded state and reason, autopilot flag, and the grounded context the turn used
tokenan incremental assistant token delta
tool_callstep, tool, call id, arguments
tool_resultstep, tool, call id, and the result envelope
confirmationstep, tool, call id, arguments and the confirmation prompt
usagemodel, prompt and completion tokens, cost in USD
errormessage
donesteps used, stop reason, and the thread id

Stop reasons are complete, cancelled, step_limit, budget_exceeded, provider_error or degraded.

The turn cancels cooperatively when the client disconnects, so closing a tab stops spending immediately.

The rest of the surface

EndpointPurpose
GET /v1/copilot/modelsAvailable models, the active model, and whether a provider key is configured
GET /v1/copilot/suggestionsContext-aware starter prompts and FAQs
POST /v1/copilot/supportRaise a support query with the Euler team
GET /v1/copilot/supportThis tenant’s support queries
GET /v1/copilot/threadsList threads
GET /v1/copilot/threads/{id}Read one thread
DELETE /v1/copilot/threads/{id}Delete one thread

Threads are deliberately lightweight: JSON documents in the tenant’s object storage, not a new table. A thread is chat scrollback, not a system of record. The audit log and the action log remain the durable trail.

The safety envelope

The only deterministic behaviour around the model is the envelope that contains it:

Model choice and degradation

The Copilot routes through OpenRouter against a curated allow-list of tool-calling-capable models, best default first. The live provider catalog is the source of truth for availability and price; the allow-list is the source of truth for choice, so a catalog change can never silently route a tenant onto an untested model. Operators can pin a model with EULER_COPILOT_MODEL.

Degradation is mandatory, not optional, and always disclosed in the start event:

It never raises into the endpoint.

Next