Skip to content

0.4.0

Choose a tag to compare

@jwcarman jwcarman released this 03 Oct 23:20

Breaking changes

  • ApprovalRequest.callKey() is replaced by idempotencyKey(), a typed
    IdempotencyKey wrapping a UUID. callKey() was turn/callId: unique only
    within one agent, and not even there when a model repeats a call id within
    a turn. The new key is made once per call when the model's request is
    recorded, stays the same across retries and restarts, and is unique across
    every agent. ToolCallRequest.idempotencyKey() carries the same key, so a
    tool can deduplicate on it and find its own approval. Stored events and
    effects change shape; recreate the database.
  • DirectHarness.ask refuses to run inside a caller's transaction. It
    throws IllegalStateException before writing anything. A turn makes a model
    call, and a transaction should not stay open across a network call; on JDBC
    the call failed anyway, with "no event at 1 for agent ...". Suspend the
    transaction for the call (PROPAGATION_NOT_SUPPORTED). The queued door's
    tell still joins the caller's transaction. nessy-engine now depends on
    spring-tx for this check, as it already does on spring-context.
  • Anthropic prompt caching is on by default. With
    anthropic.cache_control.ttl absent, a request now carries FIVE_MINUTES
    cache markers; before, it carried none. A one-off request still carries
    none, and neither does a request that asks for an answer
    (ToolChoice.Answer), which is sent without its tools.
  • Narration.ActionsRequested carries one Call per requested call
    (callId, toolName, action) in place of tool names alone, so a watcher
    can join each later call event, which names only the id, to its tool.
  • Turn has no tokens field. It was always 0 and nothing read it.
    Usage is reported as a whole, through UsageReports.

Added

  • AnthropicCacheTtl.DISABLED turns Anthropic prompt caching off. Set it on
    the provider, or on one agent type to override a provider that caches.
  • UsageReports reads an agent's usage over its whole history, by model:
    of(type, id) returns one ModelUsage per model, never added across
    models. It is projected from the agent's stored events, so it counts every
    inference, retries and failures included, and is fit for cost accounting,
    which narration is not. The starter offers a UsageReports bean.

Changed

  • The OPA adapter and the policy example are tested against OPA 1.21.1. Every
    response shape the adapter relies on is the same as on 0.68.0.

Fixed

  • The watchman example no longer hides a repeated call id. Its approvals
    board was keyed on the model's call id, so once an agent's call_1 was
    answered, a later call_1 from the same agent was never shown and timed
    out. The board is now keyed on the call's IdempotencyKey, and its
    approve and deny URLs carry that key. Drop watchman_pending_approval (or
    recreate the watchman's database) before running it.
  • Gemini no longer returns half a reply as the answer. A stream that
    stopped before its finish reason was read as a complete answer. It is now a
    fault.
  • A stream cut short is Failure.Unknown, not Permanent, on Anthropic,
    OpenAI Chat, OpenAI Responses, Bedrock and Gemini. A server that closes the
    stream mid-answer is a dropped connection, so a retry policy can now try
    again. A stream that delivers no answer at all is still Permanent.
  • A dropped connection to the model can be retried. Every adapter
    reports a dropped connection as Failure.Unknown, and the dispatcher sent
    only Failure.Transient to the retry policy, so a dropped connection ended
    the turn whatever the policy said. An Unknown model failure now reaches
    the policy too: a model call that runs twice changes nothing but the bill.
    The default policy is still Never; set one with
    InferenceConfig.retryPolicy for the queued door to try again.
  • The Boot starter closes every inference provider's client at shutdown.
    Each provider bean is an ObservedInferenceProvider, which was not
    AutoCloseable, so the container never closed the adapter inside it and
    the adapter's HTTP client leaked. The wrapper now closes what it wraps, as
    ObservedEmbedder already did.
  • A generated answer type works on Anthropic. Anthropic refuses an answer
    schema with an object that does not say "additionalProperties": false,
    and the schema generator writes none, so every generated answer type failed
    with a 400. The adapter now closes every object in the answer schema, and
    leaves one that already says what it allows as written.
  • The queued dispatcher can no longer stop for good. A claim that returned
    more rows than its batch made the dispatcher release a negative count of
    permits; the semaphore threw and the agent type never dispatched again. The
    dispatcher now runs at most its batch and puts any surplus straight back on
    the queue, and the JDBC claim picks and locks its rows in a MATERIALIZED
    CTE, evaluated exactly once.

Known issues

  • LM Studio returns an empty answer when a reasoning model is asked for a structured answer (lmstudio-bug-tracker #1773). Set openai.reasoning.effort: none on the provider; see the providers guide.