Skip to content
tnsaiDocs
GitHub

Migration

TnsAI is pre-1.0, so the public API can shift between minor versions. The current published release is listed on Installation. The framework deliberately does not ship long-lived @Deprecated shims. When a replacement lands, the old surface goes in the same release. That means every upgrade is potentially a small surgical step rather than a sprawling deprecation cleanup, but it also means you have to read the right release notes to know what to change.

Where the migration steps live

From 0.18 on, the Changelog gives every change in one line, and Migrate to 0.18 holds each breaking change in full with what to change for it. For releases before 0.18, each CHANGELOG entry includes a Migration subsection whenever consumers need to do something other than bump the version. The guide for moving from version X to Y is every entry strictly between X (exclusive) and Y (inclusive).

→ Changelog has the full list, newest first.

This page is a roadmap: it points to the entries that contain non-trivial breaking changes so you don't have to read the entire changelog when planning an upgrade.

0.17.0: RAG module extraction

This change ships in 0.17.0 (framework PR #300), the release the installation guide is now pinned to. Add dev.tnsai:tnsai-rag, aligned with the 0.17.0 BOM version. It is a published artifact as of this release.

When upgrading to 0.17.0:

  • Add dev.tnsai:tnsai-rag for the bundled RAG providers and strategies, including applications using @Retrieval or @ChatKnowledge. Align its version with that release's BOM. Keeping only tnsai-intelligence no longer supplies those providers. This also applies to programmatic/core-SPI consumers that discover the bundled provider without using annotations. Merely depending on the core SPI types does not install a provider.
  • Keep tnsai-intelligence if you also use planning, reasoning, FSM or cognitive context features. The Java package com.tnsai.intelligence.rag stays the same, because this is a Maven artifact boundary change, not an import rename.
  • tnsai-server now depends on tnsai-rag directly. Applications that relied on Server to bring cognitive Intelligence features transitively must declare tnsai-intelligence themselves.
  • PDFBox and POI remain optional: applications using those extractors still supply their parser dependencies. Moving the module does not bundle them transitively. PostgreSQL remains a test dependency of the module, not a production JDBC driver supplied to consumers.

0.17.0 has 13 runtime framework modules and 15 child modules: tnsai-bom and build-time tnsai-rewrite are the two non-runtime children. The previous release, 0.16.4, had 12 runtime / 14 child modules.

The source includes the Maven OpenRewrite recipe dev.tnsai.rewrite.UpgradeTnsAI_0_17_0, which adds tnsai-rag:0.17.0 when it finds RAG classes or the @Retrieval / @ChatKnowledge annotations. It does not upgrade the BOM or cover all core-SPI-only, runtime-only or reflection-based use: inspect the result and the application classpath. Do not expect the recipe from the published 0.16.4 tnsai-rewrite artifact.

Source anchors: module dependencies, Server dependencies, recipe.

0.17.0: the other 14 breaking changes

The RAG extraction above is one of 15 documented breaks in this release, the most breaking one since the module layout settled. The other 14 are not narrated here. Each has its own entry, with the exact API and the fix, in the [0.17.0] CHANGELOG section's Migration subsection. What to look for, grouped:

  • Planning re-checks preconditions and goals against real state, not the planner's own simulation. A plan that completed under 0.16.4 can now throw PlanningFailureException with a new Reason.PRECONDITION_STALE (a step refused mid-plan) or Reason.GOAL_UNSATISFIED_AFTER_EXECUTION (a goal the plan promised and the bodies didn't keep). Both new enum constants break an exhaustive switch without a default branch.
  • Condition and postcondition evaluation changed in four ways: an enum compares by its constant name instead of toString(), a float compares at float precision, a quoted literal means a string rather than "any type," and (the one most likely to surface silently) a variable the state does not hold now fails every comparison over it, != and negation included, where 0.16.4 read it all as true. A negated precondition over a field of any role but a multi-role agent's first, such as !busy, can start failing for exactly this reason.
  • Access control now runs on every dispatch path, including SCOP Object-target dispatch (0.16.4 skipped it there) and reads the caller from the active InvocationContext rather than the dispatch context map. See the execution policy guide and the updated Enforcement page. One thing that guide is explicit about and this release does not fix: a @Security placed only on a composed annotation is not detected on any dispatch path (ActionExecutor, plans and message callbacks included), so declare @Security directly. A known, open gap, not something 0.17.0 closes.
  • A plan or message callback whose action declares @ExecutionPolicy or @Security directly is now refused rather than silently running ungated, since neither path went through the access check before. This covers only the bundled planner (PlanExecutor, the bundled PlannerHandle.execute()). A custom PlannerHandle is not checked on its behalf and must refuse its own protected steps.
  • RetrievalConfig gains a 22nd record component (directContextTokens): a pattern that deconstructs the 21 fields of 0.16.4 stops compiling.
  • A role now inherits its superclass's @ActionSpec actions and @RoleSpec.
  • Conversation content logs under its own namespace, com.tnsai.diagnostics.content.*, separate from operational logs.
  • FilesystemLiabilitySink and its default wiring in tnsai-server are removed.
  • Planning's exposed state maps hold an enum's name() instead of toString(). PlanExecutor.extractBeliefs(), BackwardChainingPlanner.extractState(Object) and PlannerHandle.PlanResult.finalState used to render an overridden toString(). A consumer reading or logging those maps now sees the constant name instead. Signatures are unchanged, so the API-compatibility check does not catch this one.
  • A stale planned step is refused rather than silently skipped when a custom Planner declares no goals: an empty goal list used to make every stale step's skip condition vacuously true. Only affects a custom Planner, since neither built-in planner is exposed to it.

