CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.3.5] - 2026-11-02
### Added
- Models: OpenAI's `gpt-6.1-terra` ($1 / $5 per million tokens, reasoning can
be switched off) and `gpt-6.1-sol` ($2 / $10, always reasons).
- Fast mode. An agent can run the fast variant of a model whose provider offers
one: Claude Opus 5.5 on Anthropic, and OpenAI GPT-5.6, GPT-6 and GPT-6.1. The
model select and the model catalog list each such model a second time as
"· Fast", with its prices; the provider bills those calls at 2× every rate.
Choosing the variant sets the agent's `fastMode`, which
`PATCH /api/instances/:slug` accepts and export and import carry (migration
`0097`). `GET /api/instances/models` returns the price multiple as
`fastMultiplier`. A call is billed at the fast rate only when the provider
reports that it ran fast.
- Text written alongside tools. A model can write text in a step that also
calls a tool; until now that text was dropped on WhatsApp and HTTP, shown on
a live chat and spoken on voice, and the model was never told. Each agent can
now choose, in Settings, to discard it (only the text after the last tool
call is the reply, on every channel), show it as progress (shown on a live
chat while the agent works, not kept in the reply) or make it part of the
reply (every step's text, in order). A set policy adds a "Reply delivery"
section to the prompt; unset keeps the previous behaviour and prompt. The
agent's `intermediateText` (`discard`, `progress`, `reply` or `null`) is
accepted by `PATCH /api/instances/:slug` and carried by export and import
(migration `0098`).
- The analytics dashboard compares every KPI with the previous period (the
previous figure when the percentage would be meaningless), draws each KPI's
daily shape, overlays the previous period's cost, and shows when people
write as a weekday × hour heatmap. Tool usage and tool latency become one
Tools table with calls, success rate and p95. The API gains
`overview.previousPeriod`, `dailyTrend[].promptTokens` and
`cachedInputTokens`, `previousDailyCost`, `latency.previousOverview` and
`latency.toolTimings`.
- The generated description under a conversation's title stops at two lines,
with Show more / Hide when it is longer.
### Changed
- The OpenAI `standard` tier resolves to `gpt-6.1-terra` instead of
`gpt-6-sol`, at half the price. Agents with no model of their own, and the
`spawnTask` sub-agents of those agents, change model.
- On WhatsApp and Telegram, a message fragment that arrives after the running
turn has started a tool no longer cancels it: the turn finishes and replies,
and the fragment gets a turn of its own. A turn that has not called a tool
yet is still cancelled and restarted on the joined text. A tool that a
cancelled turn asks for does not run and returns `{ error, notRun: true }`.
- The response time KPI is the pipeline median (p50), as in the response time
chart, which drops p99. The model, tier and channel donuts become ranked
bars, and the tier breakdown moves into the model card.
- `GET` analytics returns `hourlyDistribution` as 168 rows, one per weekday
and hour, each with `weekday` (0 = Monday), instead of 24.
- Webhook-started turns and rooms run with the agent's fast mode, reasoning
level and temperature, which they ignored.
- The engine moves to Zod 4.6.6 and `@polyant-ai/plugin-sdk` 1.12.0, which
accepts plugins written with Zod 3 or 4. The JSON Schema of the core tools is
unchanged. The text of validation errors in `400` responses follows Zod 4.
- New Polyant logo in the panel and the README.
- Dependency updates: `ai` 7.0.156 and `@ai-sdk/openai` 4.0.104 (the engine
now requires them), Next.js 16.4.3, langsmith 0.11.7, the AWS SDK, dotenv,
tsx, drizzle-kit and Playwright.
### Fixed
- A tool run by a WhatsApp or Telegram turn that a new fragment then cancelled
was missing from the history of the restarted turn, which could run it again.
- `@ai-sdk/openai` before 4.0.104 dropped `reasoning_effort: "none"` on
`gpt-6.1-terra`, so it kept reasoning with thinking off.
- KPI values and trends stay inside their tile at every width.
- The analytics charts no longer cut their donuts or axis labels, and a daily
series fills the days without activity.
### Security
- Zod 4.6.6 fixes GHSA-mrhx-c7xc-m3w5: an array schema without a length bound
collected one issue per failing element, so a large request could exhaust
memory.
## [1.3.4] - 2026-10-26
### Added
- The Monitor: one timeline per workspace of tool calls, hook executions,
webhook-started conversations and scheduled task runs, with live updates,
filters by group, type, agent, outcome, period and text, and a detail drawer.
It replaces the Activity page and the tool-call list, whose addresses now open
it, and the agent's Logs tab, now called Monitor, shows the same view for that
agent in place of its webhook and scheduled-run lists. The engine serves it at
`GET /api/monitor` and `GET /api/monitor/:type/:id`; each type is read under
the permission that guards it elsewhere and left out of the list when the
caller lacks it.
- A knowledge document can be switched off. It stays stored and listed, and
vector and full-text search, and a plugin's knowledge read, skip it until it
is switched on again. Each
document row has a switch, and `PATCH /api/instances/:slug/knowledge/:docId`
takes `{ "enabled": boolean }`. Export and import carry the flag
(migration `0096`).
- A skill's name and description can be edited from its page and form.
`PUT /api/skills/:name` accepts `displayName`, which cannot be blank; the
slug stays fixed and agents keep reading the skill by it.
- The Web/API channel tab shows the endpoint to call, the header for the
current authentication setting, and server-side curl and fetch examples that
read the key from `AGENT_API_KEY`. It warns when authentication is off and
when the address is only the localhost fallback
(`GET /api/instances/:slug/channels/web/endpoint`).
- The conversation list shows the contact, can be filtered by it
(`userIdentifier`), and its search box matches it.
- A tool can ask a model through the engine: its context carries
`ctx.ai.chat({ system, messages, tier })` and `ctx.abortSignal`. The call runs
through the AI gateway on the agent's provider as a service call of the turn,
logged in the AI log, traced in LangSmith with the turn's settings and priced
like the turn's own calls; the tier picks the model. Both fields are optional in `@polyant-ai/plugin-sdk` 1.11.0, so a
plugin that also supports older engines keeps a fallback, and `ctx.apiKeys`
is unchanged.
- A missing-permission 403 carries `code: "missing_permission"` and the
`permission` it needed, next to the existing message.
### Changed
- Every list page has one shape: title with the primary action top right,
search above the list, a table sorted by the visible name with the secondary
line under it, an «Active» switch for on/off state, and a right-hand drawer
that holds the create and edit forms and delete. Badges, empty, loading and
no-results states are the same everywhere.
- The agent's sidebar has six groups (Overview, Configuration, Knowledge,
Resources, Automations, Governance) with every section on its own row,
automations included; `?tab=` values are unchanged, so existing links keep
working. Analytics is called Dashboard, the Polyant mark at the top of the
sidebar leads home, and skill lists show the description under the name.
- The message Debug sheet is now "Message details", in tabs: Model, Prompt,
Tools, Messages, Steps and Context. Prompt, Messages and Context each have a
Copy action; the conversation state and the messages render as a JSON tree
whose values copy on click. The Model tab adds the turn's channel, streaming
and, on a voice turn, its transcription, plus a table of the turn's model
calls with time, type, model, duration, tokens, cost and outcome. The message
debug endpoint returns `turn` and `modelCalls` for it.
- The analytics model distribution groups the calls the platform makes on its
own (titles, summaries, checks) under one labelled slice.
- The agent header puts icon, name, slug and status on one line, the agent
sidebar names the agent, and a new agent opens on its prompts. The user menu
moves from the sidebar footer to the header. Every side drawer has the same
width, and a knowledge document opens from its row in one.
- The first-steps card lists every step, derived from what already exists, and
ticks the finished ones. The panel no longer offers a page tour on its own;
the introduction stays in the Help menu.
- Italian copy reviewed across the panel; costs follow the panel language, and
agent status and default prompt sections are translated.
- At `LOG_LEVEL=debug`, warn and error lines print the stack trace, sanitized.
At every level an error line names up to three causes with their codes, and
security-relevant refusals (webhook signature and secret failures, SSRF
refusals, a channel switched off automatically, dropped audit entries) are
logged at error level. 401 and 403 responses stay at warn.
- Tool input is validated with ajv 8, with the string formats and without
strict mode, so plugin schemas compile as they did with ajv 6.
- Dependencies: Next.js 16.4.2, React 19.3.1, AI SDK 7.0.153, NestJS 12.2.0,
Express 5.3.0 in the engine, Radix UI 1.8.1 and Vitest 5.0.5. For their
security fixes: body-parser 2.4.0, multer 2.5.0, undici 7.30.1 and 8.12.3,
follow-redirects 1.16.2, markdown-it 15.0.3 and Vite 8.3.5.
### Fixed
- A row the database refused blocked the pipeline trace and AI call log: the
batch was put back and failed again on every flush. The refused row is now
set aside and the rest is written; on a connection or timeout error the rows
stay buffered.
- An AI call log row took the time its batch was written, so a call could be
placed in the following turn. It now takes the time of the call.
- A conversation title could keep a speaker label or wrapping quotes.
- A mistyped workspace in the address showed the user as belonging to no
organization; it now shows the 404 page, localized like any address that
matches no route.
- Picking a page right after a list opened sent it back to page 1.
- A failed read of the API keys or members list showed an empty table; it now
says so and offers a retry.
- The guided setup marked the chat step done before the agent had answered.
- The Playground announced the new conversation of the user's own chat.
- Dialog and sheet close buttons were not translated, and the Italian flag was
drawn wrong.
### Security
- A failed database query no longer writes its parameters to the error log;
they could carry message bodies and tool output.
- MCP server secrets of 8 characters or fewer are now fully masked in API
responses instead of showing their last four characters, as channel secrets
already were.
## [1.3.3] - 2026-10-16
There is no 1.3.2 of Polyant.
### Added
- The agent's Embedder panel lets you choose the embedding dimension among the
ones the embedder supports. `PATCH /api/instances/:slug` accepts
`embeddingDim`; changing it deletes memories and knowledge like an embedder
switch, and requires `confirmWipe` when there is data to lose.
### Changed
- A 4xx `HttpException` is logged as a warning, a 5xx as an error.
- The tool drawer is wider, so parameters and secrets fit.
### Fixed
- Uploading an agent icon failed under the default CSP, which blocks `blob:`
images. The file is now decoded without loading a URL, and a decode failure
shows an error.
- The channel manager left the message coordinator running on shutdown.
- Expired active webhook triggers were never swept.
- The Telegram adapter kept the body reader locked after a capped read.
- Speech-to-text requests left their abort listener on the caller's signal.
- Outbound HTTP did not close the per-request agent after the response.
- The AI call log dropped the newest entries instead of the oldest when its
buffer overflowed.
- Copying a webhook URL or an auth secret showed a success toast when the
clipboard refused the write.
- The knowledge tab printed an undefined unit for documents of 1 GB or more.
## [1.3.1] - 2026-10-14
### Changed
- Lists in the admin panel share one shape: the same cell spacing and column
layout, the whole row opens the item's detail, and switches or menus in a row
never open it. This covers the agent's tools, MCP servers, scheduled tasks and
model tasks, the agent list and memory. The trailing arrow on rows is gone.
- Tab bars are drawn as chips everywhere (settings, skill detail, tool picker,
the agent's automation sections).
- Buttons that open a creation are labelled "Add".
- Charts use Recharts 3; Recharts 2 is no longer maintained.
### Fixed
- The period-over-period badge on the analytics cards showed a growing response
time as a negative change (an increase of 436% read "-435.8%"). It now shows
the real direction, and its colour says whether the change is good.
- A change measured against a previous period with no data showed "+100%".
The engine now returns no comparison (`null`) and the card shows no badge;
changes above 999% read ">+999%".
- Opening a drawer focused its first control, which showed that control's
tooltip on every click. The drawer now takes the focus itself.
## [1.3.0] - 2026-10-14
### Added
- An OAuth MCP server can use one shared account for the whole agent. An admin
connects it from the agent's MCP section and every conversation uses it; until
it is connected the agent does not use the server. Authorizing in each
conversation stays available and remains the default (migration `0094`).
- Plugin manifests can give `displayName` and `description` one text per
locale, keyed by BCP 47 tag (`en-US`, `it-IT`). The panel shows the
reader's language and falls back to the same language, then English. A plain
string keeps working and stands for every locale; a manifest with a locale
map needs engine 1.3.0 or later.
- Tools and hooks can carry a `summary` for people, one text per locale, and
the `label` and `description` of their configuration fields can be
localized the same way (plugin SDK 1.10.0). The Tools section, the tool
picker and the hook panels show them in the reader's language; a tool
without a summary shows the first line of its description as before. The
description the model reads stays one string and is shown in the tool's
panel as the text the agent reads.
- The panel names tool categories in English and Italian. Every built-in tool
and hook has a summary, and its fields have labels, in both languages.
### Changed
- About moves from the sidebar footer to the last item of the Help menu, and
the agent page section "To review" is now "Alerts".
- The panel's chip switchers share one component, and the selected chip stays
readable.
- Continuous integration reports one required check, `CI`, which needs every
other job, the DCO sign-off included. Documentation-only pull requests skip
the build and test jobs.
### Fixed
- `instance_hooks.action_type` defaults to `function` in the database as it
already did in the schema, so a hook row written without it no longer gets a
hook type the engine stopped running (migration `0095`).
- A refresh of a shared MCP account that the provider refuses no longer erases
the tokens a concurrent refresh has just saved, which left the agent
disconnected with providers that rotate refresh tokens.
- Adding an MCP server under a slug the agent already uses is refused instead
of replacing the existing server.
## [1.2.0] - 2026-10-12
> **Upgrading from 1.1.2 needs operator action.** The engine and the panel run
> on Node 24. Several built-in tool families and
> `fileUpload` now require plugins (agents keep them enabled under their new
> names). Attachment storage uses each agent's own bucket and access keys, and
> storing inbound attachments becomes a per-agent choice, off for every agent. Telegram and Slack arrive
> through webhooks and need a public address. A new agent starts with its Web
> channel off and requires its API key. Environment variables that set product
> behaviour are gone, and a non-default value has to be entered in the panel
> before the upgrade. Migration `0086` rewrites the conversations table. See
> [docs/UPGRADING.md](docs/UPGRADING.md).
### Added
- First-access guidance in the panel. A new user gets a full-screen guided setup
(provider and key, model, agent, a chat test through the Playground, channels and
agent key, optional knowledge), then an interface tour; the first time a
real agent's page opens, a small offer starts a tour of its sections. Every
step can be skipped, "Don't show again" is available at each stage, and the new
Help menu in the header reopens all three and links to the documentation, the
marketplace and the product site. The state is stored per user on the engine
(`users.onboarding`, returned by `GET /api/me` and written with
`PATCH /api/me/onboarding`), so it follows the person across devices.
Accounts that exist when migration 0093 runs start as dismissed.
- A `createSkill` tool lets an agent write a skill from the conversation and
enable it on itself. It is opt-in: no agent has it until an operator enables
it in the agent's tools.
- Organization Owners issue and revoke management API keys from the panel,
under **API keys** in the organization's navigation, so `polyant-cli` and
scripts can authenticate with `X-Polyant-Key` on an OSS deployment. A key
carries the Owner permission set and an optional expiry, and its token is
shown once. The routes are `GET`/`POST /api/organizations/:orgSlug/api-keys`
and `DELETE /api/organizations/:orgSlug/api-keys/:keyId`; they need
`org:write`, and issuing and revoking refuse a request authenticated with a
key, so a key cannot mint or revoke keys; it can list its own organization's.
Issuing and revoking are recorded in the management audit log.
- Settings → General holds the installation's operational policies, for a
platform admin: analytics retention, the engine's public address, the
per-user and global live-stream caps, the rate-limit window and request
limit, the agent-to-agent and MCP connection timeouts, and the two scheduler
deadlines. Each is empty until set, and an empty field shows the value in
force as its placeholder.
- Agents can set their timezone, locale, memory-deduplication threshold and
message-coordination timings from their Settings page, and an organization's
knowledge-document cap can differ from the shipped default without a
redeploy.
- The engine's public address can be corrected without a redeploy. `BASE_URL`
stays as the value the deployment boots with; the stored address wins where
one is set, and every webhook URL, OAuth redirect and agent card is built from
the resolved value.
- Web chat context: `POST /api/instances/:slug/chat/stream` (and
`/v1/chat/completions`) accept a `context` object that is projected onto the
conversation state before the turn, through a per-agent web context field
mapping edited under Channels → Web/API, so a voice or web front end hands
tools and hooks the caller's identity and keeps streaming. A request with
context requires `chat_id` and the agent's API key, and an agent that maps at
least one context field requires its key on every web turn, with or without
context, even when its authentication is otherwise off. Migration `0085` adds
the column.
- `_private` is a reserved conversation-state key that is stored and returned by
the state API but never rendered into the prompt. The whole underscore
namespace is reserved: neither the web context mapping nor the HTTP channel
mapping may target an underscore key.
- A conversation can be followed live from its detail page, on any channel:
`GET /api/activity-stream/conversation` streams one conversation's activity
and signals each committed write, gated by `conversation:read` and sharing the
live-stream caps. Tool calls now reach the activity stream, so the activity
panel shows them, and the Playground follows the same detailed-view
preference as Conversations.
- Conversation activity is shown as a lightweight timeline.
- Scheduled tasks recover after a process dies or a run exceeds its deadline.
`GET /health/scheduler` reports free slots and stuck runs, and migrations add
per-task `max_run_ms` plus an index for running tasks.
- Models: OpenAI's gpt-6 family (`gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`),
Claude Opus 5, Opus 5.5, Sonnet 5.5 and Fable 5.1, and on Nebius GLM-5.3,
GLM-5.3-Flash, DeepSeek V4.1 Flash, DeepSeek V4 Pro 0813, Kimi K3, MiniMax M3
and Nemotron 3.5 Lightning. On Bedrock, Claude Opus 5 and Opus 5.5 (EU and
global profiles) and Sonnet 5.5 (global only, the one profile AWS publishes
for it).
- Model: Claude Haiku 5.5 on Anthropic and on Bedrock (EU and global profiles).
It is priced by prompt length: a model call whose prompt passes 100K tokens
pays 5× on every rate, input, output and cache. Each call of a tool loop is
priced on its own prompt, and `GET /api/instances/models` returns the higher
rates as `costLongPrompt`.
- OpenAI GPT-5.4, GPT-5.6 and GPT-6 are priced by prompt length too: a call
with more than 272K input tokens pays the long-context rates for the whole
request.
- AI providers and OpenAI-compatible embedders can be registered at boot
(`registerAiProvider`, `registerEmbeddingProvider`) instead of edited into the
maps every request reads. The embedder selector in an agent's settings lists
what the server serves rather than a hardcoded pair.
- `readFile` accepts `offset` and `limit` and returns that line range with the
file's total line count, so an agent can read part of a large document without
carrying the whole file through the rest of the turn.
- Plugin manifests can declare runtime packages and environment values. The
Docker build compiles plugins placed under `packages/engine/src/plugins/` and
installs only the system dependencies those plugins request.
- Plugin manifests accept optional `displayName` and `description`, and a
`requiredSecrets` entry can carry a `label` and a `description`; the panel
uses them to present the plugin and the fields its tools ask for.
- The agent's Tools section lists enabled tools with their origin, opens each
tool in a side sheet with the parameters it declares, and enables new ones
from a picker. Hooks open in the same kind of sheet, with their event,
timeout, order and parameters saved together.
- The agent page explains what a capability still needs before it works, such
as transcription credentials, instead of leaving it enabled and silent.
- The Debug sheet can copy a complete captured turn — model payload and step
trace — as one JSON object.
- `POSTGRES_POOL_MAX` sets the main Postgres pool (default 10).
`POSTGRES_ANALYTICS_POOL_MAX` (default 3) and
`POSTGRES_ANALYTICS_STATEMENT_TIMEOUT_MS` (default 15 seconds) size the new
analytics pool and bound its statements.
### Changed
- **BREAKING — the LangSmith trace metadata key `oa_conversation_id` is now
`conversation_id`.** Dashboards and filters built on the old key need
updating.
- **BREAKING — the engine and the panel run on Node 24**, the active LTS. The
published images carry it; a deployment that runs from source or builds its
own images must move to Node 24, and Node 22 is no longer tested. Under load
the platform's overhead per turn fell sharply, because Node 24 no longer
implements `AsyncLocalStorage` through promise hooks.
- **BREAKING — GitHub, Render, HubSpot and Markdown-to-PDF tools moved out of
core into plugins.** Their names are namespaced (`ghIssue` becomes
`github:issue`, `hubspotContact` becomes `hubspot:contact`, and
`markdownToPdf` becomes `extra:markdownToPdf`). Migration `0087` renames the
catalog rows in place, so agents keep the tools enabled and skills keep their
links and required-tool lists; an import of an older bundle maps the old
names too. The tools work again once their plugin is installed
([polyant-hubspot-plugin](https://github.com/polyant-ai/polyant-hubspot-plugin)
v1.1.1, [polyant-extra-plugin](https://github.com/polyant-ai/polyant-extra-plugin)
v1.1.0); GitHub and Render have no public plugin.
`verifyDocument` was removed without a replacement: the first boot removes
it, and any other tool no plugin provides, from the agents that had it and
logs their names.
- **BREAKING — `fileUpload` moved out of core into the extra plugin** as
`extra:fileUpload`. Migration `0089` renames the catalog row in place, so
agents keep the tool enabled and skills keep their links and required-tool
lists; an import of an older bundle maps the old name too. The tool works
again once the extra plugin is installed
([polyant-extra-plugin](https://github.com/polyant-ai/polyant-extra-plugin)
v1.1.0).
- **BREAKING — Telegram and Slack now arrive through webhooks.** Telegram used
long polling and Slack Socket Mode, so both worked without a public address.
Now the engine registers `<public address>/webhooks/telegram/<agent>` with
Telegram, and Slack must be pointed at `<public address>/webhooks/slack/<agent>`
with Socket Mode switched off; the Slack app token is no longer used. The
engine needs a public HTTPS address and `/webhooks/*` must reach it.
- **BREAKING — attachment storage is configured per agent, and storing inbound
files is opt-in.** The four `PLATFORM_S3_*` variables are gone; attachment
persistence and `extra:fileUpload` share the agent's bucket, reached only with
the agent's own `aws_access_key_id` and `aws_secret_access_key`. The bucket,
region and keys are set on the
agent's Advanced page, in a card that holds the **Store attachments** switch;
an agent keeps the files its users send only while the switch is on.
Migration `0088` adds the switch off for every agent, and the model sees each
file in the turn either way. Stored files are deleted with their conversation
and with the agent. Files earlier versions stored in the platform bucket stay
there; the upgrade guide shows how to copy them.
- **BREAKING for new agents — Web is a channel and starts off.** The agent's public
HTTP endpoints (`POST /v1/chat/completions`, `POST /api/instances/:slug/chat/stream`)
answer only while the agent's `web` channel is enabled (Channels section,
`PUT /api/instances/:slug/channels/web`). With Web off they answer 404, the way a
switched-off Telegram or Slack webhook does, and `GET /v1/models` with the agent's
own key lists nothing. Switching Web on needs the agent's API key unless
authentication is turned off. The panel's Playground talks to the new
`POST /api/instances/:slug/playground/stream`, which uses the panel session and the
new permission `agent.playground:use` on the agent, so it works with Web off and
asks for no key. Every built-in role holds the permission, viewer included (migration
`0092` grants it to existing roles). Migration
`0091` switches Web on for every existing agent, with its `auth_enabled` as it was;
bundles carry the `web` channel (format 1.3) and an older bundle restores the
endpoint on. A2A keeps its own `a2a_enabled` switch.
- A new agent now requires its API key (`auth_enabled` defaults to true, migration
`0090`). It cannot be reached until a key is saved; the Web channel panel can
generate one. Turning authentication off asks the reader to accept the risk
first. Existing agents and imported bundles keep their current setting.
- **The OpenAI tiers moved to the gpt-6 family** (`fast` → `gpt-6-luna`,
`standard` → `gpt-6-sol`, `heavy` → `gpt-6-astra`). They pointed at
`gpt-4o-mini`/`gpt-4o`, which OpenAI lists as deprecated. Every OpenAI agent
without a pinned model, and its background jobs, lands on the new models; the
deprecated ones stay in the catalog so a pinned agent keeps its costs priced.
- The Anthropic `fast` tier runs Claude Haiku 5.5 instead of Haiku 4.5, with
thinking switched off for the calls that do not ask for it (titles, memory,
summaries). The `fast` tier runs the background jobs of every Anthropic
agent, pinned or not.
- Bedrock's default `standard` and `heavy` tiers no longer require Anthropic
model access: they use Amazon Nova Pro and OpenAI gpt-oss 120B respectively.
Nova keeps system-prompt caching without placing an invalid cache marker on
tool messages.
- New agents start with memory off and audio transcription disabled until they
are configured (migration `0083`); existing agents keep their settings.
- Prices corrected against the published pages: Claude Sonnet 5 is $2/$10 (it
was recorded at $3/$15), the gpt-5.6 family came down, its cache writes are
charged at the published 1.25× input rate, `o3`'s cached input is $0.50, and
Bedrock `eu.*` Claude 4.5+ profiles carry the 10% regional premium, where none
was modelled. Anthropic cache writes are priced by their TTL, and traces
record five-minute cache-write tokens separately (migration `0084`).
- A hook that returns `regenerate` or `injectContext` on a Room or webhook turn,
where those controls are not honored, now logs a warning naming the hook and
the dropped control.
- The plugin SDK is pinned at v1.9.0. SDK 1.9.0 declares `ctx.artifacts` on
`ToolContext`, so plugin tools read it from the SDK's own types; `put` and
`take` may return a promise and must be awaited. The SDK's knowledge-access
declarations (`requiredKnowledge`, `ctx.knowledge`) are not implemented by
this engine: a plugin that declares them is loaded, but gets no knowledge
base handle.
- Runtime dependencies moved to NestJS 12, AI SDK 7, React 19.3, Vitest 5,
dotenv 18, jsdom 30 and markdown-it 15.
- Telegram webhook registration happens after the channel starts and is
retried on rate limits and transient errors, so a failed registration no
longer disables the channel. Telegram updates are acknowledged before the
turn runs.
- Conversations carry their message count, user message count and time of
the last message. The conversation list, the conversation detail, the
search and the analytics read them instead of counting messages, so their
cost no longer grows with message history. On 2 million messages the
analytics queries run 4 to 10 times faster.
- Dashboards and other analytics reads run on their own connection pool, so a
heavy aggregate no longer holds the connections a conversation turn needs.
- Connections to MCP servers that do not use OAuth are reused across turns
for up to a minute, instead of a new handshake on every turn. A connection
that fails is retired and the next turn opens a fresh one.
- A model call no longer reads the agent's row from the database: the agent's
identity is cached for a minute and dropped when the agent changes.
- The panel loads the changelog only for a Platform Admin, and loads the
changelog card's markdown renderer only when the dialog opens.
- Management keys are verified against a SHA-256 digest instead of bcrypt,
which held the event loop for about 250 ms on every request a key
authenticated. A key stored as bcrypt keeps working and is rewritten the
first time it is used.
- `/v1/chat/completions` answers 503 in OpenAI's error shape, with
`code: missing_provider_key` and `x-should-retry: false`, when the agent has
no API key for its AI provider, before any stream byte. Channels and the
panel keep showing the warning.
- With no language chosen, the panel starts in the browser's language when it
is one the panel supports, and in English otherwise. A stored choice still
wins.
- Turning Knowledge on gives the agent the read-only knowledge tools
(`searchKnowledge`, `getKnowledge`). Only the switch from off to on assigns
them, so re-saving the agent does not bring back a tool its author removed.
### Removed
- **BREAKING — deployment configuration was narrowed.** `AUTH_MODE`,
`DEFAULT_INSTANCE_ID`, `AWS_REGION`, `PLATFORM_ADMIN_EMAIL`,
`WORKSPACES_ROOT`, `DEBUG_LLM_PAYLOAD` and the unused `LANGSMITH_*` variables
are no longer product configuration.
- **BREAKING — ten environment variables that the panel already answers.**
`DATETIME_TIMEZONE`, `DATETIME_LOCALE`, `DEDUP_SIMILARITY_THRESHOLD`,
`MESSAGE_SOFT_DEBOUNCE_MS`, `MESSAGE_TYPING_DELAY_MS`, `MESSAGE_MAX_RESTARTS`,
`KNOWLEDGE_MAX_DOCS_PER_INSTANCE`, `ANALYTICS_RETENTION_DAYS` and
`SSE_MAX_CONNECTIONS_PER_USER` were the deployment default behind a value an
administrator now sets per agent, per organization or per installation; the
shipped defaults are unchanged and live beside the resolver that applies them.
`PDF_CONCURRENCY` belongs to the Markdown-to-PDF plugin, which reads it itself.
A deployment that still sets any of them is not failed, but the value has no
effect.
- **BREAKING — seven more environment variables become panel settings.**
`SSE_MAX_CONNECTIONS`, `THROTTLE_TTL_MS`, `THROTTLE_LIMIT`,
`AGENT_CALL_TIMEOUT_MS`, `MCP_CONNECT_TIMEOUT_MS`, `SCHEDULER_ORPHAN_GRACE_MS`
and `SCHEDULER_DEFAULT_MAX_RUN_MS` are now rows in `platform_settings`, edited
in Settings → General, with the same defaults as before. `THROTTLE_ENABLED`
stays an environment variable: it exists for parallel local runs, and a rate
limiter that can be switched off from inside the product is no use to a
locked-out administrator.
- The CDK stack no longer passes `DEFAULT_INSTANCE_ID` or `DATETIME_LOCALE` to
the engine container, and sets the deployment's time zone as `TZ`. The `app`
block of `config.yaml` keeps only `timezone`.
- The seeded `demo-agent` (authentication off). Migration `0091` deletes it on a
database where it was never used (no conversation, memory, knowledge document,
scheduled task or secret, and its row never updated); a `demo-agent` that was
used or edited stays and is treated like any existing agent. A fresh install has
no agent until you create one.
### Fixed
- The engine finishes shutting down while a panel holds an activity stream open.
`SIGTERM` used to wait on that connection until the platform killed the
process, so every deploy with a panel open waited out the stop timeout and
lost the last buffered audit rows. Open streams are now ended first, and a
request already in flight gets ten seconds to finish. Turns a Telegram,
Slack, WhatsApp or event webhook has already acknowledged get the same ten
seconds: their sender does not retry them, so cutting them off lost the reply.
- A Telegram channel went silent after every rolling deploy: the replica being
stopped deleted the webhook the new one had just registered. The webhook is
now removed only when the channel is switched off or deleted, or its agent is
deleted.
- The CDK stack sends `/webhooks/*` and `/channels/http/*` to the engine without
the OIDC sign-in. They fell through to the web panel, which answered 404, so
inbound Twilio, email, HubSpot and HTTP-channel traffic never arrived on an
ALB deployment.
- A Room's prompt reaches the model. It was editable in the panel and stored,
but only ever used for a token estimate.
- A webhook event definition no longer drops a matching event because the model
answered "Yes." or "Yes, …" instead of a bare "yes", and "si", "sì" or "y"
count as a match only when they are the whole reply, so "Si tratta di…" no
longer wakes the agent. The matcher sends the classifier the same projected
payload the Room shows instead of the full JSON.
- `spawnTask` can be enabled on an agent, and the sub-agents it starts run on
the agent's provider and pinned model and stop when the turn is aborted. Its
file did not match the tool loader's naming
pattern, so it was never registered and sub-agents were unreachable.
- An exported agent comes back as it left: the bundle carries the agent's
thinking level and the values of tool and hook parameters declared not
sensitive, which the import re-checks against its own registries.
Credentials still travel as key names only.
- `chat/stream` now aborts the pipeline when the client disconnects before the
turn has produced anything (no text, reasoning or tool call yet): the model
stops and the abandoned turn is not persisted. Once the turn has produced
something it runs to its end and is saved, so a tool that may have written is
never left unrecorded.
- `chat/stream` stops relaying a turn when the client disconnects. It listened
for the request's `close`, which Node emits once the body has been read, so a
real disconnect was never seen.
- A streaming `/v1/chat/completions` request the pipeline refuses is answered
with its error status. The stream used to open first, so the refusal arrived
inside a 200 already being read, and the response ended without `[DONE]`.
- Switching thinking off now switches it off on models that reason by default —
gpt-6 sol/luna and Claude Opus 5 and Sonnet 5 kept reasoning (and billing for
it) when the parameter was simply omitted, on Bedrock as well as on the Claude
API. Claude Sonnet 5.5 refuses the usual off-switch, so it gets
`between_tools`, its lowest setting.
- Claude Fable 5 offered a thinking toggle that did nothing: the model reasons on
every call. The toggle is now locked on, as it already was for Fable 5.1.
- The reasoning-level clamp could send `medium` to a model that does not accept
it, and two capability checks looked a model up across providers by id alone.
- An agent on a registered embedder was reported as missing credentials: the
readiness check asked for the OpenAI key regardless of the embedder.
- An unknown embedder name — from an import, or a stale row — fell through to
OpenAI instead of failing; it now fails, and an import naming one is refused.
- A multi-step model call that fails at a later step is logged with the tokens
and cost of the steps it completed, which the provider billed; it was logged
at zero.
- The engine warns at boot for each retired environment variable still set,
naming where its value is set now.
- Scheduled tasks no longer remain permanently `running` after a deploy or
crash, and one malformed tool-audit entry no longer blocks the rest of an
agent's audit trail.
- Scheduler startup recovery waits through each task's run deadline and does
not overwrite an outcome already recorded by another process.
- Agent bundles preserve each scheduled task's run deadline; event-source edits
and token rotation return 404 for missing sources; inbound messages use default
timings when their settings lookup fails. In-process plugin artifacts now have
bounded size, lifetime, storage per conversation and aggregate storage, and
a handle produced in a turn without a conversation resolves only for the
agent that produced it.
- A failed upload of an inbound attachment no longer loses the turn: the message
and the reply are saved without the attachment's stored copy.
- One management-audit row the database refused (an over-long target id, say)
stopped every later write of the management audit until 500 newer rows pushed
it out. Refused rows are now dropped one by one, and values are cut to their
column width.
- Tenant-scoped conversation and memory reads and mutations fail closed;
unresolved request tenancy is refused instead of widening or silently losing
a predicate.
- Agent behaviour overrides are persisted and type-validated, and two
concurrent fragments can no longer overwrite each other while a message burst
is starting.
- Attachment routes accept Express 5 wildcard segments, and filenames
containing spaces, `#`, `?`, or harmless dot runs remain reachable.
- An agent PATCH validates the provider and model the agent will run on
together, whenever it changes either, so changing only the provider can no
longer leave the agent pinned to a model of the previous one.
- Signed-out pages can switch language, and organization members cannot
accidentally remove their own membership.
- MCP credential edits preserve omitted secrets, OAuth metadata receives the
same URL validation as the server URL, A2A cancellation survives handler
cache expiry, and the panel refuses to enable API-key authentication without
a key.
- `docker build -f Dockerfile.web` works again: the web build reads
`CHANGELOG.md`, which the image never copied.
- Telegram and Slack redeliveries of an update or event seen in the last hour
are dropped instead of answered twice, and each bot's webhooks are rate
limited on their own bucket instead of sharing one per client address. The
CDK stack sets `TRUST_PROXY=1`, so the engine sees the caller's address
behind the load balancer.
The check is kept in memory in each process: with several replicas, a retry
that reaches another replica is answered again. Run one replica, or accept
the occasional duplicate.
- The scheduler reaper fails only the run it judged overdue, never a newer
claim of the same task.
- A cron task that `scheduleTask` creates without a zone is stored with `UTC`,
the default the tool describes, instead of running in the process zone.
- `/v1/chat/completions` with `stream: true` handles a client disconnect like
`chat/stream`: a turn that has produced nothing is aborted, a turn that has
produced output still runs to its end and is saved, and a heartbeat keeps
the connection open through long tool calls.
- Analytics cost and token totals include what the provider billed for calls
that failed after completing steps; call counts and response times still
describe answered calls.
- Amazon Nova on Bedrock is priced at the eu-south-1 rates. An `eu.*`
inference profile is billed at the price of the Region that calls it, and
the catalog carried the us-east-1 price, a cache read at a tenth of the input
rate instead of a quarter, and a cache write AWS does not charge for. A
warm-cache Nova Lite turn was recorded at a third of its cost. Nova Lite is
now $0.096 / $0.384, Nova Pro $1.28 / $5.21, Nova Micro $0.056 / $0.224 and
Nova 2 Lite $0.528 / $4.411 per million input / output tokens; Qwen3-Next
80B moves to its Milan price too.
- A turn preempted by a follow-up message (cancel-and-restart) is logged with
what its model calls were billed, as `outcome = 'aborted'`. It used to leave
no row, so that spend was missing from every cost total. Cost and token sums
count it; call counts, response times and error rates do not.
- Embedding calls are logged with their tokens and cost, as `call_type =
'embedding'`. Knowledge ingestion, memory extraction and retrieval were
billed by the provider and left no row. `text-embedding-3-small` is priced at
$0.02 and Titan Text v2 at $0.023 per million tokens; a registered
OpenAI-compatible embedder is logged with no cost.
- Cost totals are summed in double precision. The `real` column was summed as
a `real`, so a total over many rows lost precision: a million rows of
$0.000123 summed to $122.19 instead of $123.00.
- The model call behind the prompt-section tools counts in its conversation's
cost, like the other service calls of the turn.
- An import normalises the web context field mapping as the panel does, and
refuses an invalid one before writing anything.
- `readFile` caps `tail` at 500 lines like other windows, and its refusal of a
file over 512 KB no longer suggests a range read that would also be refused.
- A variable a plugin declares in `system.env` no longer overrides the same
variable set in the operator's container environment.
- `drizzle.config.ts` lists every schema file again; `drizzle-kit push` is not
a supported path and the config says so.
- A failed load of Settings → General shows the reason with a retry instead of
a skeleton.
- A Claude turn on Anthropic or Bedrock that ran three or more tool steps no
longer fails. Each step added a cache breakpoint and kept the previous ones,
so the request passed the provider's limit of four: Bedrock refused it with
a 400 (the caller saw a 500) and Anthropic dropped the newest. The
within-turn breakpoint now moves to the latest step.
- A scheduled run that finishes after the reaper failed it no longer clears
the claim of the run that replaced it, nor deletes a one-shot task under it.
A replica that claims a task another replica has just run no longer runs it
a second time.
- On shutdown the schedulers stop first, so no task starts during the
ten-second HTTP grace period to be cut off by the exit, and a step that fails
(a trace-store flush against a database already gone, say) is logged and no
longer keeps the process alive until the platform kills it.
- A WhatsApp or Telegram message sent while the previous answer was being
cancelled is answered together with the earlier fragments in one reply. When
the cancelled run took longer than the debounce window to stop, the contact
got one reply to the new fragment and another to the old ones.
- A Telegram reply in flight when the channel is saved is still delivered, and
Telegram file downloads stop after 30 seconds and refuse files over 20 MB.
- The engine warns at boot when a `PLATFORM_S3_*` variable is still set:
1.2.0 no longer reads them.
- The upgrade guide's pre-step for migration `0086` built an index on
`conversations.last_message_at`, a column 1.1.x does not have, so it failed.
It now builds only the `conversation_messages` index ahead.
- Claude Sonnet 5.5 cache reads cost $0.10 per million tokens on Anthropic and
on the Bedrock global profile, not $0.20.
- Claude models with adaptive thinking (Opus 4.7 and later, Sonnet 5 and 5.5,
Haiku 5.5, Fable) show their reasoning again. They return it with empty text
unless a summary is requested, so the panel, the logs and the stored turn had
none although the reasoning was billed; the gateway now asks for the summary
on Anthropic and on Bedrock.
- GPT-5.6 Sol is priced at its full $5 / $30 rather than the promotional
$4 / $20, so a spend cap does not loosen when the promotion ends.
- A skill whose required tool comes from a plugin is offered to the model
again. Its namespaced name was compared with the name the tool is given to
the provider, so every such skill was left out of the prompt.
- An agent configured with its own Bedrock access-key pair signs its chat and
embedding calls with that pair even when `AWS_BEARER_TOKEN_BEDROCK` is set on
the host; the deployment's token used to win.
- Text document uploads, knowledge imports and agent imports accept JSON bodies
up to 50 MB. They were refused above 100 KB, so an export larger than that
could not be imported back. Every other route keeps the 100 KB limit.
- A request body over its size limit is answered 413, and one of an
unsupported content type 415, instead of 500.
- A stored attachment is read and deleted in the bucket it was written to. After
an agent's bucket changed, older files could not be opened and a deleted
conversation left them in the old bucket while counting them as removed.
Files stored without a recorded bucket still use the agent's current bucket.
- Knowledge documents left uploading or processing by a process that died are
failed within minutes, at boot and periodically after. A restart within five
minutes used to leave them looking busy for good.
- The live conversation view shows a turn that starts seconds after the
previous one was stored, instead of hiding it until it finished.
- A model call aborted by its caller's deadline is logged as an error of kind
`timeout` rather than as a user preemption.
- Voice notes transcribed by Deepgram Nova-3 ($0.0043 per minute) and Amazon
Transcribe ($0.0001667 per second) are priced; they were logged at $0.
- Conversation history replays a tool that moved into a plugin under the name
it has now, not its old flat name.
- The playground's error messages, its copy button and the badge of a
transcribed voice message follow the panel's language.
- Messages that sent people to "an administrator" name the Organization Owner
and Admin, and a Platform Admin where one is the right person.
- A management key whose id is not a UUID is refused before the lookup instead
of reaching Postgres as an error.
- Changing an MCP server's address to another host no longer reuses its stored
credential: saving or testing it there answers 400 until the credential is
entered again. Anyone allowed to edit MCP servers could otherwise send the
stored token to a host of their choosing.
### Security
- Each Telegram channel has its own random webhook secret, stored with the
channel and independent of the bot token. The engine mints it and registers
the webhook when the channel starts.
- Message text, reply previews, contact names, the memory extractor's
unparseable replies and the full error of a failed Telegram or outbound send
are logged at debug level only; the normal levels keep the error message and
stable ids.
- Closed cross-tenant keyword search and mutation paths by making tenant scope
explicit, branded and fail-closed throughout the affected stores.
- Disabled skills can no longer resolve their retained credential placeholders;
rotating bearer values no longer mint an unlimited series of throttle
buckets; MCP OAuth endpoints use the shared SSRF denylist.
- Request-controlled channel identifiers are sanitized before logging, unknown
imported channel types are rejected safely, and raw provider response bodies
are no longer written to logs.
- Updated undici to 8.11.2 and 7.30.0 for its open security advisories. The
Next.js, Sharp, js-yaml, Multer and qs updates already shipped in 1.1.2.
- Updated axios to 1.20.0 for twelve high-severity advisories in 1.19.0.
- The engine and web images run as the unprivileged `node` user instead of
root, and declare a health check (`/health` on the engine, the sign-in page
on the web). A volume mounted on the engine's `/app/logs` or
`/app/packages/engine/workspaces` must be writable by uid 1000.
## [1.1.2] - 2026-09-24
Security patch release.
### Security
- Library updates for security reasons, including Next.js 16.3.6 in the admin panel.
## [1.1.1] - 2026-09-17
Patch release. It restores agent creation, which fails on every attempt in 1.1.0.
### Fixed
- **Creating an agent no longer fails.** `POST /api/instances`, and the admin
panel's New Agent button behind it, answered 500 on every call in 1.1.0: the
create path seeds its four tables inside one transaction, but the tool seed
issued its insert on a second pooled connection, which cannot see the agent row
that transaction has not committed, and the insert died on
`instance_tools_instance_id_fkey`. Every read and write on the create path now
runs on the caller's transaction. The skill seed carried the same split — it is
inert on this build, where the default skill list is empty, and is corrected
with it.
### Changed
- `recomputeInstanceTools` accepts an optional executor. Given one it reuses that
transaction instead of opening a second; called without one, as the import
paths do after their own transaction closes, it behaves as before. Internal
module, not a documented SemVer surface.
## [1.1.0] - 2026-09-04
> **Upgrading from 1.0.0 needs operator action** — this release is not a rolling
> upgrade, is forward-only past migrations `0071` and `0076`, and requires forcing
> every user to sign in again. See
> [docs/UPGRADING.md](https://github.com/polyant-ai/polyant/blob/main/docs/UPGRADING.md).
### Added
- **MCP client**: an agent can equip tools from external Model Context Protocol
servers, configured per agent, with `none` / `static` / OAuth 2.1 auth modes.
Credentials are encrypted at rest and never returned by the API. New tunable
`MCP_CONNECT_TIMEOUT_MS` (default 10000) bounds the per-server connect so one
slow server cannot stall a turn.
- **A2A server**: an agent can be exposed over the Agent2Agent protocol (Agent
Card + JSON-RPC). Opt-in per agent, off by default.
- **Tenant-scoped frontend URLs**: admin routes live under the workspace
segment, `GET /api/me` reports the caller's tenancy, and navigation is
scope-aware.
- **Every agent section is addressable from the sidebar.** The agent detail page
dropped its nested tab row: each section is a sidebar row under the same
headings, and `?tab=` remains the address. Twenty-three destinations became
eighteen, and the workspace-wide conversations, saved memories and run log
gained a per-agent view.
- **MCP servers are their own section** (`?tab=mcp`) rather than a block at the
tail of the Tools page.
- A scheduled task can be created disabled: `POST` accepts `enabled` (default
`true`), so a schedule can be staged without a window in which it can tick.
- **WhatsApp channels can authenticate with a Twilio API Key** instead of the
account Auth Token. Twilio signs inbound webhooks with the Auth Token only, so
a channel in that mode receives messages on a dedicated webhook URL carrying a
server-generated secret, revealed and rotatable from the admin panel. First
released in 1.0.1; included here.
- **Retention covers every traffic-driven table.** `tool_audit_logs`,
`hook_executions`, `scheduled_task_runs` and the completed half of
`event_backlog` now age out on the same policy as `ai_logs` and
`pipeline_traces`; the four are also what the heaviest analytics
aggregations read. Conversation messages, memories and the knowledge tables
are deliberately excluded — ageing product data out is an operator's
decision, not a housekeeping job's.
- **A failed provider call is recorded.** A turn that died at the provider
used to write no `ai_logs` row at all, so an agent with an expired key looked
exactly like an idle one. Migration `0077` adds `outcome` and `error_kind`,
classified into a closed set (`auth`, `rate_limit`, `bad_request`,
`overloaded`, `timeout`, `unknown`); the provider's own message is never
stored, because it can quote the request and the request is the prompt.
- **The switch that closes an agent's HTTP surface is back in the panel.**
`authEnabled` defaults off, so a new agent answers `/v1/chat/completions`
with no credential — the Status page reported this correctly, but the control
it pointed at had been removed, leaving a hand-written `PATCH` as the only
way to close an open agent.
### Changed
- **BREAKING — `users.role` is dropped.** Migration `0076` reconciles the flag
from the role column one final time — any row promoted by a direct `role`
update that never touched `is_platform_admin` is brought into agreement
first — then drops the column, so no account silently loses its standing.
**There is no rollback past this migration**: the column is gone, and older
code that still selects `role` fails on every read of the users table.
- **BREAKING — `users.is_platform_admin` is the sole authority for
platform-admin standing**, read from the database on every request instead
of carried on the session. Promoting or revoking an account now takes
effect within the platform-admin cache's five-minute window, without
requiring the account to sign out and back in.
- **BREAKING — `POST`/`PATCH /api/users` take `isPlatformAdmin: boolean`** and
no longer return `role`. `role` is still accepted on input for one release,
as a deprecated alias for both legacy spellings (`platform_admin` and
`superadmin`), and is never persisted or echoed back.
- **BREAKING — RBAC is enforced unconditionally.** The `AUTHZ_ENFORCE`
environment variable is gone; there is no shadow mode. An undeclared route or
a failed permission check is a 403 with no way to turn it off. Installations
that copied the previous sample `.env` were running with every permission
check reduced to a no-op.
- **BREAKING — `AUTH_MODE=alb-oidc` is not compatible with enforced RBAC.** A
gateway-forwarded identity carries no organization and holds no role bindings,
so it is denied on every management route. The engine now refuses the
combination at boot instead of 403-ing at runtime. Use `AUTH_MODE=session`
until the gateway-identity mapping exists.
- **BREAKING — membership is granted deliberately, never by signing in.** A new
user is provisioned with no organization membership; an administrator must
assign a role before they can reach anything.
- **BREAKING — the platform-admin role value is `platform_admin`** (was
`superadmin`). Reads accept both spellings; writes emit only the canonical
value. Migration `0071` rewrites the persisted value. **Forward-only** — see
the upgrade guide.
- **BREAKING — Member can now configure an agent end to end**, credentials
included: migration `0072` grants `agent.secret:read`, `agent.secret:write`
and `agent.export:read` to the Member role in every existing organization.
Secrets remain write-only through the API (reads return key names only), but
this is a real privilege expansion applied on upgrade.
- **BREAKING — frontend URLs are tenant-scoped** and the workspace URL segment
is authoritative. Pre-existing bookmarks to flat paths (`/instances`,
`/conversations/<id>`, `/playground`, …) and to legacy `?tab=` values no
longer resolve; navigate from the organization dashboard instead.
- **BREAKING — an empty tool set means no tools.** `instance_tools` holding no
rows used to be read as "enable everything", so an agent with every switch off
— or one seeded before the tool catalogue synced — ran with the whole
registry, `httpRequest` and the file tools included. An agent's tool set is now
exactly what is stored, on both the runtime and the secret-prompting side.
**Audit any agent whose tool rows are empty before upgrading**: it loses the
tools it was silently running.
- **BREAKING — `POSTGRES_SSL` is parsed strictly.** `true` and `false` are the
only accepted values and anything else — `1`, `TRUE`, `require`, `yes` —
fails the boot; an empty environment variable now means unset everywhere, for
every optional variable. The old coercion treated any non-empty value as true,
so `POSTGRES_SSL=false` switched TLS *on*. This bites before the migrations
run — audit it first.
- **BREAKING — `GET /api/tools`** now requires `ORG_READ` instead of
`TOOL_READ`: an agent-scoped principal that could previously list the registry
now receives 403.
- **BREAKING — `GET /v1/models`** returns an empty list, rather than every
active agent, for a principal whose organization cannot be resolved. An
integration that enumerates models to discover a slug sees `[]` instead of an
error.
- **A new agent's prompt sections are seeded empty.** The seven rows still
exist, so the panel has somewhere to write; only the default prose is gone, so
an agent's behaviour no longer comes from text its author never read.
- **Credentials offers every provider**, not just the ones an agent already
runs on, so a key can be entered before the agent is switched to it. LangSmith
stays the exception, beside the tracing switch that reveals it.
- Membership grants and revocations, platform-admin bootstrap, and the
`/api/users` mutations are now recorded in the management write-audit log.
- **The engine runs on AI SDK 7.** `usage` is now the across-steps cumulative
total, which is what feeds cost estimation and the per-model cache rates, and
the prompt-cache marker moved onto `instructions` because v7 rejects a
`system` message inside `messages`. No configuration changes.
- **Outbound HTTP that carries the SSRF-pinned dispatcher goes through one
place.** The engine moves to undici 8, whose `Agent` Node's bundled fetch
refuses outright — the three call sites that passed a dispatcher to the global
fetch would have silently lost DNS pinning.
- The engine lints on ESLint 10; the admin panel stays on 9, which
`eslint-config-next` still requires. Relative import extensions are enforced
per package rather than by a repo-wide rule that was half wrong.
### Fixed
- `/v1/chat/completions` reports real token usage instead of zeros.
- The live activity SSE feed is scoped to the caller's organization, and its
teardown no longer leaks a heartbeat timer plus a bus subscription when the
connection drops mid-write.
- CodeQL findings closed: log injection, a file-read TOCTOU, and a
prototype-pollution-prone key write. Third-party actions are pinned.
- **A revoked platform admin kept a full authorization bypass for up to five
minutes.** The platform-admin cache had a 5-minute TTL and was never
invalidated; every write of the flag now clears it.
- **A duplicate, undecorated `InstanceSkillsController` is gone.** It served
`POST`/`DELETE` on `api/instances/:slug/skills` with no authorization
decorator at all, so under the previous shadow mode any authenticated user
could enable or disable skills — and with them the tool surface and prompt —
on any agent.
- Cross-tenant boundaries closed on attachments, agent creation and listing,
memory writes, agent-to-agent handoffs and the sidebar; the organization
filter fails closed, a platform admin outranks every organization role, and a
route whose authorization is not RBAC is no longer denied outright.
- Only one migrator runs at a time (session-level advisory lock), and a journal
entry that sorts before an already-applied one is no longer skipped in
silence.
- A hook that ran out of time no longer affects the turn: the deadline aborts a
signal, late writes are refused by a fenced state view, and a control return
that arrives post-abort is dropped.
- An MCP server URL is validated when it is dialled, not only when it is saved,
and an imported server is not treated as a trusted one.
- An A2A task belongs to the agent it was created on, and the JSON-RPC endpoint
is bounded.
- Enabling a tool the catalogue has lost no longer answers 200 with the change
undone: the mirror is repaired where registry and catalogue disagree.
- The admin route group serves its own 404, the sidebar stays inside the
caller's tenant, navigation links no longer misfire while the tenancy loads,
and an anonymous visitor keeps their query string across the login bounce.
- Resources acquired by the engine are released on every path.
- **An instance bundle can no longer introduce a channel credential.** Import
strips credential-like keys the way export already did, so a crafted bundle
cannot arrive carrying a webhook secret and have the channel enabled with it.
- **WhatsApp media downloads only send Twilio credentials to Twilio.** The
`MediaUrl` on an inbound message is attacker-influenced; the first hop is now
checked against Twilio's API hosts, regional data-residency hosts included,
before the Basic credential is sent.
- **A Room event-source webhook token now requires write permission to read.**
It lets its holder inject events into an agent, so a read-only role could
previously go from "can look" to "can drive". Its list response no longer
carries the token; a dedicated endpoint reveals it.
- Credential-bearing webhook paths are redacted before they reach a log line
even when the path is percent-encoded or differently cased, and
request-controlled values are stripped of line breaks so they cannot forge
additional log records.
- `X-Forwarded-Proto` is clamped to `http`/`https` when the webhook URL is
reconstructed, so a crafted value can neither corrupt the signature check nor
reach the log.
- **Rotating a WhatsApp inbound secret can no longer be undone by a concurrent
save.** The carry-forward reads under a row lock inside the same transaction
as the write; previously a save that started before a rotation could commit
the old secret back, reviving it while the audit log said it had been retired.
- Destroying a WhatsApp inbound secret is audited, both when a credential-mode
switch discards it and when the channel is deleted — so the audit trail no
longer records only the mints and rotations.
- **A throttled route no longer buckets every caller together.** Buckets were
keyed by `req.ip`, and in the standard topology the panel proxies `/api/*` to
the engine from one container: five wrong passwords in a minute locked
sign-in for the whole deployment, while an attacker shared — and hid in — the
same budget. The bucket is now the account for a credential form and the
session for an authenticated caller.
- **A prerelease engine version no longer fails every plugin's engine gate.**
SemVer excludes a prerelease from a range that carries none, so the first
build tagged with any suffix would have skipped every third-party plugin with
a warning — boot green, agent silently without its plugin tools and hooks.
The comparison now reads major, minor and patch and ignores the suffix.
- **`PATCH /api/users/:id` rejects a non-boolean `isPlatformAdmin`** instead of
coercing it. `"off"` is truthy in JavaScript and false in PostgreSQL, so the
last-platform-admin guard saw no demotion while the database performed one,
leaving a deployment with zero platform admins and no way back except SQL.
- **Creating an agent and patching its tool set are each one transaction.** A
failure between the four writes used to commit an agent with no prompt
sections or no tool rows — which the runtime reads as "exactly zero tools" —
and nothing repaired it, while the taken slug answered the operator's retry
with a 409.
- **Two statements that failed outright above a few hundred agents are gone.**
The boot-time tool-catalogue sync and agent deletion each bound one parameter
per row and crossed PostgreSQL's 65 535-parameter limit: the first threw
inside the boot transaction so the catalogue never synced, the second made a
channel agent with tens of thousands of conversations undeletable through the
API.
- The per-turn round trips that built each `ask_<slug>` tool are batched, the
dashboard aggregation is capped, and migration `0075` indexes conversations by
`updated_at` so the organization-wide list stops sorting every row the tenant
owns to return twenty.
### Security
- **Untrusted text is fenced wherever it enters a prompt.** Nonce-tagged
delimiters existed in one file; the webhook engine's substituted values and
the channel display name went in plain. A display name carrying a newline and
a forged closing tag injected instructions at *higher* trust than the user's
own message, and in the webhook case they persisted into every later turn.
- **A sensitive skill environment variable never reaches the model.**
`readSkill` — one of two tools enabled by default on every agent —
interpolated the decrypted value into its result, so the credential entered
the model's context, the persisted conversation and `tool_audit_logs`. It now
emits a placeholder that resolves inside the tool call, and the plaintext
exists nowhere else.
- **One log serializer, and it no longer writes what the policy forbids.** The
file logger serialized errors with `JSON.stringify`, which drops the
non-enumerable `message` and `stack` and keeps the custom fields — where pg,
the AWS SDK and fetch put connection strings, request configs and
authorization headers. A single provider 401 wrote the upstream bearer token
and the folded prompt into a log file. Authorization, API-key, token, secret,
password, credential, connection-string, cookie and private-key fields are now
redacted at any depth, and tool output is capped rather than copied whole into
the audit table.
## [1.0.2] - 2026-08-26
### Fixed
- The Speech-to-Text provider setting now accepts an explicit "Disabled" value. An unrecognised or unset provider previously fell back silently to OpenAI Whisper, so an operator who wanted voice messages turned off had no way to say so, and audio replies could fail looking for credentials that were never configured. Choosing "Disabled" now returns a plain "voice messages are not supported" reply instead.
## [1.0.1] - 2026-08-25
> Released from `main` as a hotfix on 1.0.0. Its changes are also present in
> 1.1.0, which carries them forward.
### Added
- WhatsApp channels can authenticate to Twilio with a revocable API Key instead of the account Auth Token. Twilio signs inbound webhooks with the Auth Token only, so an API Key channel receives messages on a dedicated webhook URL carrying a server-generated secret, which can be revealed and rotated from the admin panel.
- The WhatsApp channel has its own configuration card in the admin panel, with a credential-mode selector and the webhook URL to paste into the Twilio Console.
### Changed
- Channel configuration is persisted exactly as its schema validates it, so credentials pasted with surrounding whitespace are trimmed and keys outside the validated shape are no longer stored.
- The management API writes only known channel configuration keys and ignores unrecognised fields in a request body.
- Twilio Account SIDs are validated for format when a WhatsApp channel is saved.
### Security
- Webhook paths that carry a credential are redacted before being written to logs, covering both the WhatsApp inbound webhook secret and the Room event-source webhook token.
- Inbound WhatsApp webhook requests that fail before authentication all return one identical response, so an anonymous caller cannot enumerate agent slugs or determine which credential mode a channel uses.
- Request-controlled values are stripped of line breaks before being written to a log line, so they cannot introduce additional log records.
## [1.0.0] - 2026-08-05
### Added
- First public open-source release of Polyant, with a Supervisor runtime for configurable AI assistants, automatic long-term memory, and multi-channel delivery.
- Telegram, Slack, WhatsApp, webhooks, and an OpenAI-compatible HTTP API, alongside multi-instance administration and conversation inspection.
- Encrypted per-instance secrets, plugins and Markdown skills, Room automation, and runtime analytics.
- Bounded agent-to-agent handoffs, live activity, configurable web search, and structured tool-secret inputs.
### Changed
- Semantic Versioning now defines the public compatibility contract for the documented OpenAI-compatible API, Plugin SDK and manifest, and documented configuration and migration behavior.
- Conversation traces preserve per-step reasoning and tool metadata; incoming message fragments can safely cancel and restart an in-flight run.
### Fixed
- Memory deduplication now honors its configured similarity threshold, and Google OAuth remains optional when its credentials are absent.
- Delegated sub-agents cannot recursively spawn further sub-agents.
- Node.js 22 is aligned across the supported development and container environments.
[Unreleased]: https://github.com/polyant-ai/polyant/compare/v1.3.5...HEAD
[1.3.5]: https://github.com/polyant-ai/polyant/compare/v1.3.4...v1.3.5
[1.3.4]: https://github.com/polyant-ai/polyant/compare/v1.3.3...v1.3.4
[1.3.3]: https://github.com/polyant-ai/polyant/compare/v1.3.1...v1.3.3
[1.3.1]: https://github.com/polyant-ai/polyant/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/polyant-ai/polyant/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/polyant-ai/polyant/compare/v1.1.2...v1.2.0
[1.1.2]: https://github.com/polyant-ai/polyant/compare/v1.1.1...v1.1.2
[1.1.1]: https://github.com/polyant-ai/polyant/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/polyant-ai/polyant/compare/v1.0.2...v1.1.0
[1.0.2]: https://github.com/polyant-ai/polyant/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/polyant-ai/polyant/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/polyant-ai/polyant/releases/tag/v1.0.0