Skip to main content
agentsfleet is in stealth-mode testing and pre-production. APIs and agent behavior may change between releases without long deprecation windows. Email [email protected] if you want a hand calibrating an agent or to join as a design partner.
What's newAPI referenceBug fixes

Receive signed events from more providers

Choose a signing recipe for a custom webhook source. Each source has its own URL, including Svix senders.

Upgrading

Replace signature.secret_ref with credential_name and verifier.scheme: "svix-v1". Store the Svix key in the credential’s webhook_secret field and use the URL Save returns. The former Svix URL returns 404.

What’s new

API reference

  • Connectors — the workspace list includes account labels alongside connection status.

Bug fixes

  • Connections — the page shows loading and retry feedback. Failed browser returns offer a fresh connection attempt.
What's newAPI referenceBug fixes

Choose the model for future fleet runs

Choose a model in fleet chat to save it for future chat, webhook and scheduled runs.

Upgrading

Fleet responses no longer include needs_re_review. Remove checks for that field from API clients. The CLI and server can upgrade separately.

What’s new

  • Models — confirmed changes appear in Chat and Activity. Saving keeps unsent messages and private setup edits.

API reference

  • Fleet responses — current readiness checks remain; catalogue changes no longer require another review.

Bug fixes

  • Chat — replies finish without replacing their text. Run details opens compact usage and duration details.
  • Setup — Save changes appears in the fleet header while you edit setup.
What's newAPI reference

Continue long conversations and review your spending

Setup chats and fleet runs summarize older messages when the model’s working space gets close to full. Billing shows confirmed charges across your account.

What’s new

  • Conversations — summaries make room for more replies. Context limits, spending limits and run limits still apply.
  • Billing — open Settings → Billing and choose a period, fleet or model. Totals cover every page.

API reference

  • Reports — read confirmed charges at GET /v1/tenants/me/billing/reports/current and individual charges at GET /v1/tenants/me/billing/activities/{activity_id}/charges.
CLI

Authorize terminal sign-in without copying a code

agentsfleet login uses your browser on the same computer. Compare the check codes before choosing Authorize, and the terminal completes sign-in after approval.
  • Cancellation — press Esc while waiting. Once Finalizing sign-in… appears, keep the terminal open until it reports the result.
  • Another device — use agentsfleet login --no-open for manual six-digit code entry, including remote terminals.
  • Browser launch — if the browser does not open, use the printed link on the same computer.
Bug fixesCLI

Clearer page loading and terminal sign-in

Dashboard pages share one loading indicator. Navigation stays visible while the next page loads.
  • Terminal sign-in — choose Authorize in the browser, then return to your terminal and enter the displayed code.
  • Guides — start with a dashboard task using pasted text. CLI reference and runner operations have separate navigation tabs.
Bug fixes

Keep fleet setup edits and recover failed replies

Setup chat preserves manual edits when a newer proposal arrives. Review a conflict before updating instructions or saving.
  • Recovery — failed replies keep partial text and offer Try again when available.
  • Navigation — saved fleets use their chosen names. Private drafts appear under New fleet → Unfinished setups.
  • Controls — Stop and Kill share the fleet header menu and require confirmation.
  • Billing — model display names, readable usage, and relative timestamps clarify each charge.
  • Settings — compact secret tables and shared navigation styles match the dashboard.
Bug fixes

Edit fleet setup beside the conversation

Edit setup opens a private chat beside the fleet conversation. Saving applies your changes to new runs.
  • Saving — Save fleet stays beside the setup-chat title. Save changes stays below the message field when editing a fleet.
  • Recent — return to fleets and your unsaved setup chats from one sidebar section.
  • Streaming — replies appear while the model answers. Live text stops when you stop a reply, and completed replies remain available after reconnecting.
What's newBug fixes

Resume setup chat after Stop

Stop an uncertain setup reply, then send a new message. Earlier usage remains available for billing reconciliation.

What’s new

  • MiniMax — the model library includes OpenRouter’s minimax/minimax-m3.1-flash:free. An administrator must enable it before you can select it.

Bug fixes

  • Recovery — stopped attempts no longer block later replies. Existing balance and capacity limits still apply.
  • Credentials — definite key rejections fail the reply; repair the credential or select another model before sending again.
  • Sidebar — Recent setup shows chat titles in navigation text. Search and fleet recents are removed; the folded New fleet plus is compact.
Bug fixes

Docs match the product branding

The docs now use the signal logo, favicon, and shared colors in light and dark mode.

Bug fixes

  • Wordmarks — outlined lettering keeps the logo consistent without installed fonts.
UpgradingWhat's newAPI referenceBug fixesCLI

Fleet setup saves directly

You can save fleet setup directly from the dashboard. API timestamps now use Request for Comments (RFC) 3339 strings in Coordinated Universal Time (UTC). Nanodollar amounts use decimal strings.

Upgrading

  • Database — start from empty. agentsfleetd migrate creates the new schemas; it cannot upgrade an earlier database.
  • Runners — upgrade with the daemon. Renewals and reports replace run totals with requests, one usage line per provider request. The old format fails at the first renewal with 400 and UZ-RUN-013, ending the run.
  • Usage limits — renewals and reports allow 512 request lines. Reports exceeding that limit or line number 2147483647 fail with 400 and UZ-REQ-001. The response’s detail explains why.
  • Authoring requests — remove deletion_generation. Requests that send it are refused.
  • Timestamps — replace Unix millisecond numbers with parsed timestamps. expires_at_ms becomes expires_at on GET /v1/auth/sessions/{session_id} and POST /v1/runners/me/credentials/mint.
  • Time comparisons — compare parsed values. Whole seconds omit the fraction, as in 2026-10-17T18:06:49Z, so text order can differ from time order.
  • Money — balance_nanos, credit_deducted_nanos, cost_nanos, and catalogue rates change from JSON numbers to decimal strings; use integer arithmetic. balance_nanos allows negative values and matches ^(0|-?[1-9][0-9]*)$; other _nanos fields match ^(0|[1-9][0-9]*)$. Negative zero is refused.
  • Approvals — rename gate_kind to gate_type and timeout_at to expires_at. Replace updated_at with nullable resolved_at. Use gate_type for the list filter.

What’s new

  • Direct saves — Your fleet setup shows the proposal below the chat. Save fleet and Save changes check it, then save. The terminal keeps /review and its digest.
  • Setup tab — purpose, instructions, version, triggers, and each schedule’s state and next run appear together. The Trigger tab is removed.
  • Setup chats — search, rename, or delete your chats. New titles use the first line of the first message or source description. Search covers titles and all messages.
  • Chat retention — saving a fleet or its changes closes the chat. The chat and its messages are deleted 30 days later, at expires_at.
  • Retries — draft creation, chat messages, Save, Apply, and publish return the original result for 24 hours. Reusing a key for a different request fails with 409 and UZ-AUTHOR-005.
  • Deleted retry results — return 410 and UZ-AUTHOR-013. After 24 hours, the key counts as new.
  • Library removal — deletes every revision. Saved fleets keep their setup; linked chats remain without the Library link.
  • Library tabs — All lists platform setups and those your account published. Workspace lists your account’s setups available to this workspace.
  • Lease history — finished runner leases remain for 30 days, even after fleet deletion. Open fleet appears only when you can open the fleet.
  • Request billing — each provider request uses its own time and prompt tier. Each token category rounds up separately. Fleet budgets count runs, excluding setup chats.
  • Billing outages — work continues, with charges recorded for later settlement. Settlement deducts the full charge and can make the balance negative. Added credits repay that debt first.
  • Callsigns — identify fleets in recent fleets, headings, runner leases, and billing.

API reference

  • Timestamps — accept any RFC 3339 offset, with at most millisecond precision.
  • Fleet descriptions — GET /v1/workspaces/{workspace_id}/fleets list items include the saved purpose as nullable description.
  • Schedules — include description, run_state, and nullable next_run_at. States are scheduled, paused, activation_pending, and next_run_unavailable.
  • Chat versions — GET /v1/workspaces/{workspace_id}/drafts items include nullable candidate_revision, setup_revision, and publication_revision.
  • Chat search — GET /v1/workspaces/{workspace_id}/drafts accepts q with 1 to 200 characters after trimming. Search ignores case across your titles and messages. The cursor is bound to the query.
  • Lease destinations — GET /v1/fleets/runners/{runner_id}/leases items include destination: available, fleet_deleted, or not_permitted. fleet_name, event_type, and actor can be null.
  • Advanced models — GET /v1/models?advanced=true includes advanced models. Rows include display_name, advanced, context evidence, and a dated prices schedule.
  • Event identifiers — event_id uses a version 7 Universally Unique Identifier (UUID), replacing <milliseconds>-<sequence>. This applies to POST /v1/workspaces/{workspace_id}/fleets/{fleet_id}/messages and webhook deliveries.
  • Runner status — GET /v1/fleets/runners and PATCH /v1/fleets/runners/{runner_id} return status, replacing admin_state.
  • Runner events — online and offline events put RFC 3339 last_seen_at in metadata. Filters since and until on GET /v1/fleets/runners/{runner_id}/events take RFC 3339 timestamps.

Bug fixes

  • Steer recovery — agentsfleet steer finds its event after the stream drops, even when newer events arrive.

CLI

  • Chats — agentsfleet chat list pages chats with --search, --limit, --cursor, --state, and --fleet. Use agentsfleet chat rename or agentsfleet chat delete for one chat.
  • Schedules — commands print the server’s description, status, and next run.
  • Models — agentsfleet models prints display names, exact rates, and context windows. Add advanced models with --advanced.
  • Billing — --limit accepts whole numbers from 1 to 100. Totals use exact nanodollar amounts.
API referenceBug fixes

Fleet pages accept compressed response validators

Fleet pages open when compression weakens the response’s entity tag (ETag). Conditional updates keep the strong version from the response body.

What’s new

  • Dashboard navigation uses clearer page headings, mint section markers, and brief transitions that respect reduced-motion preferences.
  • Fleet setup keeps instructions visible and places runtime JSON under Full setup details.
  • Mobile tables keep API-key actions and Billing amounts visible beside each record.
  • Billing includes settled setup-chat costs beside fleet-run costs, including paid failed attempts and costs from deleted private chats.

API reference

  • Killed fleets return 404 with UZ-AGT-009 for name or status updates after If-Match checks pass. Missing and outdated versions still return 428 and 412.
  • Cost history returns setup rows with charge_type: authoring and authoring_attempt_id. Setup rows can have null fleet_id, fleet_name, and event_id.

Bug fixes

  • Fleet pages accept matching strong and weak response validators while refusing a different version.
  • Platform Library updates retain the strong body version when compression weakens the response header.
  • Private chats load when the server omits unused draft metadata.
  • Activity and setup streams reconnect before the hosting time limit and recover saved activity.

CLI

  • Billing totals include setup costs. agentsfleet billing show --limit bounds charge records and keeps every returned charge before continuing with next_cursor.
UpgradingAPI referenceBug fixes

Setup chat uses configured models and saved inference settings

Setup chat selects an eligible configured model by default. Model endpoints, API formats, and output limits now come from the Model library.

Upgrading

  • Model configuration — configure each model’s inference API, HTTPS base URL, and maximum output tokens before using it. Existing rows without these settings remain unavailable for chat and fleet runs.
  • Daemon and runner — upgrade both together so runtime leases carry the saved inference settings.

API reference

  • Model settings — POST /v1/admin/models and PATCH /v1/admin/models/{id} accept inference with api, base_url, optional dialect, and max_output_tokens. Rates-only updates preserve saved inference settings.

Bug fixes

  • Model choices — chat lists eligible workspace-owner models and the active platform model. A missing configuration shows a centered explanation with a settings action.
  • Fleet library — All and This workspace tabs filter published setups. Empty views explain how to publish one.
  • Fleet list — an empty workspace keeps its New fleet action without an orphaned Your fleets heading.
  • Lease review — Status and the fleet link appear above expandable technical details.
  • Hosted access — public website access links lead to the waitlist, including Sign in links.
UpgradingWhat's newAPI referenceCLI

Fleets are written in a private chat, reviewed, and saved

A fleet now starts as a private draft in the dashboard or in agentsfleet chat. You review its exact setup, then choose Save; publishing to the Fleet library is a separate choice. SKILL.md and TRIGGER.md bundles are removed, with every command and route that imported or exported them. Run agentsfleet-runner 0.61.0 or later; an earlier runner is refused. Author a fleet covers the new flow.

Upgrading

  • Bundle storage is dropped — migration 950_drop_fleet_bundle_storage.sql drops core.fleet_library, core.tenant_fleet_library, and the core.fleets columns source_markdown, trigger_markdown, bundle_content_hash, and bundle_snapshot_key. Run agentsfleetd migrate in the rollout that replaces the binary.
  • Runners upgrade with the daemon — POST /v1/runners/me/leases now requires accepted_kinds, so a runner from an earlier build is refused.
  • Budgets are whole nanodollars — daily_nanos and monthly_nanos replace daily_dollars and monthly_dollars, as decimal strings. "1000000000" is one dollar; the caps are "1000000000000" a day and "10000000000000" a month.
  • Platform entries start over — repository-imported entries leave with core.fleet_library. An operator stages a reviewed draft, then publishes it from Fleet library under Platform.
  • Webhook URLs name their source — POST /v1/webhooks/{fleet_id} is removed. Each webhook trigger receives deliveries at POST /v1/webhooks/{fleet_id}/{source}, so register the url from the fleet’s webhook_urls with the provider again.
  • Every webhook source must be verifiable — a webhook source must be github, linear, or slack, or its trigger must declare signature.secret_ref for a Svix-signed provider. A Definition with any other source is refused, and so is one that sets signature.header, signature.prefix, or signature.ts_header.
  • The api trigger is removed — triggers may be empty or absent, and such a fleet runs only when it is sent a message. A Definition that names {"type": "api"} is refused.

What’s new

  • Private drafts — only the person who starts a draft can read it. Each chat message is charged to the workspace owner’s balance, and a short balance fails the message with UZ-AUTHOR-007.
  • Review before Save — a review shows the full Definition, its required access, its readiness checks, and a review_digest. Save, Apply, and publish refuse a stale digest with UZ-AUTHOR-006.
  • Setup can wait — a Save with an unmet check leaves the fleet installing and names each check. Saving raises the fleet’s integration grant; agentsfleet resume finishes setup once it is approved, and agentsfleet kill cancels it.
  • Improve, then Apply — Setup → Improve in private chat opens a draft pinned to the fleet’s setup revision. Apply reviewed setup makes it the next revision; running work keeps the one it started with.
  • Library publication is explicit — Save never publishes. Publishing creates an entry or appends an immutable revision, and fleets keep the revision they were saved from.
  • Slack channels live in the Definition — a mention trigger with "source": "slack" and one ID in channels attaches a fleet to a channel. Answer mentions in Slack shows the entry.

API reference

  • Drafts — GET and POST /v1/workspaces/{workspace_id}/drafts; GET, PATCH, and DELETE /v1/workspaces/{workspace_id}/drafts/{draft_id}; GET and POST .../drafts/{draft_id}/messages; POST .../drafts/{draft_id}/cancellations; GET .../drafts/{draft_id}/events/stream.
  • Library revisions — GET /v1/workspaces/{workspace_id}/fleet-libraries/owned; GET and DELETE .../fleet-libraries/{library_id}; GET and POST .../fleet-libraries/{library_id}/revisions; GET .../revisions/{revision_id}.
  • Models and fleet revisions — GET /v1/workspaces/{workspace_id}/models?purpose=authoring lists the owner’s eligible models. POST /v1/workspaces/{workspace_id}/fleets/{fleet_id}/revisions applies a reviewed improvement under If-Match and Idempotency-Key.
  • Runner authoring routes — POST /v1/runners/me/authoring-leases/{lease_id}/renew, /activity, /reports, and /model-calls. A lease_id that is not a version 7 Universally Unique Identifier (UUIDv7) answers 400 with UZ-REQ-001.
  • Save takes a reviewed source — POST /v1/workspaces/{workspace_id}/fleets requires an Idempotency-Key header and {source, name, review_digest, start}. source is a draft candidate or a Library revision; platform_library_id, tenant_library_id, and slack_channel_id are gone.
  • Save and run now reports its first run — with start set to {"kind": "run_now", "message": "<TEXT>"}, Save answers 201 even when the first run’s queue receipt is not saved yet. The reply’s start.state reads pending until then and accepted after, and a retry with the same Idempotency-Key confirms the same run.
  • Runner PATCH edits labels — PATCH /v1/fleets/runners/{runner_id} takes labels alone or with assigned_policy, and replaces the runner’s whole label set. A runner holds at most 32 labels of at most 64 bytes each.
  • Fleet PATCH sets status and name only — If-Match is required, and a missing tag answers 428 with UZ-AUTHOR-004. source_markdown, trigger_markdown, and config_json are gone from the body.
  • Publication bodies — POST /v1/workspaces/{workspace_id}/fleet-libraries takes {draft_candidate_id, label} with an Idempotency-Key header. POST /v1/admin/fleet-libraries stages {workspace_id, draft_candidate_id, label}, and PATCH with published publishes or withdraws.
  • Removed routes — GET /v1/workspaces/{workspace_id}/library-entries (use .../fleet-libraries/owned), DELETE .../library-entries/{entry_id} (use DELETE .../fleet-libraries/{library_id}), GET /v1/fleets/bundles, GET /v1/runners/me/bundles/{content_hash}, GET /v1/workspaces/{workspace_id}/preferences, DELETE /v1/auth/sessions/{session_id} (use DELETE /v1/auth/sessions), GET /v1/connectors/{provider}/callback, and POST /v1/webhooks/{fleet_id}. No alias remains.
  • Typed Definition schemas — FleetDefinition and the Definition* schemas describe the setup document {schema_version, description, instructions_md, config}, and fleet reads return definition in place of Markdown. description is required; it and instructions_md each allow 32,768 characters.
  • Error codes — UZ-AUTHOR-001 through UZ-AUTHOR-016 are new. UZ-BUNDLE-001 through UZ-BUNDLE-006, UZ-CATALOG-001 through UZ-CATALOG-005, and UZ-AGT-011 are retired.

