Skip to main content

5.4 Release Notes

Patch Releases​

All patch release notes for 5.4.x are available on the releases page.

Subscriptions​

Resuming a Subscription From a Checked Position​

A subscribe request can now carry the databaseGeneration its position came from. Harper then checks the position before and during the replay, and refuses one whose history was replaced (DatabaseGenerationChangedError, status code 409) or pruned (ResumeHistoryUnavailableError, status code 410) instead of replaying what is left. The subscription's resumeVerified promise resolves true once the replay is complete and checked. Requests without the field behave as before, and the check applies to RocksDB databases only. See Resuming from a position.

Durable MQTT Sessions No Longer Replay Short​

A durable MQTT session now resumes through the same checked replay. When a reconnect's saved position names history that audit retention has removed, Harper deletes the session and reports sessionPresent: false (or, if the problem appears after CONNACK, sends an MQTT v5 DISCONNECT with reason code 0x83 and closes the connection) instead of delivering a partial catch-up. A position saved before a restore or copy, or on another cluster node, resumes best-effort, as before. A quiet topic's position is kept current, so retention on other tables does not reset it, and positions no longer skip unacknowledged messages that share a transaction. Resubscribing to a topic the session already holds continues from its saved position, and QoS 0 subscriptions are kept with the session and resume live. Each connection's Last Will is kept separately, so a connection that is taken over publishes its own will, never the newer connection's. See Durable Sessions.

Component Deploys​

Certifying a Release in a Canary Worker​

A deploy_component with restart: true or restart: "rolling" now checks its release before rolling it out. The first worker started on the release, the canary, is held out of traffic until it reports whether the deployed component loaded, and the rollout goes on only if it did. Each later replacement is checked the same way. When the node that received the deploy rejects the release, the deploy fails with 400. The release it replaced is made live again, or, with nothing to go back to, the component fails closed on that node. A new certification field in the response reports the outcome, and says when a restarting deploy went out unchecked (uncertified or unavailable, as for a package: file:<dir> deploy). With "rolling", each other node then certifies the release with a canary of its own, and a node that rejects it fails the deploy's job, not the deploy: poll its restartJobId with get_job for the outcome. A node running a version before 5.4.0 activates without a canary. A deploy that restarts nothing is not test-loaded. If its release throws when it loads, get_status reports the failure. See Certifying a release in a canary worker.

Models and the Built-in Agent​

Model Pricing and Per-Call Cost​

A models.generative entry can now declare pricing: { inputPerMtok, outputPerMtok }, what you pay in USD per million prompt and completion tokens. Harper ships no prices of its own. Each generate() call is priced at the rates of the entry that served it, a fallback included, and the result carries costUsd when that entry is priced and the provider reported both token counts. With toolMode: 'auto' it covers the final round, and streaming calls are not priced. This makes maxCostUsd work on toolMode: 'auto' calls; before, it had no prices behind it and never tripped. See Pricing.

Spend Caps for the Built-in Agent​

The agent records the usage of each model call on its session, and get_agent_session and list_agent_sessions report a session's totals as usage. Two caps bound a whole session, across every run in it: the new agent.maxSessionTokens, and agent.maxCostUsd, which is now enforced from model pricing and no longer defaults to 5.00. A session at a cap makes no further model call: it ends error, or completed with lastError naming the cap when the call that reached it was the final answer. A cap that cannot be measured stops the session rather than letting it run uncapped. Caps count calls that return: a call that fails, even one the provider billed, is not counted. If your configuration already sets agent.maxCostUsd, declare pricing on the generative entry the agent uses, and start new sessions: a session created before 5.4.0 recorded no usage, so it cannot be continued under a cap. See Agent spend caps.

Agent Cancellation and Limits​

cancel_agent_run now answers every pending approval with a cancelled_by_operator result in the same write that marks the session aborted, so an approved action that has not started never runs, then or on the session's next prompt, and the session stays aborted even when a run was mid-step. agent.maxTurns is now validated as a positive integer at startup and by set_agent_config, where a value such as null used to make every later run end without a model call. See cancel_agent_run.