Releases that require active migration

Only release entries where ignoring the steps would actually break consumer code are listed here. Patch releases that touch internals only are omitted.

0.18.6: one chat send call, and the principal moves to the invocation

Not additive: some changes fail to compile. Run the dev.tnsai.rewrite.UpgradeTnsAI_0_18_6 recipe first, then work through Migrate to 0.18 → 0.18.6.

0.18.4: a failed action body tells the model why

Nothing fails to compile; the changes are behavior only. See Migrate to 0.18 → 0.18.4.

0.18.3: plain-object dispatch is wired and checked like a role

Nothing fails to compile; the changes are behavior only. See Migrate to 0.18 → 0.18.3.

0.18.2: artifacts move from Maven Central to repo.tnsai.dev

Add the TnsAI repository to your build before you bump the BOM version. Three further changes affect SCOP run files and ClassConfig callers. See Migrate to 0.18 → 0.18.2.

0.18.1: patched dependencies depend on BOM import order

Additive, but its patched dependencies reach your build only if the BOM import order lets them. See Migrate to 0.18 → 0.18.1.

0.18.0: one knowledge annotation, and an action sees only what it names

Run the dev.tnsai.rewrite.UpgradeTnsAI_0_18_0 recipe first, then work through Migrate to 0.18 → 0.18.0.

0.17.5: Redis stores take a RedisClient instead of a JedisPool

TnsAI 0.17.5 removes the JedisPool constructors of RedisIdempotencyStore and RedisCheckpointStore. The pool constructors are gone, with no compatibility overloads. Both stores now take a pooled redis.clients.jedis.RedisClient, with or without a custom key prefix. The Redis server needs nothing new: the checkpoint lookup uses ZRANGE -1 -1 and does not require ZRANGE REV.

  • Find the call sites first: run the dev.tnsai.rewrite.UpgradeTnsAI_0_17_5 recipe from the tnsai-rewrite module before you bump tnsai.version. Your project is still on 0.17.4 at that point, so name the 0.17.5 recipe artifact explicitly:

    mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
      -Drewrite.recipeArtifactCoordinates=dev.tnsai:tnsai-rewrite:0.17.5 \
      -Drewrite.activeRecipes=dev.tnsai.rewrite.UpgradeTnsAI_0_17_5

    After the bump the JedisPool constructors no longer exist, so the recipe has nothing to match and marks nothing. It marks every JedisPool construction of the two stores with a bare /*~~>*/ and does not rewrite it, because the client configuration cannot be translated mechanically. Remove the markers by hand once each call site is migrated.

  • Manual path: replace the JedisPool you passed in with a RedisClient. Keep the same authentication, database, TLS, timeout and pool configuration. The application owns the client and closes it at shutdown, and the stores reuse it and never close it themselves.

  • Raise your own Jedis: redis.clients:jedis is an optional dependency of tnsai-core that your build declares, so TnsAI does not upgrade it for you. Set it to 7.2.0 or later, because RedisClient first appears in Jedis 7.2.0. TnsAI 0.17.5 is built and tested only against 8.0.1. See RedisIdempotencyStore for a sample.

  • See Changelog → 0.17.5 for the full entry (auto-synced from the framework CHANGELOG).