CLI

  • agentsfleet chat — starts or resumes a private draft. --definition <PATH>, --library <LIBRARY_ID> --revision <REVISION_ID>, and --fleet <FLEET_ID> --setup-revision <N> start one without a message.
  • Scripted setup — with --no-input, --review prints Review digest: <DIGEST>. --save, --apply, and --publish <LABEL> then need --review-digest <DIGEST>, and --publish-to <LIBRARY_ID> --library-etag <ETAG> appends a revision.
  • A waiting Save exits 1 — the client prints each unmet check and the --operation-id and --candidate-id values that resume the same Save.
  • Removed commands — agentsfleet install, agentsfleet library create with --github, --from, and --template, agentsfleet fleet update, and --slack-channel. agentsfleet chat replaces them.
  • Library verbs — agentsfleet library list lists the entries this workspace owns, and agentsfleet library delete <LIBRARY_ID> removes one with its revision history.
Bug fixesAPICLI

Logout ends its own unfinished sign-ins, and the daemon refuses an unsafe identity base

The endpoint that aborts a person’s unfinished sign-ins has a path that says whose they are, agentsfleet logout can now reach it, and the daemon refuses to start with an identity provider base its secret key cannot safely travel to.
  • Aborting your unfinished sign-ins is DELETE /v1/auth/sessions — the route that ends sign-ins you approved in the browser and never finished in a terminal moved from /v1/auth/sessions/all, which read as every session, to the collection itself. It only ever touches your own sign-ins. The old path is gone.
  • agentsfleet logout aborts your unfinished sign-ins — the route now accepts your own command-line credential as well as a browser session, so logout’s abort no longer fails with 401. A tenant API key is still refused.
  • The daemon refuses an unsafe CLERK_API_BASE — every capability read sends the identity provider’s secret key to this base, so the daemon now starts only when it is https, or http on a loopback host for local work.
Bug fixesAPIUI

Lease timings measure pickup, and model tables read providers by name

Two timing fields now measure only what their names say, a refused secret field names the fields that work, and both model tables sort and show providers by vendor name. The first-token change needs agentsfleet-runner 0.60.0 or later.
  • First-token time counts from the start of the run — the run report’s time_to_first_token_ms, which product analytics records, no longer includes fetching the fleet bundle, hydrating memory, or preparing the sandbox. Expect the figure to drop by that setup time once runners upgrade.
  • Queue wait is logged only for a newly read event — runner_lease_issued leaves queue_wait_ms off a reclaimed lease and off an event delivered again after a wait, such as one parked for an approval, so the figure is pickup time alone. Clock skew between daemon replicas reads as 0, never below.
  • A wrong secret field names the fields the secret has — a request that names a field a fleet’s secret does not carry, ${secrets.github.password} against a minted GitHub token, is refused with that secret’s field names, and none of their values, instead of the refusal that says no other spelling exists.
  • Model tables sort providers by the name they show — the Provider column in the workspace Models table and in the admin Model library orders OpenAI before Tencent Hunyuan, as it reads. Provider badges are neutral, so a row’s Default badge stands out, and the admin Model library ends at its last row, as the Fleet library tables do.
What's newPerformanceUI

Fresh work reaches a runner on its next poll

A steer, webhook, or schedule used to wait up to four seconds for a runner’s poll to come round to its fleet. A lease poll now reads where fresh work landed first, so the next poll takes it. The poll change is in the daemon; the first-line and worker-spacing changes need agentsfleet-runner 0.60.0 or later. Install a runner covers the upgrade.

What’s new

  • Fresh work is found on the next poll — marking a fleet ready also signals every daemon which partition it sits in, and each daemon’s next lease poll looks there beside the partition it was due to visit. The poll still visits the sixteen partitions in turn, so a lost signal costs the old wait and never the work.
  • The live tail shows its first line at once — a run’s first activity frame, and its first answer chunk, post without waiting for the 250 ms batch.
  • Runner workers poll a quarter second apart — four workers on the one-second idle interval no longer poll at the same instant.
  • Each lease logs where its time went — lease_completed and lease_failed in the runner journal carry wall_ms, bundle_ms, memory_ms, sandbox_ms, model_ms, tools_ms, report_ms, and first_token_ms. A segment the lease never reached is left off the line.
  • The daemon logs how long an event waited — runner_lease_issued carries queue_wait_ms, from the event’s admission to its lease on the daemon clock.
  • Two new metrics — agentsfleet_lease_poll_dragonfly_commands_total counts Dragonfly commands per lease poll, two when idle. agentsfleet_fleet_ready_wake_failures_total counts doorbell rings and peeks that failed or passed their 100 ms deadline, and gaps where a daemon missed rings, by kind: ring, peek, or gap.
  • A refused write tells the agent not to retry it — the refusal for a request no rule admits, and for a secret the fleet does not have, now says the run’s rules are fixed, so another path, API, or credential will be refused the same way.
  • Provider names read the way vendors write them — Fireworks, OpenRouter, NEAR AI, and Atlas Cloud in the model picker and in both model tables, in one badge style.
  • Fleet library tables end at their last row — the pagination bar sits under the rows, as it does on the Models table.
SecurityBug fixesAPIDashboard

A workspace mints tokens only for connections it made

A runner token is now issued only for a connection the workspace itself made. A secret stored by hand can no longer name another workspace’s GitHub installation, or send a refresh to a server the connect flow never wrote.

What’s new

  • Deleting a connector’s secret disconnects it — DELETE /v1/workspaces/{workspace_id}/secrets/{name} with a connector’s name, such as github, now works like DELETE /v1/workspaces/{workspace_id}/connectors/{provider}. The connection and its event routing go together, and the response is still 204.
  • The delete dialog names the connection — on the Secrets page, deleting a secret that holds a live connection says which integration it disconnects, and the button reads Delete and disconnect.

Bug fixes

  • GitHub tokens follow the installation’s owner — a GitHub secret that names an installation this workspace did not connect is refused with UZ-GH-001, the same answer as a missing installation.
  • Refreshes stay with the provider — a Zoho, Jira, or Linear secret is refreshed only at the provider’s own token address or one of Zoho’s seven accounts servers. Any other address is refused with UZ-CONN-006, and nothing is sent.
  • Padded installation ids are refused — connecting GitHub with an installation id such as 0042 is refused, so one installation has one spelling.
UpgradingWhat's newRunners

The Rust runner replaces the previous runner

Release 0.59.0 ships agentsfleet-runner as one static Rust binary with a signed toolbox image beside it. It replaces the previous runner, and Allowlist egress is now enforced. Install a runner covers the new install.

Upgrading

  • Stage the toolbox with the binary — each release ships agentsfleet-runner-bundle-linux-amd64.tar.gz. The runner starts only with that release’s toolbox image, manifest, and signature in toolbox/incoming under its storage home.
  • The service runs agentsfleet-runner run — with Delegate=cpu io memory pids and DelegateSubgroup=runner, so each sandbox gets a cgroup of its own.
  • amd64 only — a release no longer ships an arm64 runner.
  • calculator is gone — the runner does not offer it. Remove it from TRIGGER.md.
  • Allowlist egress needs forwarding — set net.ipv4.ip_forward=1 on hosts assigned Allowlist egress. A host that fails the runner’s egress test reads degraded with egress enforcement unavailable.

What’s new

  • Allowlist egress is enforced — a sandbox reaches only the IPv4 addresses that the Allowlist and the fleet’s network.allow resolve to when the run starts. nf_tables rules on the host admit them, and port 53 is dropped.
  • An empty Allowlist means the default registry set — npm, PyPI, crates.io, and the Go module proxy.
  • Allow all egress shares the host’s network — No egress keeps the sandbox to its loopback interface.
  • A host that does not resolve refuses the run — the runner’s log names the host. An allowlist past 256 addresses is refused too.
  • network.allow entries may be URLs — http_request reads each entry the way the sandbox’s rules do, so https://api.github.com:443 admits api.github.com in both. Only the host counts.
  • A fleet host at a private address refuses the run — a network.allow host that resolves into 10.0.0.0/8, the tailnet’s 100.64.0.0/10, or 169.254.169.254 stops it. Allowlist hosts are exempt.
  • Runner traces and metrics — with OTEL_EXPORTER_OTLP_ENDPOINT set, a runner sends one trace per run and eight metrics, and holds no credential to do it. Any OTLP header variable, or a user name in the endpoint, stops it at start.
  • A fixed span budget — 256 spans per run and 128 per second; the rest are counted in agentsfleet_runner_spans_suppressed_total.
  • Workers grow without a restart — a higher Workers count starts more workers at the next heartbeat.
What's new

A chat follow-up continues where the last message stopped

On the Rust runner, a chat message now carries the thread’s recent turns, and the fleet’s next run reuses its sandbox for 10 minutes. A fleet can also hand work to child agents, and every sandbox has a stated size.

What’s new

  • Earlier turns reach the model — a message from Chat, agentsfleet steer, or a Slack mention carries up to 8 of the thread’s earlier finished turns, oldest first. Each message or answer keeps its first 16 KiB, and the oldest turns drop until the rest fit in 64 KiB.
  • The prompt’s start is cached — on the Messages and Responses APIs, the provider is asked to cache the tools, instructions, and earlier turns, so a follow-up can read them from its cache.
  • The sandbox waits 10 minutes — after a run finishes without failing, the same runner keeps the fleet’s sandbox, and the next run finds the files, installed packages, and caches the last one left. A background process ends with its command, and exec_command sessions end with the run.
  • A fresh sandbox otherwise — the next run starts clean once 10 minutes pass, the last run failed, the fleet’s repositories or network access changed, the fleet left active, or the runner restarted or went offline.
  • Child agents — delegate and spawn start a child that shares the run’s sandbox, files, memory, and budget. wait_agent, send_input, list_agents, and interrupt_agent drive a spawned child. Children nest two levels deep, with 4 working at once and 16 per run (child_cap_reached).
  • Every sandbox has a stated size — 2 GiB of memory, 2 cores, 512 processes and threads, and a 4 GiB disk. No fleet setting changes it yet.
  • /tmp shares the workspace disk — a full /tmp answers No space left on device, as a full /workspace does, and the sandbox keeps running.
  • Out of memory ends one command — the command reads Process killed: out of memory (out_of_memory), and the next command runs in the same sandbox with its files.
What's new

The Rust runner runs a fleet’s code in its sandbox

The Rust agentsfleet-runner now runs commands, file edits, patches, and git inside each run’s sandbox. It replaces the current runner at the cutover. Until then, your fleets keep the runner and tools they have today.

What’s new

  • shell — runs one command to its end in the workspace. A failed command answers its output, then its exit code. timeout_ms defaults to 10,000 and is capped at 600,000; a command past it answers timed_out.
  • exec_command and write_stdin — keep a process running across calls and send it input. A run holds up to 64 sessions (session_cap_reached), and every session still open stops when the run ends.
  • Long sessions stay heard — a session’s output streams as it is printed, and each call reads what arrived since the last one. Past 1 MiB unread, a call reads the first and last 512 KiB and a count of the bytes between.
  • A write the process will not take says so — write_stdin to a process that closed its input, or has not read what it was sent, answers one fixed sentence and the session runs on. A write to a process that had already ended says it was not delivered, then answers the ending. What a failed sandbox call says about its failure is at most 4 KiB.
  • Repositories are checked out before the first turn — each repository in a fleet’s repositories binding is cloned into the workspace when the fleet declares any tool that runs in the sandbox, file tools included. A read binding checks out the default branch. The GitHub token never enters the sandbox.
  • git — runs in that checkout. push, fetch, pull, remote, and clone answer subcommand_not_allowed, because the sandbox has no network. git runs hooks, ! aliases and configured commands, so declaring git lets a fleet run what shell can, inside the same sandbox.
  • apply_patch — applies a patch in the format Codex writes and answers the changed files, with +N −M on the last line. A patch that does not apply answers patch_invalid.
  • File tools — file_read, file_write, file_append, file_delete, file_edit, file_read_hashed, and file_edit_hashed reach only the workspace (path_not_allowed). A stale line tag answers hash_mismatch.
  • file_read pages by line — offset (from 1) and limit (2,000 when absent) pick the lines, and a page stays within 10,000 tokens. Its last line names the offset to read on from; an offset past the end answers invalid_arguments.
  • image — shows the model a PNG, JPEG, GIF, or WebP file of up to 5 MiB on its next turn. It needs a model on the Messages or Responses API; otherwise it answers image_input_unavailable. An oversized file answers file_too_large, and an unsupported image answers not_an_image.
  • Browser tools wait for the microVM engine — browser_open, browser, and screenshot answer browser_unavailable, and the run carries on.
  • The toolbox carries the tools — Node 20, Python 3.13 with uv, ripgrep, jq, curl, and the GitHub command-line tool, pinned to one Debian snapshot. A host mounts the toolbox only when its signed release manifest and digest check out.
Bug fixesDashboard

Fleet library tables share their columns

The workspace and platform Fleet library pages now show the same columns: Name, Source, Status, Time, and Actions.

Bug fixes

  • Workspace status — onboarded bundles show Ready in the workspace table.
  • Platform columns — the bundle hash column is removed.
  • Upload sources — both pages show an upload icon and source name; an empty source name uses the entry name.
What's newDashboardAPI

Invite teammates into your account

An account owner can now invite people by email. A teammate who accepts opens every workspace in the account and reads and steers its fleets. Invite teammates walks through it.

What’s new

  • Invites arrive by email — creating an invite emails the accept link. The People table shows Email sent, Email not sent, or Email not set up beside each invite, with a send button whenever this deployment sends email, so a link that never arrived can go again.
  • Members and owners — everyone who joins is a member. A member steers fleets but cannot store secrets, connect integrations, or manage people (UZ-AUTH-026).
  • Everyone sees every message — a message anyone sends to a fleet appears at once on every open screen, marked queued until the fleet starts on it. A teammate’s message shows their name above it.
  • One switcher, every account — the workspace switcher lists your own workspaces under Yours and each joined account under its owner’s name.
  • Removing a member takes effect within 15 seconds — including any page they have open.

API reference

  • POST /v1/tenants/me/invites — invites {email} and answers 201 with the invite, its link, and email_status: sent, failed, or unconfigured. GET lists pending invites with email_status and email_sent_at.
  • POST /v1/tenants/me/invites/{invite_id}/send — sends the invite email again. It answers 200 {"email_status": "sent"}, 503 UZ-INV-005 when email is unavailable, or 404 UZ-INV-001.
  • DELETE /v1/tenants/me/invites/{invite_id} — revokes a pending invite and answers 204. An invite its invitee already joined through answers 409 UZ-INV-003 with current_state member.
  • GET /v1/users/me/invites and POST /v1/users/me/invites/{invite_id}/accept — list and accept invites sent to the signed-in address. Another address is refused with 403 UZ-INV-002.
  • GET /v1/tenants/me/members and DELETE /v1/tenants/me/members/{user_id} — list and remove members. The last owner cannot be removed (409 UZ-INV-004).
  • event_admitted — a new fleet stream frame carrying the sender and message the moment a steer is accepted. A thread read returns waiting messages as queued rows.
DashboardAPIBug fixes

Chat keeps your place while a fleet replies

The fleet chat now holds still while a reply runs. When you send a message, it moves to the top of the panel once, and the reply grows below it. Folding a thought or a reply finishing no longer drags the conversation past you.

What’s new

  • A turn someone else sends leaves you where you are — if you have scrolled back, a teammate’s, API, or webhook turn does not move the chat. Jump to latest takes you to it.
  • Reasoning folds once — the thought folds when the answer starts and stays folded if the fleet reasons again. Thought counts every stretch of reasoning.
  • A send with no answer ends after 30 seconds — it reads Couldn’t confirm this message was sent. with Resend. Later messages to the fleet no longer wait behind it.
  • Another tab’s unsent message appears here — once the tab that sent it has closed, the message shows with Resend.
  • A dismissed message stays dismissed — in every tab of that browser, for a day.
  • A reused message identifier gets its own notice — This message conflicts with one already sent. offers Send as new instead of Resend.
  • A failed run carries a failure mark — so its sentence never reads as a short reply. Lines such as Completed. read as status, not as the fleet’s words.
  • The byte count appears near the limit — from 90% of 8,192 bytes, the message box shows the count, and Send is disabled past the limit.
  • Durations of a minute or more show minutes — for example 2m 05s.
  • Chat catches up after a lost stream — the chat fetches recent saved activity, and fleet tiles read catching up meanwhile. Missed reply fragments and tool updates may remain missing.

API reference

  • POST /v1/workspaces/{workspace_id}/fleets/{fleet_id}/messages — the 202 body carries replayed: true when the operation_id matched an earlier message, false for a new one.
  • A NUL character is refused — a message or operation_id containing one returns 400 UZ-REQ-001.
  • Tool activity frames carry call_id — each tool frame on the fleet stream names its call, so two calls to one tool stay apart.

Bug fixes

  • The reconnect notice no longer reads as an error — it is an amber warning above the message box and no longer pushes the conversation down. Retry now reconnects at once.
  • A thought clock stops when its reply’s end was missed — it no longer runs on after the stream dropped the reply’s last frame.
  • A replayed message no longer sits at Queued — its reply loads from the event.
DashboardAPIBug fixes

A message you resend runs once

A chat message now carries an operation_id, and the API answers a repeat of it with the first message’s event. Resending after a dropped connection or a reload no longer starts a second run, even if the fleet stopped in between.

What’s new

  • Unsent messages stay listed — each keeps its own Resend and Dismiss until you choose one, including after a reload or in another tab.
  • An unanswered send says so — Chat reads Couldn’t confirm this message was sent. when no answer came back, and Message not sent. when the server refused it.
  • Unsent messages stay with you — someone who signs in next on the same browser does not see them.
  • An expired session keeps Resend — the notice shows Sign in beside Resend.
  • Reasoning shows how long it took — a reply reads Thinking with a clock while the fleet reasons, then Thought and the total time.

API reference

  • POST /v1/workspaces/{workspace_id}/fleets/{fleet_id}/messages — a repeated operation_id returns 202 with the first message’s event_id, even if the fleet has since stopped, paused, spent its budget, or reached its limit of waiting work.
  • UZ-AGT-016 — the same operation_id with a different message, or from another sender, returns 409 with current_state: "admitted", and nothing runs. A workspace’s API keys count as one sender.
  • An operation_id sent before this release — is not recognized after it, so a retry that crosses the upgrade runs as a new message.
  • operation_id belongs to one fleet — the same value sent to two fleets is two messages.

Bug fixes

  • Two failed sends keep both messages — Chat used to keep only the latest one.
  • A dropped connection no longer sends a message twice — the automatic retry carries the same operation_id.
  • An over-long message is caught before it is sent — Chat shows Messages can be at most 8,192 bytes. instead of sending one the server refuses.
  • Malformed tool activity no longer breaks Chat — a tool update without a readable name is ignored.
DashboardWhat's newBug fixesAPI

Fleet chat shows replies as they arrive

You can send another message while a fleet is working. The dashboard keeps each accepted message in order, shows when it is queued, and adds the reply as it arrives.Update each agentsfleet-runner to 0.51.0 along with the control plane. An older runner still completes runs, but its replies appear only when the run finishes.

What’s new

  • Reasoning and tool activity have their own place — the chat keeps the final reply readable while showing progress during a run.
  • Reasoning text is lighter than the reply — the answer reads first, and the reasoning still meets the Web Content Accessibility Guidelines (WCAG) AA contrast level.
  • Reply text arrives in smaller steps — the dashboard receives new text every 200 milliseconds instead of every second.
  • event_complete carries the final reply — the fleet stream’s completion frame includes final_reply when the reply is at most 64 KiB. Without it, read the event.
  • A failed event carries a copyable diagnostic — open its event details and click Copy diagnostic to capture its identifier and recorded cause for an operator.

Bug fixes

  • A stopped reply has a plain message — chat no longer displays a runner error name as if it explained what happened. The cause remains available in the event diagnostic.
  • Streaming tool calls reach the fleet — a model that asks to use a tool while streaming can complete the tool call and continue its answer.
  • A brief network drop no longer fails a run before it starts — the runner makes up to 3 fleet bundle download attempts with a 5-second retry budget.
  • A failed bundle download no longer blames the fleet’s instructions — chat now says “The runner refused this run before the fleet started.”
What's newBug fixesAPICLIIntegrations

Mention a fleet in Slack and it answers in the thread

A fleet attached to a Slack channel now answers when someone mentions agentsfleet there, and posts its answer as a reply in the same thread. Until now the Slack route acknowledged every mention and dropped it. Answer mentions in Slack walks through the setup.

What’s new

  • Attach a fleet at install — --slack-channel <CHANNEL_ID> writes a mention trigger for that channel into the fleet’s TRIGGER.md. One fleet answers in one channel.
  • The fleet reads the thread — it sees the first message and the latest 19 replies, each cut at 2,000 characters and 16,000 in total. In a private channel, or when Slack takes longer than 1.5 seconds, it answers from the mention alone.
  • Name a fleet to pick it — start the message with a fleet’s name when the channel has several. An unnamed mention with several candidates gets a notice that lists their names.
  • Each channel gets its own fleet — the first mention no attached fleet takes creates slack-channel-<TEAM_ID>-<CHANNEL_ID>. It has no tools, a budget of 1.00 dollars a day, and memory scoped to the channel. It replies with the command that attaches a fleet.
  • The fleet page shows the channel — a fleet attached to a channel lists a Chat mention trigger with its channel identifier.
  • Write fleets answer only when named — a fleet with repository_access: write opens at most one draft pull request per mention. The fleet cannot merge that pull request, mark it ready for review, or change a workflow.
  • Fleets read their Continuous Integration (CI) runs — a GitHub-bound fleet’s token now reads workflow runs and jobs (actions: read), and check-run annotations (checks: read) where the GitHub App installation holds that permission. A permission the installation lacks is left out of the token, and the mint still succeeds.

API reference

  • POST /v1/workspaces/{workspace_id}/fleets — accepts an optional slack_channel_id, such as C0123456789, and adds the mention trigger for that channel.
  • POST /v1/connectors/slack/events — answers 200 with {"event_id": "<EVENT_ID>", "replayed": false} for an accepted mention, and {"ignored": "<reason>"} otherwise: unreadable_body, bot_message, team_not_mapped, or unsupported_event.
  • Slack signature errors — a bad signature returns 401 UZ-WH-010 and a stale timestamp UZ-WH-011, as every signed route does. The error registry now marks UZ-SLK-010, UZ-SLK-011, and UZ-SLK-020 retired.

Bug fixes

  • An answer with nowhere to go is no longer queued — an answer from a scheduled run, a steer, or a GitHub review was queued for its model provider, which is not a place to post, and then dropped. Only an event with a reply destination, such as a Slack thread, now owes a delivery.

CLI

  • agentsfleet install --library <LIBRARY_ID> --slack-channel <CHANNEL_ID> — attaches the new fleet to one Slack channel. An identifier that is not C or G followed by at least eight upper-case letters or digits fails before any request: must be a Slack channel ID: C or G, then upper-case letters and digits.
DashboardWhat's newBug fixes

A Fleet library row shows where its bundle came from

Both Fleet library tables spelled provenance as a prefix on every row — upload:probe/skill-only, github:agentsfleet/github-pr-reviewer. The kind is now its provider’s mark and the text is the reference alone, and a GitHub row links to the repository it was onboarded from.

What’s new

  • Source replaces Repository on the platform table — both Fleet library tables now carry the same columns in the same order: Name, Source, Status, Bundle, Time, Actions. The workspace table gained Time in place of Onboarded.
  • A GitHub row is a link — the platform row points at the reference it was fetched at and says on hover that a branch can move; the workspace row points at the repository, which is all that row stores.
  • Remove is the same icon as the Model library’s — a delete glyph with the verb in its tooltip, on both tables.
  • Both sidebar entries read Fleet library — the workspace entry under Configuration was Library. Only a platform admin sees both, and the group heading tells them apart.
  • The Getting started checklist has one button — the next step is now the page’s primary action, named for the step, and it moves on as each step lands. The rows were always links and never looked like it.
  • Install a fleet opens the fleet it recommends — the step’s hint names github-pr-reviewer, so the link now opens that card rather than the gallery’s front door.

Bug fixes

  • Events fills the page — a handful of events left the table floating in the top third of the screen with its pagination bar beneath them. The page reserved a minimum height where the table needed a definite one.
  • Platform > Fleet library fills the page — the same fault from the other side: the page reserved the height and the table never claimed it.
  • A connector row says Connected once — the row carried the word on its status pill and again underneath. The description now names the account the install landed on instead.
  • Connect and Disconnect are icons — matching every other row action, with the provider named in each tooltip.
  • Saving a fleet’s SKILL.md or TRIGGER.md just saves — the two documents shared one version tag, so saving either one told the other’s editor it was out of date. The editor then asked you to compare two copies that were identical and would not save until you did, and saving the second document put the first back into the same state. A save now re-reads the server before it believes a conflict: if your document has not moved, it saves. If it genuinely changed, it says so in one line and keeps your edit on screen; saving again replaces the server’s copy with yours. The API still refuses a stale save with UZ-AGT-014.
APICLIDashboardWhat's newBreaking changes

Remove a Fleet library entry your workspace added

You can now list the Fleet library entries your workspace onboarded and remove the ones you no longer want. Until now, every entry a workspace added stayed in the install gallery for good.Removing an entry leaves fleets installed from it alone. Installing copies the bundle onto the fleet, so the fleet keeps running from its own copy.

Breaking changes

  • agentsfleet library add is now agentsfleet library create — every other collection on this surface uses create, so the library group now matches. There is no alias: agentsfleet library add is rejected as an unknown subcommand, so update any script that uses the old spelling. Run agentsfleet library --help for the current verbs.
  • agentsfleet connector status is now agentsfleet connector show — reading one resource is called show everywhere else.
  • agentsfleet auth status is removed — agentsfleet whoami answers for it. The two showed nearly the same thing, so whoami gained the one field it lacked: source, which tells you whether the credential in use came from the environment or from a file. That matters because an exported service key overrides a stored login, so a terminal can look signed in as one identity while acting as another. agentsfleet doctor still checks reachability and workspace binding.

What’s new

  • agentsfleet library list — lists the entries your workspace onboarded, with the identifier that agentsfleet library delete takes.
  • agentsfleet library delete <ENTRY_ID> — removes one entry. Running agentsfleet library on its own still prints the full gallery.
  • A Library page in the dashboard — under the workspace sidebar. It lists what the workspace owns, and names the entry in a confirmation before removing it.
  • Removal is permanent — nothing is kept behind. Onboard the same bundle again and it returns as a new entry with a new identifier.

API reference

  • GET /v1/workspaces/{workspace_id}/library-entries — lists only the entries this workspace onboarded, never a platform entry, paged on its own cursor. The combined gallery at GET /v1/workspaces/{workspace_id}/fleet-libraries is unchanged, and agentsfleet install --library still resolves against it.
  • DELETE /v1/workspaces/{workspace_id}/library-entries/{entry_id} — removes one entry and returns 204. It needs the library:write scope. Repeating the call returns 204, and so does an identifier belonging to another workspace, so the reply never reveals whether an entry exists elsewhere.
  • No new error codes — a malformed entry_id returns 400, a cursor from a different collection returns UZ-LIBRARY-001, and a database failure returns UZ-LIBRARY-006.
APICLIWhat's newBug fixes

Every charge records the fleet that incurred it

Two fleets could share one event_id, because that identifier is minted per admission and nothing tied it to a fleet. The billing ledger settled a charge on (event_id, charge_type) alone, so two fleets’ charges could pile into a single row: one fleet’s spend counted against the other, with no report that would show it.Charges now settle on (event_id, charge_type, fleet_id), and fleet_id can no longer be null.

What’s new

  • agentsfleet billing groups charges by fleet and event — when two of your fleets share an event_id, you see one row per fleet instead of two charges merged into one line.
  • A charge always names its fleet — GET /tenant/charges was documented as returning a null fleet_id for charges written before fleet identifiers survived deletion, but no database holds such a row. workspace_id and fleet_name are still nullable, and fleet_name is still a snapshot taken when the charge was written.

Bug fixes

  • A charge counts only its own fleet’s spend — billing.usage_ledger now settles on uq_usage_ledger_event_id_charge_type_fleet_id. Two fleets charged under one event_id get two rows, each with its own credit_deducted_nanos. The second fleet’s charge used to be added to the first fleet’s row.
  • A continued run remembers what it resumed — a run continued from an approval keeps its resumes_event_id whichever writer lands first. Both the lease path and the approval path write this row, and whichever one arrived without a predecessor used to erase the value already stored, leaving the history unable to show what the run followed from.
  • A redelivered approval no longer announces the run twice — when an approval lands on a row the lease path already opened, no second event_received is published, so a console counts the run once.
CLIBreaking changes

The command-line client runs on Bun

agentsfleet now runs on the Bun runtime. Install Bun 1.4 or later before you install the client. Install the client with bun install --global @agentsfleet/cli. If bun is not on your PATH, the command reports that it was not found.

Breaking changes

  • --no-open and --no-input belong to login — write them after the command name, as agentsfleet login --no-open. Written before it, as agentsfleet --no-open login, they are now rejected as unknown flags. --api and --json are unchanged and still work in either position.
  • Bun 1.4 or later is required — the client no longer runs on Node.js alone.
  • --workspace-id is now --workspace — agentsfleet list and agentsfleet workspace show took --workspace-id, while the agentsfleet schedule subcommands took --workspace. One spelling now, matching --fleet. agentsfleet list --workspace-id <id> is rejected as an unknown flag; write agentsfleet list --workspace <id>.

What’s new

  • agentsfleet help <command> reads the same as agentsfleet <command> --help — both print the same body, for a command or a group.
  • An unrecognised command names a next step — a near match is suggested, and a command matching nothing points at the command list for the group you were in, not the root.
  • Help fits an eighty-column terminal — every line wraps at eighty characters, and options advertise the value they take, such as --limit <n> and --api <url>.

Bug fixes

  • A mistyped flag reports the flag, not your sign-in — agentsfleet list --limit 0 while signed out now says must be ≥ 1 and exits 4. It used to ask you to sign in first, and the flag was still wrong when you came back.
  • agentsfleet doctor exits non-zero when a check fails — it reported success while naming an unreachable server.
  • A refused value names the bound it crossed, the flag it came from, and the value you passed — agentsfleet list --limit 0 reports invalid --limit: must be ≥ 1 (got "0"). Flags and arguments read the same way, so a wrong value buried in a long command is visible without re-reading the line you typed.
  • --json prints the error envelope and nothing else — a rejected invocation used to print the help page above it, so reading the output as JSON failed on the first character. agentsfleet --help --json still prints help.
  • A dropped connection is retried, on every command — a socket lost mid-request used to fail the command outright with cannot reach agentsfleet API. It is now sent again, up to three attempts with a growing pause, because a reply you never saw is usually a request that never ran. A server that answers — a 503, a rate limit, a timeout the client declared — is unchanged: those still stop at one attempt for anything that writes.
  • What a retry can cost you, stated plainly — if the connection drops after the server accepted the write, the second attempt is a second write. agentsfleet secret create reports already exists for the value it just stored (the vault holds what you sent; no secret update is needed), and agentsfleet api-key create can leave a key you never see the secret for — check agentsfleet api-key list if a create is interrupted. A rejected certificate or an unusable URL is never retried, and now says so in its own words instead of suggesting you check your network.
APICLIWhat's newBug fixes

See who you are signed in as

agentsfleet whoami tells you the person, tenant, and credential this computer is signed in with. Signing in saves a credential that carries no readable details of its own — the server works out what it can do from the record it names — so until now nothing on your machine could answer that question.

Upgrading

Update the server before the client. A client that calls /v1/users/me against a deployment that does not serve it yet gets a 404, which says nothing about your credential, because the path is matched before the credential is checked. When that happens, agentsfleet login keeps the credential it just saved and tells you the deployment cannot answer, and agentsfleet whoami names the deployment instead of calling the credential good or bad. Every other command keeps working.

What’s new

  • agentsfleet whoami — prints email, name, user_id, tenant, credential, api_url, and scopes. --json returns the same fields for a script. It gains a source field on Sep 21, when it absorbs agentsfleet auth status. With nothing signed in, it points you at agentsfleet login and sends no request.
  • agentsfleet login names the account it signed you in to — the closing line reads signed in as <name> — <tenant> instead of login complete. It costs no extra request: the check that already ran after signing in now returns the identity.

API reference

  • GET /v1/users/me — returns user_id, email, display_name, tenant_id, tenant_name, credential, and scopes. It accepts a browser session token, a tenant API key, or a command-line credential; a tenant API key answers with the person who created it. It needs no scope, so it also works as a check that a credential is still valid. A caller this tenant does not know as a person is refused with 403.

Bug fixes

  • A credential without billing access is no longer reported as rejected — signing in used to verify a credential by calling GET /v1/tenants/me/billing, which needs the billing:read scope. Anyone signed in without that scope was told the server had rejected their credential, when the server had refused that one path. The check now calls GET /v1/users/me, which needs no scope.
APIWhat's newBug fixes

A reviewer fleet reviews without asking you first

Installing a fleet that declares a mintable credential now writes an approved integration grant and raises no approval card. Installing is the answer: you chose the fleet, its bundle names the integration, and TRIGGER.md names the repositories and the access level. A fleet declaring repository_access: write used to open a repository_write gate on every run, and because each model turn is its own event, a single instruction could raise a card per turn and post nothing.

What’s new

  • The integration grant authorises a repository write — one approved row in core.integration_grants per fleet and service, checked before any token is minted. agentsfleet grant list --fleet <FLEET_ID> shows it approved with an approved_at the moment the install returns.
  • agentsfleet grant delete <GRANT_ID> --fleet <FLEET_ID> is the stop button — the next event is refused before the fleet contacts the provider. It covers one fleet and one service, so it halts every repository that fleet reaches through that service; narrow a fleet’s reach by editing repositories: instead.
  • budget.daily_dollars bounds the spending — it halts the fleet rather than one credential request, and it is now the only spending limit on a write path.

Bug fixes

  • A continuation no longer raises its own card — each model turn carries a fresh event identifier, so a run that took ten turns asked ten times. It asks none.
  • repository_write gates are no longer raised — gates recorded before this release stay readable in the approval inbox, and an event already parked on one still resolves from it.
  • Three error codes retired — UZ-REPAIR-010, UZ-REPAIR-011 and UZ-REPAIR-013 can no longer be returned. A fleet without an approved grant returns UZ-GRANT-001 instead. The retired codes keep their anchors, because older logs still carry them.
  • The 32-request allowance is gone — it was counted per approval, and with one approval per run a ten-turn run drew far more than the number the card showed. budget.daily_dollars replaces it.
CLIWhat's newBug fixes

The terminal can answer a fleet that is waiting on you

agentsfleet approvals lists, reads, and decides approval gates. A fleet whose TRIGGER.md declares repository_access: write opens a gate on every run and parks until a person decides it, so agentsfleet steer on one of those fleets could not finish from a terminal before now.

What’s new

  • agentsfleet approvals — list, show <gate_id>, approve <gate_id>, and deny <gate_id>. The list prints GATE, FLEET, KIND, STATUS, and ACTION, and narrows with --fleet <fleet_id>. show prints the gate’s blast radius in full, so the reach is read before it is granted.
  • agentsfleet library add — onboards a workspace library entry from --github <owner/repo>, --from <path>, or --template <id>. Exactly one source is required; none or several exits 4. --ref pins a revision for --github, --replace overwrites an entry the workspace already holds, and the output names the entry’s secrets, tools, and network hosts beside the install command to run next.
  • agentsfleet status counts what is waiting — a Waiting row joins Name, Status, Events, and Budget. When any fleet is parked, the block ends with the command that lists the gates.
  • agentsfleet secret list is a table — NAME, KIND, and CREATED, with a readable date in place of the raw counter the old two-field output printed. --json is unchanged.

Bug fixes

  • agentsfleet library lists the workspace gallery — it read the platform-only catalogue, so an entry a workspace owned was installable but invisible. It now reads the same list install --library resolves against, and a TIER column separates platform from tenant. A workspace with no entries says so and names agentsfleet library add.
  • Server failures read as sentences — a refused install prints the sentence written for a reader, such as “That Fleet Bundle isn’t valid. It’s missing SKILL.md, or has an unsafe or oversized file. Check the source and try again.”, in place of the terse internal detail.
  • agentsfleet logout claims only what it does — its help now reads: revoke this machine’s credential, abort unfinished sign-ins, and clear local state. Other machines stay signed in, and it never claimed to reach tokens already minted.
  • The paging hint names a real command — a paged agentsfleet list points at agentsfleet list --starting-after <cursor>. It named agentsfleet fleet list, which does not exist.
  • One spelling for the library placeholder — help and hints advertise --library <library_id> throughout, including install --help.
APIBug fixes

A quiet fleet stops reading “Connecting…”

GET /v1/workspaces/{workspace_id}/fleets/{fleet_id}/events/stream opens with a hello frame as soon as the subscription attaches. A fleet with nothing happening sent nothing until the first heartbeat fifteen seconds later, so a surface that reports itself live off the first frame showed “Connecting…” while nothing was wrong.