0.17.5: BridgeLLMClient failures report their real type

Nothing fails to compile, but retries on the SCOP HTTP fallback path change. BridgeLLMClient.chat() used to report every failure as ErrorType.UNKNOWN, which is retryable. It now reports the classified type.

  • A RetryPolicy keyed on SERVER_ERROR or MODEL_OVERLOADED now retries 5xx and 429 failures.
  • 401/403 and other 4xx failures are no longer retryable. If you relied on them being retried, review your policy before upgrading.
  • An empty 2xx body, or a 2xx body carrying an error object, stays UNKNOWN and retryable, as before. The bridge itself still sends each request once.
  • See Migrate to 0.18 → Bridge retries follow the error type for the status-to-type table, and Changelog → 0.17.5 for the full entry.

0.17.5: document extraction refuses oversized or relabelled inputs

Nothing fails to compile, but PDF and OOXML extraction in tnsai-rag now refuses inputs past its page, glyph, node and inflation caps instead of loading them. Through ingestion a refused file is an EXTRACTION failure, and a direct extractor call throws IllegalArgumentException.

  • Inputs that used to ingest can now be refused: PDFs past the page or glyph caps, OOXML packages past the entry, node or inflation caps, spreadsheets beyond about 3,100 rows × 50 columns, a legacy .xls under an XLSX label and default-password-encrypted XLSX.
  • If your corpus holds large spreadsheets or long PDFs, run an ingestion pass on 0.17.5 and check the failures before you roll it out.
  • The caps do not bound memory against a crafted PDF: stream decompression size is not checked yet.
  • See Formats → PDF and OOXML caps for the full list, and Changelog → 0.17.5 for the full entry.

0.17.5: SCOP routine logs move to DEBUG

Nothing fails to compile, but routine INFO records in SCOPBridge, LLMDispatcher and LLMClientFactory now log at DEBUG. These include action execution, LLM resolution, client creation and request/response summaries.

  • If dashboards or alerts match those lines, they stop firing on 0.17.5.
  • WARN and ERROR records, the HTTP-fallback record and the source-override and external-routing records stay at INFO.
  • Do not raise LLMDispatcher or any logger above it to DEBUG: it logs the last four characters of the API key. See SCOP Bridge → Log levels for the loggers that are safe to raise.

Maven groupId io.github.tansuasici → dev.tnsai

Coordinates move to the tnsai.dev namespace. Artifact IDs stay. Java packages (com.tnsai.*) stay. Versions already on Maven Central under io.github.tansuasici (through 0.15.0) keep resolving as real artifacts. 0.15.1 is the handover: on the old groupId it is a relocation POM, so bumping to it follows to dev.tnsai with a Maven warning. Bumping to 0.15.0 instead resolves a real artifact and stays on the retired coordinate.

OpenRewrite recipe IDs io.github.tansuasici.rewrite.UpgradeTnsAI_* do not change.

  • Automated path: mvn rewrite:run with dev.tnsai.rewrite.ChangeMavenGroupId (the tnsai-rewrite module).
  • Manual path: replace <groupId>io.github.tansuasici</groupId> (and the Gradle io.github.tansuasici: prefix) with dev.tnsai.

0.16.0: the KnowledgeBase chat bridge is removed

AgentBuilder.knowledgeBase(...), AgentBuilder.knowledgeBaseTopK(...), Agent.setKnowledgeBase/getKnowledgeBase, Agent.setKnowledgeBaseTopK/getKnowledgeBaseTopK, the matching AgentOrchestrator and AgentChatOrchestrator members, ChatKnowledgeBinding.snapshot(...) and ChatKnowledgeBinding.SNAPSHOT_SOURCE are removed. The KnowledgeBase type stays. To ground chat on a corpus the application owns, declare a MEMORY entry and bind the corpus with ChatKnowledgeBinding.live(...), as RAG → Application-supplied live sources shows.

0.14.0: @Param deleted

TnsAI 0.14.0 removes @com.tnsai.annotations.Param. The annotation shipped in 0.13.0 and never had a runtime reader. ActionDiscovery.discoverParameters builds every ParamSpec from Parameter.getName() under the -parameters compiler flag, so an action's LLM-visible name has always been the Java parameter name. A mismatch with @Param(name = "q") String query silently resolved to query.