Bug fixes

  • hello on a fleet’s stream — event: hello carrying {"kind":"hello","fleet_ids":["<fleet_id>"],"counters":{}}, naming the one fleet this connection serves. The workspace stream has always opened this way; the per-fleet route is what lacked it.
  • The sequence is undisturbed — the frame uses identifier 0 and does not advance the activity sequence, so a client may report the stream live without waiting for a heartbeat.
  • When no hello arrives — an instance holding no publish/subscribe connection sends none. The route still serves; it has nothing to say hello about.
API

A repeated report charges nothing

POST /v1/runners/me/reports takes the lease id as its idempotency key, so it accepts no Idempotency-Key header. Send the same report again after a lost response and the stored outcome comes back.

What’s new

  • The lease id is the key — one lease carries one terminal result, so the identifier already in the request is what makes a repeat safe. Nothing extra to send.
  • A repeat costs nothing — the second call returns the first call’s stored outcome, charges nothing, and counts the run once.
  • A superseded holder still writes nothing — a report from a holder the fleet has already replaced is refused by the fence, so a stale writer cannot land a partial finalize on the current holder’s run.
API

A retried steer runs once

POST /v1/workspaces/{workspace_id}/fleets/{fleet_id}/messages accepts an optional operation_id. Send the same value on a retry and the fleet runs once and charges once, however many times the request arrives.

What’s new

  • operation_id — an optional, nullable string on the steer body, your own name for the operation, repeated across its retries. A retry carrying a value already seen is answered with the first attempt’s event rather than starting a second run.
  • Why the caller supplies it — a timeout does not prove the request failed, and the server cannot tell a retried POST from a person pressing send twice, because the bytes are identical. Only the caller knows which one it is.
  • Leaving it out — omit it and every message is its own operation, so two identical messages start two runs. That is what a person pressing send twice expects, and it is why the field is optional rather than required.
Bug fixesAPI

One fleet’s broken configuration stops that fleet, not your runners

A fleet whose stored configuration cannot be read now ends its own event and leaves every other fleet leasable. Before, a runner poll that landed on such a fleet answered 500 UZ-INTERNAL-003, so one unreadable document could fail polls across a whole deployment.

Bug fixes

  • Runner polls — a fleet with an unreadable configuration answers 200 with {"lease": null, "retry_after_ms": 1000}, the same empty answer a poll gets when no fleet holds work, instead of 500 UZ-INTERNAL-003. The runner waits out the hint and polls again.
  • config_unreadable — a new failure_label on the refused event, returned beside failure_detail, so the fleet needing attention is named rather than inferred from a log.
  • Refused events close — the refusal writes the terminal gate_blocked row and publishes the closing bracket, so a watcher on that fleet’s stream sees the event end instead of hanging.
UIBug fixes

Chat keeps your place while work continues

Sending a message moves Chat to your new turn. A paw and Working… appear beside the fleet’s reply until it starts answering.

What’s new

  • Message field — longer drafts expand the field up to its height limit, then scroll inside it.
  • Jump to latest — the button stays above the message field when you read older turns.

Bug fixes

  • Reading position — new background activity no longer pulls you away from older messages.
  • Fleet tabs — scroll sideways so every tab remains reachable on a narrow screen.
  • Status line — figures wrap, and a long outcome shortens to fit the available width.
Bug fixesUI

Quiet chats renew their connection automatically

Brief connection renewals no longer interrupt a quiet chat with a reconnect warning.

Bug fixes

  • Connection recovery — long-lived quiet connections no longer accumulate failures across routine renewals.
  • Silent failures — stalled connection attempts time out. Chat detects connections without activity or a server heartbeat after 45 seconds of silence and retries automatically.
  • Connection notice — sustained failures explain that automatic retries continue. Retry now starts an immediate attempt when you want one.
  • Overlapping recovery — callbacks from a replaced connection cannot close its replacement or change the conversation.
UIWebsiteBug fixes

The credit balance shows on every page

Every dashboard page now shows what is left to spend. A Credits chip sits in the header beside the workspace switcher and links to Settings → Billing. When the balance is exhausted the chip turns red, because that is the state that blocks the next run.
  • Precision — the chip rounds to cents. A balance under one cent keeps four decimals, and a smaller one reads <$0.0001. A live balance never shows as $0.00.
  • Freshness — the figure is rendered with the page rather than polled. A tab left open overnight shows last night’s figure; Settings → Billing holds the live one.
  • Billing outage — the chip renders nothing rather than a wrong number, and the rest of the dashboard still loads.
  • Narrow screens — the chip is hidden below 640px, where the header has no room for it.

What’s new

  • Fleet and runner consoles — each console renders one section strip at every width. The 224px navigation rail that appeared on wide screens read as a second sidebar and took that width from the transcript.
  • Status line — the metric cards under each strip are now one monospace line. On a fleet it sits under the composer; on a runner it sticks to the foot of the canvas. It carries the same figures the cards did.
  • Runner checks — the checks card is now a verdict on the runner’s identity line. It reads checks passed, N checks failed, checks stale, checks pending, or checks never run. A failure is the one state painted red, and the verdict opens the full report in a dialog named after the host.
  • Tabs — the fleet and runner section strips use the underlined tab style that Billing and the settings sections already use.
  • Fleet transcript — a fleet’s replies read at 18px instead of 15px, the reading size rather than the interface size.
  • Core capabilities — the landing section shows one divided list at every width. It stacked a three-across card grid on a four-across card grid, rendering the same seven items twice.
  • Landing hero — the kicker above the headline is gone. It rearranged the words of the headline beneath it.

Bug fixes

  • Fleet library import — importing a bundle from GitHub now has 30 seconds instead of 10. A repository that GitHub served slowly failed in the dialog while agentsfleetd was still fetching it. The two pages that host the action declare a 60 second server ceiling, so the dialog’s message arrives instead of a gateway 504.
  • Balance card caption — the billing balance card drops its spent $X · N events caption. It summed only the charge rows the page had loaded, so page two reported a different figure for the same account.
  • Landing page headings — five sections opened with a 12px eyebrow marked as a heading, above their real heading. A screen reader heard ten headings where there are five sections.
  • Approvals link — the link in the fleet status line underlines on keyboard focus, not only on hover. Its target is a finger’s height rather than 12px.
  • Admin lease filter — the filter fits a phone screen. It carried a 720px minimum width that the fixed shell clipped.
  • Header edge — the brand and the avatar sit the same distance from the window edge. The header’s trailing side followed the canvas gutter plus whatever scrollbar the platform reserved. At 1920px the avatar sat 48px in from the right, the brand 22px in from the left.
APIUI

Every live frame carries the fleet’s counters

Each frame a fleet publishes on its live tail now carries events_processed and budget_used_nanos as an absolute snapshot, and the workspace stream’s hello carries a counters map keyed by fleet id. Both counters only ever grow, so the Fleets wall keeps the greater of the figure it holds and the figure a frame carries, and never adds: a dropped, duplicated, late or out-of-order frame cannot leave a tile wrong, and a gap in the stream is followed by a fresh hello. A tile is correct before its first event: the hello reports where each fleet stands at subscribe time.
  • Frames — event_received, event_complete, gate_opened and gate_resolved all carry the pair. A frame the daemon could not fill omits both fields rather than sending zeros; a client leaves the figures it has standing.
  • hello — {"kind":"hello","fleet_ids":[…],"counters":{"<fleet_id>":{"events_processed":n,"budget_used_nanos":n}}}. A fleet that has never run answers with zeros; a fleet the read could not answer for is omitted.
  • Wall — the tile footer no longer derives its figures from the streamed rows, and the wall no longer re-reads the fleet list when a row arrives without a price. A run that parks behind an approval shows the event it counted.

Dashboard

  • Navigation — items are symmetric pills with no accent rail; the four groups share one 16px eyebrow rhythm.
  • Tables — a paginated table lets the page scroll rather than opening a second scroll region; cells sit on the 4px scale and the page controls align with the body’s right edge.
  • Buttons — one primary action per surface renders the default variant; every other non-destructive action is ghost.
  • Runs strip — a processed run whose list row carries no body reads Completed.; only the event detail, which holds the body, says when no reply was recorded.
  • Admin leases — the Fleet column names the agent by callsign, as every other agent column does, and keeps the fleet id on hover.
APIUIBug fixes

Connecting GitHub completes again

Pressing Connect on GitHub now finishes and the connector reads Connected. Connect authorized the account and obtained its grant, then failed while asking GitHub which App installations the account can reach; the connection was never written.A refusal at that step now answers 502 with UZ-CONN-009, separately from the token exchange. The two shared UZ-CONN-006, so a connect that had already obtained its grant reported a failed exchange and pointed at credentials that were working.

Bug fixes

  • Sidebar toggle — the collapse control sits in the sidebar column, aligned to the navigation beneath it, and centres in the rail when collapsed.
InternalPerformance

Four benchmark lanes measure what steer, lease and delivery sustain

make bench-steer, make bench-lease, make bench-outbound and make bench-cardinality drive the production paths against the compose Postgres and Redis and write one JSON result each under bench/results/. make bench-compare LANE=<lane> PROFILE=rig prints the delta against the committed baseline in bench/baselines/ and always exits 0 — no build fails on a throughput number.
  • Profiles — PROFILE=rig is unbounded against the compose stack; dev and prod carry caps and refuse a parameter above them before opening a connection. prod also refuses without BENCH_LOAD_PRODUCTION=i-accept-the-blast-radius.
  • Attribution — every result carries Redis commands and Postgres round trips separately, read from the servers’ own counters, so a saturation is attributed rather than guessed.
  • Idle lease poll — 1 Redis command and 0 Postgres round trips, so idle cost scales with runners and not with fleets. A contended lease costs about 37 round trips.
  • Delivery head-of-line — with one destination in sixteen answering in 250 ms, the other fifteen’s p95 rose to 3.8 s: one stream, one worker, one queue position at a time.
  • Honest zeros — a lane refuses a knob it cannot parse, writes no tail for an empty distribution, and records a window that ran out as an abort; bench-compare names differing parameters before any delta.
  • Cardinality — about 4.6 KB of Redis per idle fleet, flat from 10 to 10 000 fleets; a million idle fleets is roughly 4.6 GB.
  • Sweep — a run creates only under its own prefix and removes everything it created on every exit path, reporting created against swept in the result.
APIBug fixes

Session verification bounds stale signing keys

Browser session verification stops using signing keys more than six hours and fifteen minutes after their last successful refresh. A provider outage beyond that ceiling returns 503 with UZ-AUTH-004; verification recovers when a refresh succeeds.

Upgrading

  • Fleet versions — use stable MAJOR.MINOR.PATCH values. Import now refuses prerelease and build suffixes that installation cannot accept.

Bug fixes

  • Signing-key restrictions — session verification refuses incompatible key usage or algorithm metadata and ambiguous signing-key identifiers.
  • Capability outages — repeated requests retain the documented stale capability fallback without extending its age.
  • Malformed URL escapes — memory paths reject escapes such as %+a instead of decoding them as bytes.
  • Extreme dates — timestamps beyond the supported calendar range display their original millisecond value as text.
UICLIBug fixesPerformance

Dashboard reads stop retrying at a deadline, and a write is never sent twice

A dashboard page read gives up after 20 seconds of retries instead of holding the page, and any approve or steer waiting behind it, for half a minute. A write that may have reached the server is never sent again.

Bug fixes

  • Retry deadline — a read stops retrying 20 seconds after its first attempt, whatever the attempt ceiling says.
  • Retry-After cap — a 429 asking the dashboard to wait more than 10 seconds fails at once with the 429; a shorter wait is obeyed exactly.
  • Writes sent once — a POST is sent again only when the connection was refused or the server declined to run it. A reset after sending, a timeout, or a gateway 502, 503 or 504 surfaces the error instead, so a steer never becomes two events.
  • Spread retries — retries after a shared blip spread across the full backoff window instead of landing together.
  • Failure classes — a 408 or 425 is reported as its own class, no longer as a server error, and a socket failure is read from Node’s error cause rather than its message.

CLI

  • Writes sent once — agentsfleet no longer replays a POST after its own 15 second timeout or after a socket reset. A POST refused at connect is still sent again.
  • Retry-After cap — agentsfleet waits at most 10 seconds for a Retry-After; a longer ask fails at once with the answer.
WebsiteBug fixes
The website’s Dashboard links open the waitlist.
  • Installation instructions — the website hides the unavailable shell installer command and its copy button. The agents page retains the npm instructions.
What's newUIBug fixes

Incident response examples and clearer dashboard navigation

The website leads with incident response examples. Brighter surfaces, larger touch controls, and clearer spacing make the dashboard easier to read and navigate.

What’s new

  • Documentation theme — headings, reading text, and code use distinct fonts; the docs share the website’s brighter backgrounds and mint accents.
  • Website navigation — mobile and desktop links expose the dashboard, workflow examples, and agent resources at /agents.
  • Account settings — profile and sign-in security open on a dedicated page at /settings/account.
  • Workspace names — leave the name blank to use a generated name.

Bug fixes

  • CLI login — browser approval reaches the terminal correctly, and a sign-in redirect no longer displays an unapproved verification code.
  • Keyboard navigation — dialogs return focus to their opening control, focused fleet tabs stay visible, and document previews support keyboard scrolling.
  • Header controls — the workspace switcher and avatar align vertically, and runner refresh matches the other action buttons.
  • Failed initial reads — approvals, events, integrations, secrets, and billing show recovery when their list or balance request fails.
What's newUIAPIPerformance

A fleet chat follows its runs from the live tail, and the dashboard changes the screen at the click

The daemon now announces every run on the fleet’s live tail: one frame when a run starts, and one when it ends that carries the finished row, the fleet’s status, and how many approvals are waiting. A chat you are watching reads nothing to follow it. The summary strip moves the instant a run ends, the “approvals waiting” link appears when a gate opens and disappears when someone answers it, and the transcript never re-renders, flickers, or scrolls. Deleting a secret, cordoning or revoking a runner, and approving or denying a gate change the screen as soon as you confirm; the server confirms in the background, and a refused change puts the row or badge back with the reason beside it.

What’s new

  • The daemon brackets every run on the live tail — event_received when a run’s row is written, including the run an approval continues, and event_complete when a report or a refusal closes it. The completion carries the row as the events list serves it (status, outcome, tokens, duration, spend) without fleet_id and workspace_id, which the stream names, plus fleet_status and pending_approvals. A gate opening, a decision, and a sweep publish gate_opened and gate_resolved with the count; gate_resolved.event_id is null for a gate raised outside a run. The API reference for both event streams now lists every frame kind. The CLI’s steer tail, which always waited for event_complete, now receives it.
  • The chat summary is a view over the stream — the strip’s figures and its pending count come from the frames, not from a request. A run that pauses or kills the fleet re-renders the page once so the Stop, Resume, and Kill controls follow; nothing else does.
  • The fleet detail carries pending_approvals — GET /v1/workspaces/{workspace_id}/fleets/{fleet_id} counts the gates waiting on a human, so a console opens on the fleet and its thread alone instead of paging the approvals inbox to render a number.
  • Secrets, runner state, and approvals paint at the click — the row leaves or the badge changes when you confirm, before the server answers. The confirm dialog stays disabled until the write settles. A refusal restores the row and shows the error next to the control. A secret delete re-reads the list only when the outcome is unknown: a timeout, a network fault, or a server error.
  • Dashboard reads retry transient failures — a read answered with 408, 425, 429, 502, 503, or 504, or failed by the network, is retried up to 3 attempts. The wait starts at 250 ms, doubles each time, is capped at 2 s, and honours a Retry-After header. Writes are sent once: a write that timed out is never replayed, because the server may have processed it.
  • Every dashboard request times out — a request with no answer after 10 seconds fails and, for a read, is retried like any other transient failure. A hung approval resolve times out instead of pending forever. A stalled backend no longer holds a page open.
  • Runner detail and the platform model library load one round-trip sooner — each page starts its two reads together instead of one after the other.
  • The approvals inbox stops polling while its tab is hidden — and reads once when you look at it again.
What's newObservability

The daemon exports traces, metrics, and logs over OTLP

agentsfleetd now sends all three signals to one OpenTelemetry Protocol (OTLP) endpoint, named by the OpenTelemetry specification’s own variables. With no endpoint set, the daemon starts, serves, and exports nothing. With an unreachable endpoint, requests are unaffected: batches drop and a counter records the loss.

What’s new

  • OTEL_EXPORTER_OTLP_ENDPOINT names the collector — traces post to /v1/traces, metrics to /v1/metrics, and logs to /v1/logs under it. No default. Unset means no export.
  • OTEL_EXPORTER_OTLP_HEADERS adds request headers — comma-separated key=value pairs. A pair without = stops the daemon from starting. No default.
  • OTEL_EXPORTER_OTLP_PROTOCOL picks the encoding — http/protobuf (default) or http/json. Any other value, grpc included, stops the daemon from starting.
  • OTEL_EXPORTER_OTLP_TIMEOUT bounds one export — whole milliseconds, default 10000. Zero or an unreadable value stops the daemon from starting.
  • GRAFANA_OTLP_ENDPOINT, GRAFANA_OTLP_INSTANCE_ID, and GRAFANA_OTLP_API_KEY still work — the endpoint is read when the standard variable is unset, and the pair becomes a basic Authorization header. A standard header of the same name replaces it. These names retire with the previous daemon.
  • Metrics are collected every 5 seconds — the interval the previous daemon used, so a series keeps its rate across the switch.
  • Lost telemetry is counted, never retried — spans, log records, and metric collection cycles that failed to send are counted in the process. A resent window would double-count delta families.
  • The log signal counts without logging — a warning about a failed log export would become a log record handed back to the exporter that just failed it. For logs the count is the whole report.
  • A family this build does not produce is named at start-up — each is logged once, at debug level, with the reason, rather than exported as zeros.
  • The start-up line names where the endpoint came from, never its value — startup_otel_enabled carries source=OTEL_EXPORTER_OTLP_ENDPOINT or source=GRAFANA_OTLP_ENDPOINT, because the header beside the endpoint carries a credential.
Bug fixes

An unreadable PostgreSQL certificate file names itself at startup

An unreadable certificate path in DATABASE_URL_MIGRATOR used to surface only as No such file or directory after the database server answered. The error now names the variable, certificate parameter, and path before agentsfleetd migrate opens a database connection.

Bug fixes

  • Certificate file errors identify the setting — sslrootcert, sslcert, and sslkey failures name DATABASE_URL_MIGRATOR, the canonical parameter, and the unreadable path under UZ-INTERNAL-001.
  • Malformed inline certificate data stays out of errors — PEM-like values are redacted instead of being printed as file paths.
What's new

Every command answers a mistyped invocation the same way

A missing argument used to be reported one of two ways depending on which command you ran. agentsfleet logs named what was missing and exited 4; agentsfleet events printed a bare parser message, offered no way to fix it, and exited 2 — the same code a network failure returns, so a script could not tell a typo from an unreachable daemon. Every command now prints what is missing and the command that supplies it, and every rejected invocation exits 4. Exit 2 means a network failure and nothing else.

What’s new

  • A rejection names the fix, not just the fault. agentsfleet events answers ✕ error: missing required argument 'fleet_id' followed by Suggestion: usage: agentsfleet events [options] <fleet_id>.
  • An unknown command still suggests the nearest match and points at the command list it belongs to.
  • --json rejections are parseable. A rejected invocation emits {"error":{"code","message"}} on standard error with a stable code — MISSING_ARGUMENT, MISSING_OPTION_VALUE, MISSING_REQUIRED_OPTION, UNKNOWN_COMMAND, UNKNOWN_OPTION, INVALID_ARGUMENT, or EXCESS_ARGUMENTS. It printed human text before.
  • A command group run bare prints help on standard output, so agentsfleet workspace | less is no longer empty. It went to standard error while exiting 0.
  • agentsfleet logs stops repeating itself. Its suggestion line said the same sentence as the error above it; it now carries the runnable usage line, as grant list now does too.
  • The published exit-code table is what the client does. The command reference listed 2 as covering invalid input as well as network failure; that row is now network failure alone.
What's newBug fixes

A hardened runner completes credentialed work again

On runners with the full sandbox tier, every model call that carried a credential failed within milliseconds. The sandbox granted its scratch space as read-only while the mount beneath it was writable, and the engine writes each call’s authorization header to a private scratch file — so the very first credentialed call died. Both layers now read one shared list of writable paths, so they cannot disagree again.

What’s new

  • Run checks can catch this class of fault. The runner’s check panel gains a fourth line: the scratch dir accepts a write inside the sandbox. It runs under the same restrictions your work runs under, so a sandbox that would fail your fleets fails the check first.
  • The runner page reads plainer. The lease filter is labeled Filter with an Apply button, the filter accepts workspace:<id> and fleet:<name or id> written exactly as its hint shows, and the lease and activity tables use the same Time and Duration column names as the events list.
  • Every runner action carries an icon, and Cordon and Drain show as disabled with the reason “Not active yet” until they are ready to use.
  • A refresh button re-reads the runner page on your click — after you request checks, the verdict appears without a full page reload.
  • Runner states are explained where they show. The state chip links to the states a runner shows, and that section now defines every admin state and liveness value on the page.

Bug fixes

  • The check panel’s relative timestamp shows the absolute time on hover, matching the lease table’s time column.
What's newBug fixes

Installing a fleet is one step

The install flow asked you to name the fleet, then asked again to confirm it. A name already in use failed the whole install at that second step. Install now runs from the card straight through to the live states, and the server renames a taken default for you by adding a three-digit suffix. Asking for a duplicate name yourself is still refused with UZ-AGT-006, because that one you meant.

What’s new

  • A renamed install tells you its new name. The response carries the name that was stored, and the dashboard shows that name rather than the one it started from.
  • The platform fleet library takes a bundle from your computer. Admin → Fleet library → Create fleet library now offers the same GitHub and upload sources the workspace dialog already had. An uploaded entry records no repository and still lands as a draft, so publishing stays a separate act.
  • A runner policy dialog shows what is already mounted. The paths agentsfleetd mounts on its own are listed as read-only rows, so you can see the baseline before adding to it. The screen says “mounts” throughout; the wire field is unchanged.
  • Model prices read as Input, Cached, and Output, in United States dollars per 1 million tokens.
  • Runners have a page. Runners covers enrolment, every policy field with its default and range, and what makes a runner stop taking work.

Bug fixes

  • A development runner no longer fails every lease with runner_crash/HostResolutionFailed. The sandbox’s readable-path set is derived from the same bind list the child is handed, so the files needed to resolve a host name are inside the sandbox. Operator mounts also reach the child at the mode they were assigned, read-only or read-write.
  • Dialog buttons say Create. The button that opens the dialog keeps its noun, such as Create fleet library, so the two are no longer the same words in two places.
SecurityBug fixes

A revoked integration grant stays revoked

Revoking a grant only ever wrote to the grant itself — it had no reason to know about the fleet’s approval gate. A gate raised before the revoke and left unanswered was still pending, and resolving it later, even as an ordinary approval, matched the grant by fleet and service alone with no check on its current status. The revoke was silently reversed, with no error and no record that it happened.

Security

  • A stale gate can no longer resurrect a revoked grant. Gate resolution now leaves an already-revoked grant alone, whatever decision the gate carries. Re-authorizing a service goes back through the install flow, which raises a fresh gate.
BreakingWhat's newCLI

--provider is checked against the model catalogue your server serves

A provider credential could name any string, and the failure surfaced later as a fleet that could not reach any model. agentsfleet secret create and secret update now check --provider against GET /v1/models, the same catalogue the dashboard’s model picker reads. The client carries no provider list of its own, so a provider becomes available by being added to the catalogue rather than by upgrading the client.

Breaking changes

  • A provider your catalogue does not price is refused. The message names what your server does price and points at the custom-endpoint form for anything it does not. Free-form --data bodies stay unvalidated.
  • The typed form requires --provider. --api-key with --model and no provider used to store a credential whose provider was the empty string — a provider key that could never dial.
  • claude-cli, codex-cli, gemini-cli, openai-codex, and claude-code are refused by name. Each names a local coding tool that carries no API key. The refusal says so and does not offer the custom-endpoint form — these tools have no endpoint to point at.

What’s new

  • agentsfleet models lists the catalogue — provider, model id, context window, and per-million-token rates, with --provider <id> scoping it to one provider. Rates are charged under platform-managed posture only; a self-managed credential is billed by your own provider account, so an unpriced row shows a dash.
  • --model is checked too, against that provider’s models only. It used to be validated nowhere, so a typo stored a credential that failed at the first event.
  • Provider ids match without case. --provider Anthropic stores anthropic, the catalogue’s spelling and what the runtime compares against. Model ids are matched exactly — they belong to the provider, and several are case-sensitive.
  • An empty or unreachable catalogue accepts the value. A newly deployed server has no catalogue until an administrator primes it, and an outage is not a reason to refuse a credential. The server validates either way.
  • The credential store reads the caller’s environment. AGENTSFLEET_STATE_DIR handed to the process now reaches every read and write, not only the ones that consulted the process environment directly.
  • An unreadable credentials.json warns instead of impersonating logged-out. A permission or disk error prints warning: could not read credentials.json with the cause, then continues as logged out. A missing file stays silent, as before.
SecurityBug fixes

Sign-in events record the caller’s address, not the proxy’s

X-Forwarded-For and Fly-Client-IP were read under capitalised names. httpz lowercases header names in place while parsing and req.header is an exact lookup with no case folding, so both reads returned nothing on every request and the derivation fell through to the raw connection peer. That is the correct answer for a deploy with no proxy in front of it, so the address chain read as healthy while recording the load balancer on every event.

Security

  • A forged X-Forwarded-For is caught again. When the forwarded chain and Fly-Client-IP disagree, the Fly value wins and the request is marked divergent. Two unreadable headers meant that comparison ran on two empty values, so client_ip_divergent was false on every event ever emitted.
  • A replayed session is refused when it arrives from another address. The consume path keys idempotency on sha256(address || user-agent || session id) — a matching fingerprint within 60 seconds returns the cached payload, any other fingerprint is terminal. With the address pinned to the proxy it separated nobody, so a session presented from elsewhere behind the same proxy, under a matching user agent, fingerprinted identically and was handed the cached payload instead of being refused.

Bug fixes

  • client_ip and client_ip_source name the caller and the header it came from. The seven session audit events — created, approved, verified, verify-failed, consumed, consumed-replay, aborted — carry the derived address. client_ip_source read tcp_peer on all of them whatever the request actually carried.
  • created_from_address records where a credential was minted. The core.cli_credentials column holds the address the operator signed in from rather than the proxy’s, so it can identify a machine.
BreakingSecurityWhat's new

Login saves a credential that outlives the browser session

agentsfleet login used to save the browser session token it recovered, and that token is valid for about a minute. A terminal went stale while its operator was still reading the success message, and no renewal path exists. Login now spends that minute exchanging the session for a durable afc_ credential, and the credential is what reaches disk. It resolves to the person who signed in rather than to the tenant, so an audit trail records who ran a command instead of a free-text label.

Breaking changes

  • agentsfleet login --token is gone. The flag is refused as an unknown option rather than ignored. Piped input goes with it: a login with no terminal attached and no AGENTSFLEET_API_KEY fails and names that variable. Unattended callers set AGENTSFLEET_API_KEY to a tenant API key, which authenticates every request and writes nothing to disk.
  • agentsfleet logout no longer ends your dashboard session. It revokes this computer’s credential, aborts any unfinished sign-in, and clears local state. A browser session is a different credential class refreshed through the identity provider, so signing out of a terminal leaves the dashboard signed in. A revoke that cannot reach the server still clears local state, warning that the credential stays live until you revoke it from the dashboard.
  • A deployment that sets OIDC_PROVIDER=custom is refused at boot. The value selected a second ladder that read capability and workspace claims from custom_claims, app_metadata, and namespaced keys, none of which anything writes. OIDC_PROVIDER=clerk is unchanged, so no deployment that runs today needs editing.

What’s new

  • One live credential per computer. A second login from the same computer revokes what it left behind, and core.cli_credentials carries a partial unique index, so two live credentials for one person and computer cannot be recorded at all. Only a hash is stored, so the credential cannot be read back from anywhere. agentsfleet logout is how a computer ends its own.
  • A credential records the server that issued it. Later commands reach that server with no flag and no environment variable, resolving --api, then AGENTSFLEET_API_URL, then the recorded server, then the built-in URL. A saved credential recording no server, dialed at a server nobody named, is refused before anything is sent with DEPLOYMENT_UNKNOWN and exit code 1. logout and doctor still run — one ends a credential, the other explains the refusal.
  • Three error codes name what went wrong. UZ-AUTH-023 says the credential was revoked, UZ-AUTH-024 says no live credential of yours has that identifier, and UZ-AUTH-025 says login recovered a session but could not exchange it, so nothing was saved. A 401 or 403 from the client now names the API URL the credential was presented to.

Security

  • Minting a credential takes a browser sign-in. A credential still authenticates listing and revoking, so a terminal can end its own access without opening a browser, but it cannot mint a replacement. Minting used to accept an existing credential, which turned one stolen credential into an unbounded supply under machine names of the caller’s choosing.
  • A stored value is checked against its whole shape. The load path and the mint response both require afc_ followed by exactly 64 lower-case hexadecimal characters. A prefix check passed values carrying trailing bytes.
  • A tenant API key carries its creator’s capabilities. An agt_t key resolves its capability set from the identity provider, keyed on the created_by subject it already stored, replacing a nine-entry set compiled into the binary that no operator could narrow. Narrowing that person narrows every key they minted, on the next request past the cache window. A creator the provider no longer knows resolves to no capability; an unreachable provider past the staleness ceiling reports unavailable rather than an empty grant.
  • Each claim is read from exactly one key. The capability claim is scopes and nothing else. The reader used to try OAuth2’s scope first, so a token carrying both would have supplied a capability set we never granted, on the authorisation path. The scp spelling and the workspaceId alias are unread.
BreakingBug fixesAPI

Metered runs are priced from the model catalogue

A promotional window sat in front of every price: a per-tenant timestamp, nullable with no default, that nothing ever wrote, where empty read as “still open”. Every account held it empty, so every metered stage priced to zero for the life of the account. The window is deleted rather than repaired — pricing resolves from the model catalogue and the execution posture alone, and no rate resolver takes a clock, so a price cannot drift with wall time. Free usage is the starter grant and the balance draining from it, bounded by exhausted_at.

Breaking changes

  • GET /v1/tenants/me/billing no longer returns free_trial. The response carries balance_nanos, updated_at, is_exhausted, and exhausted_at, which is the whole billing state a client needs. The member is gone from the published schema and from its required list, so a client that reads it must stop.

Bug fixes

  • A metered platform stage is charged the catalogue rate. Run time and all three token tiers — input, cached, and output — accrue against the credit pool at the rates the catalogue carries, instead of resolving to zero. A self_managed stage is unchanged: the run fee only, with token counts recorded and not charged.
  • A model the catalogue does not price is refused rather than run for nothing. The window answered ahead of the catalogue lookup, so a provider-and-model pair with no rate row priced at zero instead of failing. Renewal and settlement now fail closed on an unpriced pair; the lease estimate still admits the run, so it surfaces as a billing error rather than a silent free run.
  • The budget gates can refuse a fleet. A fleet budget is spent against real rates, so credit_deducted_nanos accrues from the first metered slice. While every charge was zero no budget was ever consumed and neither gate could stop anyone.
  • The free trial is named early access. The pricing card reads “Early access”, and the answer to “What am I actually paying for?” no longer describes an ending — it used to say metering began “after the trial”. Free usage is the starter grant, which ends when the balance does rather than on a date. The rates themselves are unchanged.
  • Deleting a fleet erases its memory. Rows in memory.memory_entries go with the fleet they belong to. They used to survive it, and because every sweep is scoped by an enumerable fleet, memory whose fleet was already gone was unreachable by all of them — an erased account kept it indefinitely. The isolation boundary is unchanged: the memory role holds no grant on core and cannot name core.fleets.
What's newSecurityIntegrations

A fleet with write access always parks for a human

A fleet whose repository binding declares write access now parks every event at an approval card before anything runs — even with no gates block, and even when its gate rules match nothing. Gate rules are editable with the same permission that wakes the fleet, so they cannot hold this boundary; the write kind itself does. The card states the write blast radius (the repository and what one approval spends), and the evidence line is sealed so crafted backticks in model output cannot break out of its code span.

What’s new

  • incident-repairer joins the fleet library. It wakes on a failed deploy workflow run, reads the incident timeline and the repository at the exact verified head, authors a forward fix, then pushes one branch and opens one draft pull request over the GitHub API — no checkout, no git tooling. A human approves the write before any of it, and a human reviews the bytes on the pull-request diff before any merge.
  • A write-scoped GitHub token spends a recorded approval, never a live setting. The mint checks the durable gate row for the run’s own event — its status, its kind, and the repository binding stated on the card a human actually saw. No recorded approval → 403 UZ-REPAIR-010. The binding drifted since the card was approved → 403 UZ-REPAIR-011. Tokens never carry the workflows permission, so an approved fix cannot rewrite the pipelines that gate it.
  • A repair pull request is linked to its incident, then to its deploy result. When the repair branch’s pull request opens, the platform records the link in core.repair_pr_links; when the next deploy on that branch completes, the row is stamped with the outcome. The stamp matches the repository as well as the branch, so a run finishing on a same-named branch somewhere else cannot claim it. Rows are insert-only — a replayed webhook answers duplicate_repair_link (UZ-REPAIR-012) rather than overwriting history.
  • A fleet no longer wakes on its own repair. Pull requests and workflow runs on a repair branch are the crew hearing itself, and a failed one used to look exactly like a fresh incident — so the fleet would set out to fix what it had just written, asking for another approval each time round. That traffic is now recognised and dropped on both webhook routes.
Bug fixesUI

A runner you assign now runs the work

A host could show Active and Online, take an isolation assignment, and still fail every job. Two faults stacked: the daemon never enabled the resource controllers systemd had delegated to it, and the sandboxed child had no HOME from which to resolve its configuration — so each lease died at startup with UZ-EXEC-012, and the journal named a code and no cause. Controllers are now enabled at daemon start rather than on first assignment, agentsfleet-runner.service supplies the HOME, and a host that cannot enforce limits is refused before it is given a runner.

Bug fixes

  • Every lease died at initialization when the service had no HOME. systemd gives a service with no User= no home of its own, and the sandbox forwards HOME only when the daemon holds it, so the engine could not resolve a configuration directory. The unit now sets it to its own RuntimeDirectory, which ProtectHome=yes and ProtectSystem=strict leave reachable where /root would not be.
  • Resource controllers are enabled at daemon startup, not on the first assignment. Delegate= makes cpu memory pids available to the unit, but writing cgroup.subtree_control is the delegatee’s job and systemd never does it. Doing it at startup means a runner with no assignment yet still enables them, and the post-deploy check no longer races the first heartbeat.
  • A failed configuration load names its cause. The error was discarded before it reached the log, leaving UZ-EXEC-012 with nothing to act on; the record now carries the underlying failure.
  • Execution cgroups are removed when a lease ends. Teardown used a recursive delete, which the kernel refuses because the control files inside a cgroup cannot be unlinked — so every reclaim failed and the directories accumulated on the host for the life of the runner.
  • An unrecognised runner failure is no longer blamed on the fleet. A cause the chat surface could not classify fell back to “this fleet needs instructions” and appended the internal error name. A cause that is not operator-facing prose now reads as a runner failure, and the identifier is not shown. A fleet that genuinely has no instructions still says so.
  • A host that cannot enforce limits is refused before deployment. The readiness check ran only after the runner was installed, and stopped at the first missing controller — so a host missing all three reported one. It now names every missing controller in a single run and verifies the kernel and system.slice before the deploy proceeds.

Assigning a policy

  • The policy dialog scrolls. Its actions sat below the fold on a short viewport with no way to reach them, so assigning a policy required maximising the window.
  • The three isolation tiers sit on one row. Two columns wrapped the third onto a row of its own.
APIBreaking

The free trial ends per account, not on a calendar date

The trial boundary was a constant compiled into the server and both web surfaces. It passed at 2026-08-01T00:00:00Z and flipped pricing for every tenant at once, with no deploy. The boundary now lives on each account — billing.tenant_billing.free_trial_ends_at, where NULL means open-ended — and every account today is open-ended: a trial ends when an end date is set on it, never on its own.
  • free_trial.ends_at_ms on GET /v1/tenants/me/billing is now nullable. Null means the account’s trial has no end date set — it is not a missing value. Render it as “no end date”, never as an expiry at the epoch.
  • The pricing pages no longer print an end date. The banner and hero pill read “Free during early access”, and the Terms point at the Dashboard for an account’s own boundary instead of naming a date that can go stale.
BreakingWhat's newBug fixesAPIUIPerformance

Every runner lease says whose workspace it belongs to

A runner’s lease list showed every workspace’s work in one table, so an operator on a shared host could not tell their own fleet’s runs from anyone else’s. Each lease now names its workspace, and one click narrows the table to it. The same release stops the runner pages re-reading a host’s whole history on every load, and starts pruning that history so it cannot grow without bound.