@ToolParam and @LLMParam are different, live annotations and are untouched.

  • Automated path: mvn rewrite:run with io.github.tansuasici.rewrite.UpgradeTnsAI_0_14_0 (the tnsai-rewrite module). It deletes @Param usages only.
  • Manual path: delete every @Param usage. Parameter names and types continue to come from the method signature, so nothing else changes. -parameters is already set by the parent POM.
  • See Changelog → 0.14.0 for the full entry (auto-synced from the framework CHANGELOG).

This shipped in 0.14.0.

0.13.0: declarative RAG starts taking effect, and @KnowledgeSource narrows to ingestion

The largest breaking release so far (21 entries), and most of it is behavioural: @Retrieval members that were accepted and then silently discarded are now enforced. Your code still compiles. It just starts doing what the declaration always said.

Read your @Retrieval declarations before upgrading. rerank, cache/cacheTTL, onFailure, queryExpansion, deduplicate, contextWindow/includeSpec and the GRAPH/HIERARCHICAL/TEMPORAL strategies were inert, and they now change retrieval behaviour. Two specifics worth knowing:

  • cache defaults to true with a 300s TTL, so caching switches on for every existing @Retrieval. Corpus changes are picked up on the next dispatch only if you call RoleRagBinding.invalidate(Class). Otherwise entries clear by TTL or LRU. Set cache = false to keep the previous behaviour.
  • rerank, queryExpansion and strategy = GRAPH resolve through SPI seams with no bundled provider. Enabling them without an add-on module now throws at dispatch instead of being ignored.

@KnowledgeSource is now ingestion-only: 17 members down to 6 (name, type, path, connection, query, enabled). The 11 removed members (provider, index, topK, minSimilarity, embeddingModel, dimensions, namespace, filter, cache, cacheTTL, priority) never had a reader, so deleting them changes nothing at runtime. Retrieval tuning belongs to @Retrieval, which removes the old ambiguity where both annotations declared topK/cache/cacheTTL with no rule for which wins.

KnowledgeType drops to FILE, URL, DATABASE, MEMORY, and the default moves from VECTOR_DB to FILE. VECTOR_DB, WEB_SEARCH and CACHE are removed with no replacement: a remote index is queried per request, which the ingest-time SourceLoader seam cannot express. Those belong behind RetrievalEngineProvider.

  • Automated path: mvn rewrite:run with io.github.tansuasici.rewrite.UpgradeTnsAI_0_13_0 (the tnsai-rewrite module). 73 entries: 61 type moves from the com.tnsai.quality.* and com.tnsai.agents.groups.* consolidations, 11 attribute removals for the narrowed @KnowledgeSource, and one method rename (RoleRagBinding.evictForTesting → invalidate). Activate the recipe for the version you are moving to.
  • What the recipe cannot do: the behavioural changes above. A recipe could auto-insert cache = false and pin you to the old behaviour, which is the opposite of what you want to decide. It also does not migrate the removed KnowledgeType values (no target to rewrite to).
  • If you implement SourceLoader: load(...) and the optional metadata(...) hook now take com.tnsai.rag.KnowledgeSourceConfig instead of the @KnowledgeSource annotation. KnowledgeSourceConfig's six components are name-for-name and type-for-type identical to the narrowed annotation's six members, so a body that reads name / type / path / connection / query / enabled compiles unchanged and only the parameter type in the signature needs editing. If your loader read one of the 11 removed members (topK, provider, embeddingModel and the rest), that is a separate break to resolve. In practice a loader reads path / connection / query, not retrieval knobs. Not yet covered by the recipe.
  • See Changelog → 0.13.0 → Migration for the full entry.

0.12.0: name collisions + 23 orphan annotations removed

Two simple-name collisions in core, plus the 0.11.1-era orphan-annotation and @LLM removals bundled into the first release after 0.11.0.

  • The com.tnsai.identity.AgentSpec record is renamed to AgentDescriptor. (The @AgentSpec annotation is a different type and is unchanged.)

  • The @com.tnsai.roles.annotations.RoleIdentity annotation is renamed to @RoleDeclaration. (The runtime com.tnsai.models.role.RoleIdentity identity class is unchanged.)

  • Combined guide: 0.11 → 0.12: removed list, newly active wiring, Config records, recipe.

  • Automated path: mvn rewrite:run with io.github.tansuasici.rewrite.UpgradeTnsAI_0_12_0 (the two renames only).

  • See Changelog → 0.12.0 → Migration for the full entry.