Upgrading

  • Runner lease history is pruned 30 days after settlement. A sweep deletes a settled lease 30 days from when it settled — not from when it started — with the two per-lease activity records describing it. A lease still running or renewing is untouched, and the runner’s lifecycle activity (the Activity feed) is kept at any age, so a long-lived host never shows an empty feed. A lease whose runner died without reporting is marked expired at the same window, then keeps its own. Lifetime counts on the runner page are unaffected — they count transitions, not surviving rows — but export raw lease history older than 30 days from GET /v1/fleets/runners/{id}/leases before upgrading.
  • Four migrations apply on startup. They add a per-runner counter table, an index for the activity feed’s filtered reads, the delete grants the sweep needs, and two indexes that keep the sweep itself off whole-table scans. The counter table backfills itself from existing history inside the migration, so no step is manual and no existing row is rewritten.

What’s new

  • The lease table has a Workspace column. Each row links to that workspace’s fleet wall and carries the full identifier in its tooltip; a funnel beside it narrows the table to that workspace alone.
  • The workspace filter lives in the address bar. ?workspace=<id> is deep-linkable, survives a reload, and Back steps out of it — so a narrowed view is something you can send to someone.

API reference

  • GET /v1/fleets/runners/{id}/leases?workspace_id=<uuid> — new optional filter, still requiring runner:read. The response shape is unchanged and total narrows with the page. A malformed value answers 400 UZ-REQ-001; a well-formed identifier that owns nothing answers an empty page rather than an error, so a stale link reads as “no leases” instead of failing. starting_after composes with the filter: the cursor must name a lease on the filtered page, and one taken under a different workspace_id answers the same 400 UZ-REQ-001 instead of silently skipping rows.

Bug fixes

  • A runner that refused a run no longer reads as a fleet missing instructions. Both conditions reported the same startup class, so a sandbox, egress, or resource-control refusal on the host was blamed on the fleet’s own configuration. The chat row now names the runner’s reason.
  • Deleting an account unregisters its scheduled runs. The purge removed the schedule rows but never told the upstream scheduler, so an erased tenant’s cron kept firing at runners indefinitely. Teardown now unregisters upstream while those rows still exist, and counts a provider failure on agentsfleet_account_teardown_unregister_failures_total rather than swallowing it — erasure still completes either way.
  • One failed schedule no longer strands the rest. The unregister pass stopped at the first provider error, leaving every schedule behind it firing forever. Each schedule is now attempted; a failure is logged with the identifiers needed to retire the survivor by hand, and missing provider credentials count on the same failure counter instead of passing silently.
  • Simultaneous account deletions no longer skip the scheduler. Concurrent deletions could exhaust the database connection pool, time out every unregister, and still complete the purge. The unregister pass now runs without holding a connection, so deletions queue instead of starving each other.
  • A stale lease link offers a way back. A bookmarked lease page whose workspace filter or page cursor the server no longer accepts showed “try refreshing”, which replayed the same address every time — and the control that could clear the filter sits inside the table that a failed read does not draw. That page now links to the newest leases instead.
  • The runner page no longer re-counts a host’s whole history to show four numbers. Acquired, succeeded, failed, and expired are maintained as each lease settles, so the page cost stays flat however long the host has been running.
BreakingWhat's newAPIUISecurity

The isolation you assign is the isolation the runner applies

The sandbox tier picked in Add Runner used to be a label: the host read a different value from its own environment file, neither side compared the two, and a host that could not deliver its claimed isolation kept accepting work. Policy now flows one way — the dashboard assigns it, the runner row stores it, every heartbeat delivers it — while the host probes what its kernel can enforce and reports that back. A runner whose assignment exceeds its capability is marked degraded, receives no work, and its row names the missing mechanism (for example cgroup controllers not delegated) instead of showing a green badge over a host that refuses every job.

Upgrading

  • POST /v1/runners takes the assignment envelope — the body is {host_id, assigned_policy: {sandbox_tier, network_policy, registry_allowlist[], worker_count}, labels[]}; the old top-level sandbox_tier answers 400 UZ-REQ-001. The response echoes the assignment as stored (worker_count clamped into [1, 64]).
  • The runner environment collapses to the bootstrap pair — AGENTSFLEET_API_URL and AGENTSFLEET_RUNNER_TOKEN, plus the optional host-local RUNNER_STORAGE_HOME (renamed from RUNNER_WORKSPACE_BASE). RUNNER_HOST_ID, RUNNER_SANDBOX_TIER, RUNNER_NETWORK_POLICY, RUNNER_REGISTRY_ALLOWLIST, RUNNER_WORKER_COUNT, and the RUNNER_CP_*_MS deadlines are removed, not deprecated — the daemon no longer reads them, so a stale env file cannot fork the truth again.
  • macos_seatbelt leaves the tier vocabulary — it never had enforcement code, and a tier that cannot be applied must not be assignable. A stray stored value parses fail-closed and the runner refuses to lease. The assignable tiers are landlock_full, container_nested, and dev_none (dev builds only).
  • Existing runners read degraded once repaired — pre-upgrade rows carry no assignment. A live, upgraded host marks its own row degraded — no assigned policy on its first heartbeat; a host that is down, or still on the pre-upgrade daemon, keeps a healthy-looking row until the manual statement below runs. The fix is the same either way: open the runner, use Edit policy, and the host applies it on its next heartbeat.
  • One migration applies on startup, one statement runs by hand — the migration adds the policy, capability, and verdict columns to fleet.runners and rewrites no rows. Right after deploying, mark the pre-upgrade rows degraded yourself (idempotent; it only matches rows without an assignment): UPDATE fleet.runners SET degraded = TRUE, degraded_reason = 'no assigned policy' WHERE network_policy IS NULL;

What’s new

  • Add Runner assigns all four policy fields — isolation, network policy (defaults allow_all until allowlist enforcement ships), registry allowlist, and worker count — with copy that says the host must satisfy the selection.
  • Edit policy on the runner page — PATCH /v1/fleets/runners/{id} with {assigned_policy: {…}} re-assigns a live runner; the change reaches the host within one heartbeat, and the verdict is re-checked in the same request. Growing the worker count past what the daemon started with takes effect after a runner restart.
  • The runner row shows assigned against achievable — the fleet list and detail reads carry assigned_policy, the host’s reported achievable mechanisms, and degraded with its reason; a degraded row wears a badge and states what is missing.
  • A runner that recovers, recovers by itself — a later heartbeat whose report satisfies the assignment clears the verdict and leasing resumes; UZ-EXEC-017 names the refusal while it stands.
Bug fixesCLISecurity

Steer never misses the reply’s opening words

agentsfleet steer now subscribes to the live event stream and confirms the subscription is established before it sends your message. A fast fleet could previously publish its first words before the terminal was listening — the command sat silent and recovered only the final text from history. The opening words now stream live on every run.

Bug fixes

  • Subscribe before send — the steer turn opens its Server-Sent Events tail first and waits (up to 2 seconds) for the server to confirm the subscription; frames that arrive before the server names the event are buffered and replayed in order, and a tail that cannot open falls back to the previous post-then-poll behavior.
  • Cancel before send — Ctrl-C that lands before the message goes out now interrupts the turn without sending; the fleet no longer executes a message you cancelled.
  • No stray connections — every steer turn now provably closes its stream on success, failure, and interrupt alike.

Security

  • Dashboard toolchain refresh — Next.js 16.2.12 clears three high-severity advisories (Server-Side Request Forgery in Server Actions, a middleware bypass, and a request-driven denial of service), and the dashboard now type-checks and builds on TypeScript 7’s Go-native compiler.
What's newPerformance

Follow-up — chat replies start streaming immediately

Fleet replies now appear in chat the moment the fleet starts answering. The runner used to hold the first activity frames of a run in a batch for up to a second before shipping them; it now ships the first frame of a run and the first response chunk the instant they arrive, so the chat surface shows the fleet is working — and its first words — at model speed. Long runs batch exactly as before, at a cost of at most two extra requests per run.
BreakingWhat's newAPICLIUIPerformance

A runner has a page of its own, and every list pages by cursor

A runner used to be a table row with a dialog of raw events behind an icon, and that dialog’s only number counted both halves of every execution — a host that had run 4,000 events reported about 8,000 of something. Each runner is now an addressable page opening on its leases: what it is working on now, and for anything that failed, the reason in plain English linked to the fleet whose work it was. Separately, no list pages by number any more — page numbers repeat and skip rows whenever something is inserted mid-traversal, which on a host acquiring leases continuously is every few seconds.

Upgrading

  • page and page_size are removed from three reads — GET /v1/fleets/runners, GET /v1/fleets/runners/{id}/events, and GET /v1/api-keys. All three take ?starting_after=&limit= and answer {"items": [...], "total": N, "next_cursor": "..."}; follow next_cursor until it is null. A request still sending either parameter answers 400 UZ-REQ-001 rather than being silently ignored, so client and server upgrade together.
  • sort is removed from GET /v1/fleets/runners — newest-first is the only order. Its non-default values existed to serve a sortable column on the table this release replaces, so the capability left with the control that used it. Sending sort answers 400 UZ-REQ-001.
  • The fleets list renames its cursor on both sides — the request parameter cursor becomes starting_after and the response field cursor becomes next_cursor on GET /v1/workspaces/{workspace_id}/fleets. Both old spellings are refused, not translated.
  • The memory list envelope drops request_id — it is now exactly {"items": [...], "total": N, "next_cursor": "..."}. Read the request identifier from the response header if you were consuming it from the body.
  • agentsfleet api-key list no longer takes --page or --page-size, and agentsfleet list no longer takes --cursor. The paging flags are gone rather than deprecated: an invocation carrying one fails as an unknown option and makes no request. Use --starting-after on agentsfleet list; api-key list needs no flag, since it now returns every key.
  • Two index migrations apply on startup — both add an index and touch no column, so no row is rewritten and no step is manual.

What’s new

  • /admin/runners is a card wall, and each card is a link. A card states what its host is working on in one line, or that it is idle, and shows administrative state before liveness so a cordoned host never reads as healthy.
  • /admin/runners/{runner_id} opens on Leases — a metrics strip over the standard table, live leases first, each row carrying its outcome. A failed row reads the same plain-English sentence the fleet console uses, never the machine tag, with the daemon’s detail line beneath it. Activating a row opens Review lease: fencing token, kind, provider, model, posture, token meters, and expiry.
  • Activity carries lifecycle records only. Lease acquire and release are excluded because the lease table already states each of them once, with its outcome — which is what removes the doubled count.
  • An expired lease is never credited with someone else’s success. Outcome is computed from the lease’s own status first, so a lease this host stopped renewing reads expired even after another host finished the same work. A lease whose fleet event is missing reads as not recorded rather than as a success.
  • agentsfleet api-key list returns every key you hold. The list follows the cursor to the end instead of showing a first page, and the dashboard’s key list drops its pagination footer while keeping column sorting.
  • agentsfleet memory list takes --starting-after <KEY>, and the dashboard’s memory panel now shows every entry a fleet has learned rather than the first page of them.

API reference

  • GET /v1/fleets/runners/{id} — requires runner:read. Returns the runner with derived liveness, active_lease_count, active_fleet_count, and lifetime leases_acquired / leases_succeeded / leases_failed / leases_expired, all computed from durable lease and event rows. Never returns token_hash. An unknown id answers 404 UZ-RUN-014.
  • GET /v1/fleets/runners/{id}/leases?starting_after=&limit= — requires runner:read. limit defaults to 50 and is refused above 100. Each item carries outcome (running, succeeded, failed, expired, or unknown), failure_label and failure_detail when it failed, fencing_token, kind, provider, model, posture, the three token meters, and the fleet and workspace identifiers plus the fleet name so a link needs no second read. Never returns request_json. An unparseable starting_after, or a limit outside the range, answers 400 UZ-REQ-001.
  • GET /v1/fleets/runners/{id}/events?event_type=<tag>[,<tag>…]&starting_after=&limit= — event_type now accepts a comma-separated set and returns the union. An unrecognised tag anywhere in the set answers 400 UZ-REQ-001 and no partial result, so a typo can never read as “no such events”. Follow next_cursor until it is null to read a runner’s whole history.
  • GET /v1/workspaces/{workspace_id}/fleets/{fleet_id}/memories?starting_after=&limit= — all three query shapes (recent, category, and text search) page by cursor over creation order, so a filtered or searched list can no longer be silently truncated at the first page.

Bug fixes

  • The memory list’s next-page hint is runnable. It printed a command without --fleet, which memory list requires, so copying it produced a usage error instead of the next page. It now carries the fleet you asked about.
  • Paging a runner’s leases no longer costs its whole history. The lease table accumulates a row per claim and is never pruned, so reading one page had been sorting everything that host had ever done. Both of the read’s access paths are indexed now, and page cost stays flat as the history grows.

CLI

  • agentsfleet list [--starting-after <ID>] [--limit <N>]
  • agentsfleet memory list --fleet <ID> [--starting-after <KEY>] [--limit <N>] [--category <NAME>]
  • agentsfleet api-key list [--sort <FIELD>] — no paging flags
BreakingWhat's newAPICLIUI

Replacing a secret means sending the secret you want stored

A stored secret can never be read back, so a field-by-field change could not be checked by the caller — and on most secret shapes the old field patch silently did nothing. Replacement is now total everywhere: the API takes the whole body, the client gets agentsfleet secret update, and the dashboard’s Edit dialog is the Add form, prefilled.

Upgrading

  • PATCH /v1/workspaces/{workspace_id}/secrets/{secret_name} is removed — replaced by PUT on the same route. PUT takes {"data": {...}}, the same shape create takes, and replaces the stored body whole; a field you omit is absent afterwards. There is no compatibility spelling, and a PATCH now answers method-not-allowed. Client and server upgrade together.

What’s new

  • agentsfleet secret update <name> replaces a stored secret in one call. The name stays claimed for the whole call, so fleets that require it keep resolving — no delete-and-recreate gap. It takes the same --data object (or --data=@- on stdin) and the same typed custom-endpoint flags as create.
  • The dashboard’s Edit dialog is the Create form, prefilled — provider, base URL, and model come from the row you opened; you supply the key. A custom endpoint’s base URL is editable after creation for the first time.
  • Edit writes the model entry before the credential — a credential is shared by every entry referencing it and can never be read back to restore, so it is written last. If the credential write is the one that fails, the model change that already committed is re-read into the table rather than left off screen.

API reference

  • PUT /v1/workspaces/{workspace_id}/secrets/{secret_name} — request {"data": {...}} (non-empty object, at most 4 KiB stringified); 200 {"name": "..."}. A name the workspace does not hold answers 404 UZ-VAULT-003 and creates nothing — a replace racing a delete cannot resurrect the deleted secret.
  • The write is one statement. Two concurrent replaces resolve to the later body whole; no merge of the two can be stored.

Bug fixes

  • Reconfiguring an existing custom endpoint from the Add dialog works again — it still sent a create, which stopped overwriting on Jul 28, 2026, so reusing the name answered UZ-VAULT-005 and saved nothing. It now replaces the endpoint’s stored body.
  • A failed save in Edit no longer strands the shared credential — a save changing both the model and the key could commit the new key, then report failure, leaving every entry sharing that secret on a credential the dashboard said was not saved. The credential is now the last write, so a failure ahead of it touches nothing.

CLI

  • agentsfleet secret create on a name that already exists still reports skipped and exits 0; the message now points at agentsfleet secret update instead of delete-and-recreate.
BreakingWhat's newBug fixesAPIUICLI

The dashboard loads the page you asked for, and a failed read says so

The Fleet library gallery and the Models registry used to read everything they held before painting, then render a failed read as an empty one — a workspace whose library was merely unreachable was told it had none, with no retry and no way to tell the two apart. Both now load one page at a time, keep what is on screen when a read fails, and state what they have not loaded. Creating a workspace secret has also stopped overwriting a secret that already holds the name.

Upgrading

  • Creating a secret no longer overwrites one that exists — POST /v1/workspaces/{workspace_id}/secrets answers 409 UZ-VAULT-005 when the name is already held, and writes nothing. Rotate with PATCH /v1/workspaces/{workspace_id}/secrets/{secret_name} instead. The database decides, so two concurrent creates on one name resolve to one 201 and one 409. Reconnecting a provider and refreshing its token are unaffected — those are rotations, not creations.
  • agentsfleet secret create --force is gone — it relied on the endpoint upserting, so it could only have failed. Passing it exits non-zero before anything is sent; creating an existing name reports skipped and exits 0. Rotate in place with PATCH /v1/workspaces/{workspace_id}/secrets/{secret_name}, which holds the name for the whole call. The client has no rotate command yet, and delete-then-create leaves the name absent between the two calls — any fleet requiring it fails in that gap.
  • ?q= is gone from GET /v1/models and GET /v1/workspaces/{workspace_id}/fleet-libraries — the substring filter published on Jul 27, 2026 had no caller on any plane. A request that still sends it is answered as though it were absent, so you get a page rather than an error. Page with next_cursor and filter client-side.
  • support_files has left every API response — the admin catalog was the last surface carrying it. The manifest is still written and stored on import, and the content-addressed bundle tar remains the authoritative file list, so no install behaviour changes.

What’s new

  • The Fleet library gallery and Models registry load more instead of everything — each states how many entries it is holding and that more remain, so a bounded page never reads as a complete list.
  • Your place in the gallery survives a reload — the cursor of the page you are on is written into the address bar. A reload, a shared link, or a Back out of a fleet lands where you were rather than at the first page.
  • The model catalogue loads when you reach for it — hovering, focusing, or opening the model picker fetches it, and an ordinary visit to Models does not. Hover is skipped on touch pointers and under Save-Data, where a hover is not a guess about intent.
  • A deep link to a library entry that is not on the loaded page says so — it neither errors the page nor quietly shows you something else.
  • The Add-model dialog will not save against a secret list that never loaded — it used to submit anyway, skipping the name-ownership check. Save now waits for the list, says what it is waiting for, and offers a retry when the read fails.

API reference

  • New error code: UZ-VAULT-005 (409) — a secret with this name already exists in the workspace.
  • UZ-LIBRARY-003 narrows to a limit outside 1 to 100. Its search-length cause left with ?q=; the code and its registry row are unchanged.
  • GET /v1/workspaces/{workspace_id}/fleet-libraries cursors are bound to the workspace and page size, no longer to a filter.

Bug fixes

  • Fleet chat replies render as they arrive — a reply stayed invisible until you switched tabs or reloaded, while the typing indicator ran and the Live badge stayed green. Every streamed frame is named by its kind, and a named frame reaches only a listener registered for that name; the per-fleet stream listened for unnamed frames alone and dropped all of them.
  • The Events tab scrolls again — expanding a grouped run set left the page frozen in a non-maximized window. The table’s scroll region contained the wheel on both axes while having nothing of its own to scroll vertically, so the page never received it.
  • Sortable column headers read evenly — the unsorted indicator was drawn heavier and taller than the sorted one, on every sortable column at once.
  • Runner rows lost a copy button that copied nothing useful, and now lead with administrative state before liveness.

CLI

  • agentsfleet secret create costs one request — it no longer reads the secret list first to decide whether the name is free. The endpoint decides, which also closes the window where two creates of one name could both believe they were first.
BreakingWhat's newBug fixesAPICLI

Model and Fleet library reads are bounded pages

The model catalogue and the workspace Fleet library gallery used to return everything they held in one response. Both now return a page, ordered and resumable through a cursor, and the catalogue can be filtered and revalidated with an ETag.

Upgrading

  • GET /v1/models returns at most 50 models per request — follow next_cursor until it is null. A client that reads only the first response now sees a truncated catalogue. version and models keep their names and shapes; total (always null) and next_cursor are added beside them.
  • GET /v1/workspaces/{workspace_id}/fleet-libraries is a page too — ordered created_at DESC, then platform entries before tenant entries, then id. Follow next_cursor.
  • The gallery summary no longer carries support_files — the manifest is not served on the workspace plane; the admin catalog (GET /v1/admin/fleet-libraries/{id}) remains its only reader. Nothing else moved off the card.
  • Upgrade the agentsfleet CLI together with the server — agentsfleet install --library <id> resolves an id against the gallery, and a pre-upgrade CLI reads only the first page, so it reports a valid entry past that page as missing.
  • Bundle imports and catalog edits now enforce requirement ceilings — at most 32 required credentials, 64 required tools, and 64 network hosts, with names up to 200 bytes and hosts up to 253. An import past a ceiling returns 413; a catalog edit whose install-gate copy is over 32 entries, or longer than 500 bytes for one credential, returns 400. Stored entries are not re-validated, so nothing already saved becomes unreadable.

What’s new

  • Filter the catalogue — ?q= matches a case- and accent-insensitive substring across a model’s id and provider, ?provider= matches a provider exactly. % and _ match literally.
  • Conditional catalogue reads — every answer carries an ETag, Cache-Control: private, no-cache, and Vary: Authorization. Send the tag back as If-None-Match and an unchanged catalogue answers 304 with no body.

API reference

  • GET /v1/models?limit=&starting_after=&q=&provider= → {version, models, total, next_cursor}, plus ETag / Cache-Control / Vary on both 200 and 304.
  • New error codes: UZ-LIBRARY-001 (cursor this endpoint never issued), UZ-LIBRARY-002 (cursor for different filters or page size), UZ-LIBRARY-003 (limit outside 1..100, or q over 128 bytes), UZ-LIBRARY-004 (catalogue revision unreadable), UZ-LIBRARY-005 (a compliant response would exceed its body ceiling), UZ-LIBRARY-006 (pool or query failure), UZ-LIBRARY-008 (a secret-reference write lost a race to a concurrent delete).
  • A stale-but-valid cursor is not an error: it resumes from its boundary and may return an empty page with next_cursor: null.

Bug fixes

  • The dashboard Models page lists every model entry again — the page sent no limit and ignored next_cursor, so a tenant with more entries than one page silently saw only the first.

CLI

  • agentsfleet install --library <id> reads the whole gallery — it follows next_cursor until the id is found, so an entry past the first page installs instead of reporting itself absent.
What's newBug fixesPerformance

Adding runners no longer slows the control plane

An idle runner’s poll now costs one bounded Redis read and no database round-trip. Idle cost tracks how many runners you run, never how many fleets exist, so adding execution capacity stops making the API slower for everyone.

What’s new

  • Idle polls skip the database entirely — a poll consults a readiness index that ingress writes when a fleet actually receives work, so a fleet holding nothing is never examined. Previously every poll walked every active fleet on the platform.
  • Scheduled fires enter the fast path too — the cron producer records readiness when it appends, so a scheduled run is picked up on the next poll instead of waiting for the background sweep to reach its fleet.
  • Per-poll work is capped — the candidate scan is bounded by MAX_READY_CANDIDATES_PER_POLL, alongside NO_WORK_RETRY_AFTER_MS so both knobs are visible together.
  • Runner authentication is memoized for one heartbeat interval — repeat calls from the same runner no longer re-read fleet.runners. A cordon, drain, revoke, or delete drops the entry on the machine that served it, and any other machine’s entry expires within one heartbeat.
  • New /metrics families for poll cost and readiness depth — candidate-scan depth and per-poll database round-trips, unlabelled and rendered without touching a datastore.

Bug fixes

  • A message posted to a non-canonical fleet id is now rejected — an uppercase, brace-wrapped, or dash-free fleet_id on POST /v1/workspaces/{workspace_id}/fleets/{fleet_id}/messages matched the fleet and returned 202, then queued the event onto a stream nothing reads, so the message was never delivered.
  • Stopping, deleting, or auto-pausing a fleet clears its readiness entry — a fleet that left active kept an unreachable entry that held a slot of the bounded sample permanently; the approval gate’s automatic pause now clears it the same way the stop and delete paths do. A stop keeps the fleet’s stream, so a resume still finds its undelivered event.
  • A stranded event no longer waits on unrelated traffic — the background sweep re-derives readiness from the streams themselves, including entries appended but never delivered to any consumer, which the previous reclaim path could not see.
BreakingWhat's newObservability

Metrics move to one namespace and standard names

Exported metrics now use standard OpenTelemetry names and correct units on a single agentsfleet_ prefix. Old names are not emitted alongside the new ones, so stored queries need repointing.

Upgrading

  • /metrics families that started fleet_ now start agentsfleet_ — fleet_triggered_total becomes agentsfleet_fleet_triggered_total.
  • Four metrics renamed — agentsfleet.credit.drained_nanos → agentsfleet.billing.credit.consumed ({nanocredit}); agentsfleet.run.duration_ms → gen_ai.invoke_agent.duration (seconds); agentsfleet.tokens.processed → agentsfleet.invoke_agent.token.usage ({token}, split by gen_ai.token.type), with cached input separate as agentsfleet.invoke_agent.cache_read.token.usage — a subset of input, not a third total.
  • Workspace and tenant identity no longer appear on metrics — per-workspace cost stays a Postgres query against the execution-telemetry rows.

What’s new

  • Bounded model attribution — gen_ai.provider.name and gen_ai.request.model are attached while a series budget allows; past it the attribute is dropped, never truncated, and agentsfleet_otel_attribute_omitted_total counts the omission.
  • Standard HTTP span keys — http.request.method, http.route, and numeric http.response.status_code. The route is the matched template, so the concrete path, query string, and Authorization header stay out.

Bug fixes

  • Successful lease renewals now reach the credit metric — renewals committed money but emitted no sample, understating consumption for any run that renewed.
What's newBug fixesAPI

Idle load tracks your work, not your history

Database work now scales with current activity instead of total stored history.

What’s new

  • Idle database load stays flat as accounts grow — stored runners and events no longer make background work more expensive over time.
  • Long lists stay responsive — runner, credential, and API key lists limit their work to the requested page.

Bug fixes

  • A backslash in an actor filter no longer returns 500 — an actor value containing \ now matches the literal backslash and returns a normal page.
What's newObservabilityPerformance

A hung peer cannot hold a call open

Outbound network calls now have enforced deadlines.

What’s new

  • Telemetry export finishes predictably — each request obeys a deadline, so a stalled endpoint cannot hold delivery open indefinitely.
  • Exporter health is on /metrics — queue depth and dropped-telemetry totals make an exporter outage visible.
  • High-volume traces stay bounded — routine success traffic is limited; runner rejections, server errors, and drops stay visible in metrics.
BreakingBug fixesAPICLI

An identifier has exactly one spelling

Every workspace, fleet, and event id is a lowercase Universally Unique Identifier version 7 (UUIDv7). The API and the agentsfleet CLI now reject a non-lowercase id, which used to be accepted and could then drift from its lowercase twin across caches and deduplication keys.

Upgrading

  • Lowercase any identifier your code stores or builds itself. Ids returned by the API and CLI are already lowercase; act only where your code uppercases an id or hard-codes one in a fixture or webhook URL. Those requests fail with UZ-UUIDV7-009, or UZ-REQ-001 on inline-validating routes.

API reference

  • Uppercase and mixed-case ids return 400 — UZ-UUIDV7-009, or UZ-REQ-001 on inline-validating routes. Handle both.
  • The CLI rejects the same shape — checked before the request leaves your machine.

Bug fixes

  • A clock outside the representable range fails loudly — minting an id on a host set before 1970 returns an error instead of an id that sorts wrong.
What's newBug fixesAPIUI

A struggling fleet explains itself

A failed run now names its cause instead of repeating “Failed a startup safety check”, and identical failures collapse into one row.

What’s new

  • Failures name their cause — Failed a startup safety check — no instructions configured appears in Chat, the events table, and the live completion frame. A run from an older runner that records no cause still shows the plain-language sentence, never a guess.
  • A repeating failure is one banner — what failed, why, how many times, when it last happened, and what to do. It clears when the fleet recovers.
  • Repeats collapse — consecutive identical deliveries fold into one expandable row with a ×N count and time range.
  • Compact integration events — one line with the full payload one click away; a recognized change proposal links to its source.
  • Guidance where you can act on it — a startup failure links to the fleet’s Skill tab.
  • One pagination control everywhere — every console table pages the same way, and a page is shareable by link.

API reference

  • core.fleet_events gains failure_detail — an additive nullable text column carrying the human-readable cause. The events endpoint returns it beside failure_label; both are null on a clean run.

Bug fixes

  • Zero metrics on a failed run dim — 0 tokens, $0.00 cost, and 0ms no longer read as a real measurement.
  • A long-open Chat tab stays bounded — the live view keeps a recent window; full history stays in Events.
What's newUI

Tables stay sortable and contained

What’s new

  • Sortable columns — click a supported heading to cycle ascending, descending, original order.
  • Contained event history — long lists scroll below a sticky heading.
  • Consistent pagination — client, cursor, and page-backed lists share one control and keep recovery navigation on an empty later page.
  • Exact dashboard frame — the header aligns edge to edge and page content owns viewport scrolling.
What's newBug fixesAPIUI

Fleet detail is now a focused workspace

Fleet detail opens on Chat, and every other panel moves into fleet-local navigation. The summary keeps status, latest outcome, tokens, spend, duration, and waiting approvals visible.

What’s new

  • The composer is always on screen — only the conversation scrolls.
  • Every message says who sent it — a sender chip, a readable name, and the time. Yours read Operator; the fleet’s read its own name.
  • Events describe themselves — an incoming event shows what arrived, such as opened · owner/repo#539 — add retry with backoff, with its payload one click away.
  • Failures read as sentences — in Chat, the summary, and the events table, instead of the runner’s internal tag.
  • Fleet-local views — Events is fleet-scoped; Memory, Skill, Trigger, and Settings each get one destination.
  • Approvals stay in context — a waiting count opens workspace Approvals filtered to the current fleet.
  • Trigger drops setup clutter — source, filters, schedule, and latest delivery only.

Bug fixes

  • Messages send while the live feed is down — a refused message shows as failed with a retry instead of sitting queued.
  • Your own messages survive a reload — they rendered as empty rows because Chat used the fleet’s reply field for every entry.
  • Incoming events no longer render as blank rows.
  • A lost connection recovers on its own — Chat reconnects when the tab returns to the foreground or the network comes back.
  • The API accepts a real request’s headers — the 4 KiB request-header limit answered 431 on any authenticated request crossing a proxy. The limit is now 16 KiB.
What's newBug fixesUI

The console puts steering first

The fleet console opens on the steer thread instead of the source editor, and the wall drops its abbreviations.

What’s new

  • Steer-first console — the SKILL.md/TRIGGER.md source card is collapsed to its header until you open it; Edit keeps it open while a draft exists.
  • A way back to the wall — the console header gains a ← Fleets link.
  • Plain-words wall tiles — footers read $1.20 spent · 7 events; a stale feed reads not live.
  • Events in the standard table — Time, Status, Fleet, Actor, Type, Summary, Tokens, and Duration, with cursor paging.
  • The wall search box is gone — it only filtered fleets already loaded in the page.

Bug fixes

  • Console columns no longer overlap — long unbroken lines scroll inside their own block.
  • The run-metrics strip says Time — Wall collided with the Live Wall page name.
What's newAPIUI

Delete revoked runners, and a seeded model catalogue

What’s new

  • Delete a revoked runner — revoke is the destructive step, delete retires the record. The trash action appears only on revoked runners.
  • A pre-seeded model catalogue — Anthropic, OpenAI, Gemini, DeepSeek, Kimi, GLM, MiniMax, Qwen, xAI, Groq, and Mistral direct, plus the same models via Pioneer, Fireworks, Together, Novita, and OpenRouter. Each host carries its own pricing.
  • Custom endpoints learn their context window — an OpenAI-compatible endpoint naming a known model enforces that model’s real context cap.
  • Loading screens speak — route loaders rotate wording; screen readers hear the plain wording.

API reference

New endpoint: DELETE /v1/fleets/runners/{id} (scope runner:write). Deletion cascades the runner’s lease and event history.

Bug fixes

  • No fake platform default — a fresh tenant sees “No default is configured” instead of a green Active badge.
  • Self-managed models say “Billed by provider” instead of “Rates unavailable”.
  • The platform-default row is hidden for tenants running their own model with no default configured.
Bug fixesUI

No missing-page flash, and a Platform sidebar group

Bug fixes

  • No not-found flash on the workspace home — the dashboard entry, workspace switcher, and workspace creation open the fleet wall directly.
  • The refetch dialog reads as a refetch — Fetch update opens its own dialog with the repository locked to the entry’s source. Repointing a source stays under Edit.
  • A finished onboarding step shows a check.

What’s new

  • A Platform sidebar group — Runners, Model library, and Fleet library move out of Configuration. It shows only to an operator holding the matching scope.
What's newAPIUIPerformance

Fleet detail becomes the operating console

Source editing, live steering, memory, run cost, and lifecycle controls land on one page.

What’s new

  • Source editing — read and edit SKILL.md or TRIGGER.md from the dashboard. A save takes effect on the next wake and keeps the fleet’s memory; a concurrent save reloads the other change instead of overwriting it.
  • Memory correction — inspect learned entries and forget one. Deleting the fleet still deletes all its memory; editing its source does not.
  • Run cost — each event can show tokens, wall time, and settled cost. The recent rollup covers the latest 200 events within seven days and labels that bound; lifetime spend stays separate.
  • Faster fleet lists — lifetime event and spend counters are maintained on write.

API reference

  • Fleet detail — GET /v1/workspaces/{workspace_id}/fleets/{fleet_id} returns source, triggers, lifecycle state, lifetime counters, and an ETag header.
  • Event cost — fleet event rows include nullable cost_nanos, sourced from settled billing telemetry.
  • Memory forget — DELETE /v1/workspaces/{workspace_id}/fleets/{fleet_id}/memories/{key} requires fleet:write, returns 204 when deleted and UZ-MEM-004 when the key is absent.
  • Stale-write protection — fleet source and platform library PATCH may send If-Match. A stale fleet edit returns UZ-AGT-014, a stale catalog edit UZ-CATALOG-005; both are 412 and include the current ETag.
What's newAPIUI

The fleet catalog stops overstating itself

A catalog entry’s status badge now matches what a workspace will actually get, and an operator can correct an entry without deleting it.

What’s new

  • Draft-first onboarding — creating or refetching a platform entry returns it to draft. Publish after review.
  • Edit a catalog entry’s identity — name, repository, and ref are editable. Changing the repository or ref discards the stored bundle and returns the entry to draft; workspaces already running the fleet keep the bundle they installed. Fetch update fetches at the ref the entry names, not the default branch.
  • An honest status column — an entry published with no bundle shows as Broken, not Published.
  • Consistent naming — Create fleet library replaces “Add fleet”; the page reads Fleet library.
  • Copy buttons where a value wants copying — bundle hashes (the full hash), identifiers, secret names, cron schedules, webhook URLs, and one-time credentials. A failed write is reported on the button instead of showing “Copied” and costing you a one-time token.
  • The dashboard counts every fleet — installing and killed fleets appear in the summary tiles.
  • The live thread shows tool calls and cost — each response lists the tools it called with durations.

API reference

PATCH /v1/admin/fleet-libraries/{id} additionally accepts name, source_repo, and source_ref. A request whose source_repo or source_ref differs from the stored value sets content_hash to null and visibility to draft atomically; re-sending the stored value is a no-op. Changing the source and setting published: true in one call is refused with UZ-CATALOG-002. Malformed sources are refused with UZ-BUNDLE-001. The entry identifier is never patchable.
What's newAPI

Platform operators onboard fleets from the dashboard

Enter a GitHub repository as owner/repo under Fleet libraries, and agentsfleet fetches it, validates the bundle, and adds it to the catalog. Requires the platform-library:write scope.

What’s new

  • Onboarding without a hand-built API request — onboarding the same repository twice updates the existing entry rather than creating a second one.
  • Four prebuilt fleets — github-pr-reviewer, platform-ops, zoho-sprint-daily-summarizer, and zoho-recruiter-daily-summarizer. Each is a public repository under agentsfleet.
  • A fleet’s library identifier comes from its bundle — read from the name in SKILL.md, not the repository path you type. Keep the repository name, the SKILL.md name, and the TRIGGER.md name identical.

API reference

  • security-reviewer removed from the prebuilt catalog — it named a repository that was never published.
BreakingBug fixesAPICLI

Removed CLI commands stop appearing in hints

Upgrading

  • agentsfleet workspace delete JSON output field renamed — {"deleted": "<id>"} is now {"removed_from_local_state": "<id>"}. Update any script parsing it. The workspace and its remote data are unaffected by this command.

Bug fixes

  • Stale command hints removed — error text no longer points at agentsfleet install --from, agentsfleet secret add, agentsfleet workspace add, or agentsfleet tenant provider add. agentsfleet list with no fleets points to agentsfleet library and agentsfleet install --library <library_id>; vault hints say agentsfleet secret create <NAME>.
  • Clean --help output — agentsfleet --help no longer prints a telemetry-shutdown timeout after valid help output.
  • GitHub Pull Request events — the signed per-fleet webhook accepts the full set of supported Pull Request actions.
What's newAPICLIIntegrations

GitHub App events route to repository-bound fleets