0.7.0: Legacy Tool interface removed

Function-shape ToolMethod is now the only tool abstraction in the runtime. If your code still does class MyTool implements Tool, this is the breaking change to plan for.

  • Migration paths: convert to a POJO with @Tool-annotated methods (recommended), or build a DynamicToolMethod directly.
  • See Changelog → 0.7.0 → Migration for the exact replacement snippets.

0.6.0: Tool interface metadata methods + ChatRequest.tools type changed

Removed the 12 metadata-discovery default methods (getCategory, getUsageExamples, etc.) and changed ChatRequest.getTools() from List<ToolDefinition> to List<Map<String, Object>> (JSON-Schema fragments).

  • Custom-tool-catalog callers that built ChatRequest directly must switch to the new map shape.
  • Direct Tool implementers can simply drop overrides of the removed metadata methods, since they're no longer called.
  • See Changelog → 0.6.0 → Migration for before/after snippets.

0.5.7: Legacy tool catalog removed from tnsai-tools

The 130+ extends AbstractTool classes were replaced by ~60 typed POJOs exposing ~190 @Tool-annotated methods. Most consumers already used AgentBuilder.toolPojos(...) and don't need to touch anything.

0.5.0: AgentState.READY renamed to RUNNING

Single-token rename. No deprecation shim: search-and-replace AgentState.READY → AgentState.RUNNING in your code.

Other 0.5.0 behaviour changes that can surface as silent breaks if your code depended on them:

  • Agent.chat() now throws IllegalStateException after the agent has been stopped (was silent / undefined before).
  • BridgeLLMClient.streamChat() now throws LLMCapabilityException (was returning a single-element synthetic stream).
  • BridgeLLMClient.chat() now throws typed LLMException (was raw RuntimeException).

See Changelog → 0.5.0 → Migration notes.

0.4.0: Monorepo migration

Not a code break, but a coordinate break: the 11 separately-published modules consolidated under a single tnsai-bom. The recommended dependency shape is now to import the BOM once and depend on individual modules without <version>.

Optional refactors

These are not release breaks: the old shape still compiles and runs. They move code to the pattern the current pages teach.

From return null; bodies to @Capability

The @Capability pattern ships since 0.4.0 (Changelog → 0.4.0). A dispatched @ActionSpec method (LLM / MCP_TOOL / WEB_SERVICE) with a return null; body still works, because ActionExecutor never runs that body. But every role repeats the annotation, and a direct call returns null. To move such methods onto a capability:

  1. For each dispatched @ActionSpec method whose body is return null;, extract it to a @Capability interface.
  2. Give the interface method a default { throw Actions.dispatchedByFramework(); } body.
  3. On the role class, remove the original method entirely and add implements YourCapability.
  4. Methods that have a meaningful body (typically because they declare an ActionResult parameter for post-processing) stay on the role class unchanged.

Before

@RoleDeclaration(name = "Editor", goal = "Produce clean articles")
public class EditorRole extends Role {

    @ActionSpec(type = ActionType.LLM, description = "Summarise the text",
                llmSystemPrompt = "You are concise.", llmTemperature = 0.2f)
    public String summarize(String text) {
        return null;
    }

    @ActionSpec(type = ActionType.LLM, description = "Translate to the target language")
    public String translate(String text, String targetLanguage) {
        return null;
    }
}

After

@Capability
public interface Summarizer {
    @ActionSpec(type = ActionType.LLM, description = "Summarise the text",
                llmSystemPrompt = "You are concise.", llmTemperature = 0.2f)
    default String summarize(String text) {
        throw Actions.dispatchedByFramework();
    }
}

@Capability
public interface Translator {
    @ActionSpec(type = ActionType.LLM, description = "Translate to the target language")
    default String translate(String text, String targetLanguage) {
        throw Actions.dispatchedByFramework();
    }
}

@RoleDeclaration(name = "Editor", goal = "Produce clean articles")
public class EditorRole extends Role implements Summarizer, Translator {
    // No capability bodies.
}

Composition, override and validation rules: Capabilities.

When in doubt

If you're skipping multiple versions, walk forward one minor at a time and read the migration subsection of each entry. The deletions are intentionally aggressive: sleeping on the changelog for six months and trying to jump from 0.5 to 0.10 in one go is harder than four 30-minute upgrade sessions spread across releases.

For changes that aren't covered above but caused you grief, open a discussion on the framework repo, and the entry will land here in the next release.