A connected GitHub App routes Pull Request and failed workflow-run events to active fleets that subscribe to the repository and event and hold an approved GitHub grant.

What’s new

  • Repository-bound triggers — add repositories: [owner/repository] beside source: github and events in TRIGGER.md; omission receives no managed App traffic.
  • Scoped fan-out — one delivery may wake several matching fleets in its workspace; wrong-repository, wrong-event, inactive, and unapproved fleets receive nothing.
  • Verified installation ownership — the callback verifies access to the claimed installation and refuses to move one already connected to another workspace.
  • Replay recovery — replay protection uses the signed payload body per fleet, so changing the unsigned delivery header cannot bypass it.

API reference

  • POST /v1/ingress/github verifies the platform GitHub App signature, acknowledges signed ping checks, maps installation.id to a workspace, and normalizes supported events. Returns 202 after routing and 404 with UZ-WH-022 when no fleet subscription matches.
  • pull_request events are accepted. workflow_run is accepted only for completed failed runs.

CLI

  • agentsfleet connector list [--workspace <id>] [--json] — reports connected, not_connected, reconnect_required, or unconfigured per provider.
  • agentsfleet connector status <provider> [--workspace <id>] [--json] — one provider’s state, without treating a valid disconnected state as an error.
BreakingAPISecurity

The model library is now an authenticated read

Per-model pricing is no longer world-readable.

Upgrading

  • GET /_um/<key>/cap.json is retired — the path returns 404, no alias. Switch to GET /v1/models with a Bearer token. The models[] rows are unchanged: id, provider, context_cap_tokens, input_nanos_per_mtok, cached_input_nanos_per_mtok, output_nanos_per_mtok. The ?model= filter is not ported — filter client-side. Dashboard and CLI users need do nothing.
  • The global rates/billing block is gone with the document — GET /v1/models carries the catalogue only. Current rates stay at agentsfleet.net/#pricing.

API reference

  • GET /v1/models — Bearer-authed, any tenant, no capability scope. Returns { "version": "YYYY-MM-DD", "models": [...] }; an empty catalogue is 200 with models: []; a missing or invalid token is 401.
  • UZ-PROVIDER-004 — the “Model not in library” guidance points at GET /v1/models.
Bug fixesAPIIntegrationsSecurity

Reconnecting an integration takes effect immediately, and Jira stays connected

No API shapes, endpoints, or error codes changed.

Bug fixes

  • Stale token after reconnect — the credential cache spots a changed identity, such as a different GitHub installation or a rotated personal access token, and mints a fresh token. It used to serve the cached one for up to an hour.
  • Jira hourly reconnect loop — the platform stores the rotated refresh token returned during renewal. Applies to any provider that rotates them (Atlassian’s Jira OAuth does by default); Zoho Desk is unaffected. A failed store still lets the in-flight request succeed, at the cost of at most one reconnect prompt.
APIBug fixes

Per-fleet spend ceilings are enforced

daily_dollars was required in every TRIGGER.md and documented as a hard ceiling, but nothing read it. The ceiling now holds.

What changed

  • daily_dollars and monthly_dollars now stop a fleet — a fleet that has spent its allowance is refused before its next run opens, recording gate_blocked with a budget_breach label. The refused event is not charged. A fleet crossing its ceiling mid-run stops at its next lease renewal (UZ-RUN-015) and records fleet_error with the same label.
  • budget_breach is a distinct label — previously every mid-run stop reported renewal_terminate.
  • Spend windows — daily_dollars is a rolling 24 hours, monthly_dollars the UTC calendar month; spend counts what you were charged. monthly_dollars stays optional.
  • Lowering a ceiling takes effect immediately, including on a run in flight.
  • /v1/admin/models is documented — the platform-admin catalogue routes were served without appearing in the API reference. A continuous-integration gate now fails whenever a served route is missing from the specification.

Documentation corrections

  • Continuation chains never existed. The context-lifecycle page described a continuation chain capping at 10 and an 11th attempt labelled chunk_chain_escalate_human. None of it was built: a fleet that runs out of context wraps up, the run ends processed, and nothing re-queues it.
  • actor=continuation:<original_actor> is not a value the runtime writes — removed from the activity-stream and --actor filter documentation.
  • budget_breach was documented before it existed. It exists now.
Every run is charged $0 during the free-trial window, so no budget can be reached. The per-fleet ceiling and the tenant credit gate both begin to bite when the window closes.
Bug fixesUI

Fleet timeline recovers frames lost to a connection blip

Bug fixes

  • Reconnect gap-recovery — on reconnect the timeline re-fetches events published during the outage and merges them by event id, so a frame delivered twice renders once.
  • Honest tail on failure — a failed recovery fetch changes nothing on screen; the next reconnect retries. Nothing is fabricated to fill a gap.
  • Conflicting style classes resolve last-wins — components merge classes through one Tailwind-aware implementation.
UIAPI

Admin model library: edit rates and set the default from a row

What’s new

  • Edit rates in place — the pencil on a catalogue row opens a dialog for the context cap and per-token rates; provider and model id stay fixed.
  • Make any model the default from its row — a star action sets the platform default (you enter the provider API key, stored in your vault).
  • Consistent action icons — every Create button shows a plus icon, and the fleet Install button a download icon.

API reference

  • GET /v1/admin/platform-keys — each row now includes model, the priced (provider, model_id) the default resolves to (null for a provider that has been stood down).
UIAPI

Models page becomes a registry

Four fixed slots become a sortable table: register three Anthropic models on one key, or the same model on two hosts.

What’s new

  • One row per configured model — sorted by model or provider, with the platform default pinned first.
  • Key names are identities — a key name you already stored (same provider) updates that key in place and points the new model at it, so models share one credential. A name belonging to a different provider’s key is rejected, not overwritten.
  • Custom endpoints can go keyless — the API key field is optional.
  • Per-row actions — View details (provider, endpoint, key name, created date — never the key), Edit, and Remove (blocked with a reason on the row powering inference).
  • Switch with one click — an inactive row’s Switch button activates it immediately.

API reference

  • GET /v1/tenants/me/models — lists registered entries, each joined to its key’s provider/kind/endpoint and an active flag; also returns platform_default_available.
  • POST /v1/tenants/me/models — registers {model_id, secret_ref}. secret_ref is required and names the stored provider key or keyless custom-endpoint record. 404 UZ-MODELS-002 when it names no stored key; 409 UZ-MODELS-003 on an exact duplicate.
  • PATCH /v1/tenants/me/models/{id} — changes an entry’s model_id. 404 UZ-MODELS-004 when the id isn’t yours.
  • DELETE /v1/tenants/me/models/{id} — removes an entry; idempotent. 409 UZ-MODELS-001 when it’s the active entry.
  • PUT /v1/tenants/me/provider also registers the matching model entry when none exists.
  • Activating a self-managed model takes the model from the PUT body or the registry entry, never the stored key. An activation that resolves no model returns 400 UZ-PROVIDER-004.
  • Deleting a stored key still referenced by a model entry returns 409 naming how many entries reference it.
UI

Crisper cards, clearer isolation modes, per-account avatars

What’s new

  • Sharper cards and tables — resting-state borders and card backgrounds are brighter.
  • Pinned table header on Billing usage — the “Date / Amount / Type / Description” header stays visible while scrolling.
  • Isolation mode as option cards — Settings → Runners → Create runner picks its mode from four described cards instead of a dropdown.
  • A distinct account avatar — with no uploaded photo, your avatar is a pattern generated from your account.
Bug fixesAPI

Error responses stop leaking internal names

Bug fixes

  • No more raw internal error names — an authentication-middleware failure returned a raw identifier (e.g. error.TokenExpired) in detail; it now returns a curated message, with the raw identifier logged server-side only.
  • Plainer failure messages — around three dozen internal-failure responses now return plain English; several previously-generic connector and provider failures return distinct, diagnosable codes.
  • Model-provider errors say “library” — they previously said “catalogue”.
  • UZ-AUTH-014 is now 409 Conflict — submitting a login verification code before approving the session returned 410 even though the session was still approvable.

API reference

  • The error codes reference is generated from the backend error registry on every release, so it cannot drift.
BreakingSecurityAPI

Fleets need an approved integration grant before minting provider tokens

Connecting GitHub, Zoho Desk, Jira, or Linear authorizes your workspace, not every fleet in it. Such a fleet must hold a human-approved grant, checked at lease and again on every token request. Slack is not affected — its bot token is long-lived and delivered directly.

Upgrading

  • Existing GitHub/Zoho/Jira/Linear fleets without a grant stop minting after this release. One-time fix per fleet: POST /v1/workspaces/{workspace_id}/fleets/{fleet_id}/integration-requests (body {"service": "github", "reason": "..."}), then approve it from the dashboard. Already-approved fleets are unaffected.
  • Slack fleets and static secrets you pasted yourself (${secrets.<name>.<field>}) are not gated.

What’s new

  • Grants are requestable for github, zoho, jira, and linear — the endpoint previously rejected these services.
  • A refused mint returns UZ-GRANT-001 (403, “No integration grant for service”) with the request-grant endpoint in the hint.
  • Revoke works mid-run — revoking refuses the fleet’s next token request even while its run is leased; re-approving restores minting with no reconnect.
What's newIntegrations

A guide to connecting GitHub, Slack, Zoho Desk, Jira, and Linear

What’s new

  • Connectors guide — the connect round-trip, the GitHub App-install exception, and which providers issue a refresh token.
  • Secrets page links to the guide and calls out the difference between a pasted vendor key (${secrets.<name>.<field>}) and a connector.
UIBug fixes

Runners, Model library, and Secrets adopt the standard table

What’s new

  • Runners page — renders in the same table as API keys and secrets. Enrolling is the “Create runner” dialog, which shows the install token once with a notice that it won’t be shown again.
  • Runner labels — “Host id” is now “Host name”; “Sandbox tier” is now “Isolation mode” with plain options: Linux · Landlock, Nested container, macOS · Seatbelt, and None.
  • Model library — “Model rates” is renamed “Model library” across the nav, page, and dialog.
  • Secrets — “Secrets & ENVs” is now just “Secrets”. Renaming a secret is its own dialog, opened from the Name column.

Bug fixes

  • Model library token counts — large counts rendered inconsistently between server and client (a locale mismatch).
Bug fixesUIAPI

API Keys stands alone, Buy credits works, library copy drops “template”

What’s new

  • API Keys page — the sidebar’s “Workspace” entry is now “API Keys”, a standalone page; /settings redirects here. Switching and creating a workspace stay in the top-right switcher.
  • Buy credits — renamed from “Purchase credits” and now a live mailto: link instead of a disabled button.
  • “Create fleet library” replaces “Add library entry” across the dashboard and CLI empty states.
  • Left-nav active indicator — the selected item shows a left accent bar.
  • Scopes reference — a new page listing every scope a tenant token can carry, linked from UZ-AUTH-022.

Bug fixes

  • Models page — the active-model row no longer glows permanently (a [data-live] selector matched the “not live” state too); its redundant “Switch to platform defaults” button is removed.
  • UZ-PROVIDER-009 — switching back to platform defaults when none is configured returns plain English instead of leaking “operator action required”.
What's newBreakingUICLIAPI

Fleet library replaces Templates, Secrets gets its own page

Upgrading

  • agentsfleet templates → agentsfleet library; --template → --library. No alias — update any script or continuous-integration job calling the old command or flag.
  • agentsfleet credential → agentsfleet secret. Same for credential add/list/show/delete.
  • Routes renamed, no deprecation window: /v1/admin/fleet-templates → /v1/admin/fleet-libraries; /v1/workspaces/{workspace_id}/fleet-templates → .../fleet-libraries; /v1/workspaces/{workspace_id}/credentials → .../secrets. Wire fields platform_template_id/tenant_template_id → platform_library_id/tenant_library_id.

What’s new

  • Fleet library — “Add library entry” replaces “Create a template”; the ?library= deep-link query param replaces ?template=.
  • Secrets & ENVs — vault secrets get their own sidebar entry and page.
  • Models page collapses to one list — the platform-default model renders as a normal row. The “Other provider” form’s Provider field is a dropdown of real catalogue models instead of free text.

Bug fixes

  • Friendlier errors — every error response can carry an optional plain-English user_message field (RFC 7807 problem+json body). It is written for ~15 codes: UZ-PROVIDER-001..004, UZ-VAULT-001..003, UZ-BUNDLE-001..002, and UZ-APPROVAL-001..006. A code without one omits the field rather than nulling it, so untouched responses are byte-identical.
Bug fixesUI

Readable account settings, a copyable workspace ID, consistent labels

What’s new

  • Copy the workspace ID — the Workspace page lists the workspace name and ID as labelled rows with copy buttons. The ID is the value the agentsfleet CLI and the API expect.
  • One label size — section headers, table columns, and sidebar groups share a single type size.
  • Clearer first-run screens — Dashboard, Fleets, and Models empty states lead with Learn more plus one primary action.

Bug fixes

  • Account settings dialog — headings, your email address, and the close button rendered dark-on-dark on the dark theme.
What's newAPIUI

Connect Grafana, Zoho Desk, Jira, Linear, Fly, and Datadog

Six connectors join GitHub and Slack. Each connect lands a vaulted credential the platform mints short-lived tokens from, and every vendor call runs under an enforced deadline.

What’s new

  • OAuth connectors — Zoho Desk, Jira, and Linear connect with a browser OAuth round-trip and store a refresh token. Jira resolves its Atlassian cloud instance during connect.
  • API-key connectors — Datadog, Grafana, and Fly take an operator-supplied key, checked by a live validation probe before it is vaulted; a rejected key returns UZ-CONN-005 and nothing is stored.
  • Connector catalog — GET /v1/connectors lists every provider with its archetype and whether it is configured and connected.

Bug fixes

  • Bounded broker calls — token exchanges are deadline-armed and fail closed.
Bug fixesCLI

Reliability fixes for logs, credential help, and approval enforcement

  • agentsfleet fleet logs — a malformed event timestamp no longer throws RangeError and aborts the command; that row renders — and the stream continues.
  • agentsfleet workspace credentials — the redirect points at the real agentsfleet credential command group instead of a non-existent agentsfleet agent credential.
  • Approval enforcement — an approved or denied grant is honored by the fleet’s lease path even when the Redis mirror of that decision fails to write; the durable database row is the fallback.
Bug fixesAPI

Error reference matches the server

  • UZ-PROVIDER-003 hint — now states that provider and model are required and api_key is required only for a named provider (optional for an openai-compatible endpoint). The old wording had clients sending an unnecessary api_key.
  • Error-code reference — removed the UZ-AUTH-009 and UZ-AUTH-010 rows, which have no producer in the server registry (both superseded by UZ-AUTH-022). The retired UZ-AUTH-021 stays as a struck-through historical entry.
What's newUI

Add a workspace template from the dashboard

The Fleets install gallery gains Add template: enter owner/repo, the dashboard validates the required SKILL.md, adds the workspace template, and returns you to the gallery.

What’s new

  • Dashboard onboarding — Add template calls the workspace template onboarding API and refreshes the gallery.
  • Empty states — Fleets and Events use simple “No … found” copy with direct links.
  • Models navigation — the nav labels the page Models, and Bring your own key uses the same primary button style as Install fleet.
  • Route motion — route changes no longer wobble.
What's newAPI

Operator dashboard access follows the same scopes as the API

The runner fleet and model catalogue are gated on explicit resource:action scopes, the same ones the API enforces. The separate platform-admin flag is gone.
  • Error code — a request missing an operator capability returns UZ-AUTH-022 (Insufficient scope), whose detail names the required scope; the former UZ-AUTH-021 is retired.
  • Scope hierarchy — a held higher scope satisfies a lower one on the dashboard exactly as at the API (model:admin covers model:read, runner:write covers runner:read).
  • Marketing-site analytics recovers from a transient load failure instead of staying dark for the rest of the visit.
What's newAPIUIIntegrations

Slack: mention @agentsfleet in a channel, get an answer in the thread

Connect Slack from the dashboard Integrations page, invite @agentsfleet to a channel, and mention it. The first mention creates a fleet resident in that channel. It is reactive and read-only: it speaks only when mentioned, and only in the thread that asked.

What’s new

  • Connect Slack from Integrations — the callback stores the bot token in the workspace vault. The row shows “Slack connected: {team}” and offers Reconnect if the install is revoked.
  • One fleet per channel — later mentions in any thread reuse it. Memory is channel-scoped.
  • Answers land in-thread — never at channel top level, never in another channel.
  • Recent thread context — each mention re-reads up to 20 recent messages in its thread. Best-effort: if Slack throttles the read, the mention still processes.

API reference

  • POST /v1/workspaces/{workspace_id}/connectors/slack/connect (scope connector:write) — returns the Slack authorize URL carrying a signed single-use state; GET /v1/connectors/slack/callback finishes the round-trip and vaults the bot token.
  • GET /v1/workspaces/{workspace_id}/connectors/slack (scope connector:read) — status as {status, team}, where status is connected, reconnect_required, or not_connected; never a secret.
  • POST /v1/connectors/slack/events — the signed events ingress, authenticated by Slack’s request signature (a hash-based message authentication code over the raw body, 5-minute replay window), not a Bearer token.
  • New error codes: UZ-SLK-010 (401, invalid signature), UZ-SLK-011 (401, stale timestamp), UZ-SLK-020 (200, event from a team with no install is acknowledged and ignored), UZ-SLK-022 (502, OAuth token exchange failed), UZ-SLK-030 (502, answer delivery failed — logged and retried; the run never fails). UZ-CONN-001/UZ-CONN-002 now cover Slack as well as GitHub.
What's newAPIIntegrations

Connector platform: one uniform API, bounded vendor calls

Connecting a third-party service runs through one documented route set instead of a per-provider one. The URLs you already use are unchanged.

API reference

  • POST /v1/workspaces/{workspace_id}/connectors/{provider}/connect and GET /v1/workspaces/{workspace_id}/connectors/{provider} — start a connect and read status for any registered provider (slack, github), Bearer-authed with connector:write / connector:read.
  • GET /v1/connectors/{provider}/callback — the vendor redirect target; Bearer-less, authenticated by the signed single-use state minted at connect.
  • UZ-CONN-003 (502) — an outbound vendor call hit its enforced deadline, could not be time-bounded, or the vendor was unreachable. Transient — retry.
  • UZ-CONN-004 (404) — the {provider} matches no provider on this deployment; the body names it.
Archive

Earlier entries

Entries before July 1, 2026 covered the pre-release period when the API, the CLI, and the data model were still changing shape weekly. They described behaviour that later entries have superseded, so they are no longer a useful guide to the product and have been removed from this page.The full record is in version control: changelog.mdx history.