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-ragfor the bundled RAG providers and strategies, including applications using@Retrievalor@ChatKnowledge. Align its version with that release's BOM. Keeping onlytnsai-intelligenceno 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-intelligenceif you also use planning, reasoning, FSM or cognitive context features. The Java packagecom.tnsai.intelligence.ragstays the same, because this is a Maven artifact boundary change, not an import rename. tnsai-servernow depends ontnsai-ragdirectly. Applications that relied on Server to bring cognitive Intelligence features transitively must declaretnsai-intelligencethemselves.- 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
PlanningFailureExceptionwith a newReason.PRECONDITION_STALE(a step refused mid-plan) orReason.GOAL_UNSATISFIED_AFTER_EXECUTION(a goal the plan promised and the bodies didn't keep). Both new enum constants break an exhaustiveswitchwithout adefaultbranch. - Condition and postcondition evaluation changed in four ways: an enum
compares by its constant name instead of
toString(), afloatcompares atfloatprecision, 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
InvocationContextrather 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@Securityplaced only on a composed annotation is not detected on any dispatch path (ActionExecutor, plans and message callbacks included), so declare@Securitydirectly. A known, open gap, not something 0.17.0 closes. - A plan or message callback whose action declares
@ExecutionPolicyor@Securitydirectly 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 bundledPlannerHandle.execute()). A customPlannerHandleis not checked on its behalf and must refuse its own protected steps. RetrievalConfiggains 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
@ActionSpecactions and@RoleSpec. - Conversation content logs under its own namespace,
com.tnsai.diagnostics.content.*, separate from operational logs. FilesystemLiabilitySinkand its default wiring intnsai-serverare removed.- Planning's exposed state maps hold an enum's
name()instead oftoString().PlanExecutor.extractBeliefs(),BackwardChainingPlanner.extractState(Object)andPlannerHandle.PlanResult.finalStateused to render an overriddentoString(). 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
Plannerdeclares no goals: an empty goal list used to make every stale step's skip condition vacuously true. Only affects a customPlanner, 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_5recipe from thetnsai-rewritemodule before you bumptnsai.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_5After the bump the
JedisPoolconstructors no longer exist, so the recipe has nothing to match and marks nothing. It marks everyJedisPoolconstruction 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
JedisPoolyou passed in with aRedisClient. 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:jedisis an optional dependency oftnsai-corethat your build declares, so TnsAI does not upgrade it for you. Set it to 7.2.0 or later, becauseRedisClientfirst appears in Jedis 7.2.0. TnsAI 0.17.5 is built and tested only against8.0.1. SeeRedisIdempotencyStorefor 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
RetryPolicykeyed onSERVER_ERRORorMODEL_OVERLOADEDnow 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
errorobject, staysUNKNOWNand 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
.xlsunder 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.
WARNandERRORrecords, the HTTP-fallback record and the source-override and external-routing records stay atINFO.- Do not raise
LLMDispatcheror any logger above it toDEBUG: 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:runwithdev.tnsai.rewrite.ChangeMavenGroupId(thetnsai-rewritemodule). - Manual path: replace
<groupId>io.github.tansuasici</groupId>(and the Gradleio.github.tansuasici:prefix) withdev.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:runwithio.github.tansuasici.rewrite.UpgradeTnsAI_0_14_0(thetnsai-rewritemodule). It deletes@Paramusages only. - Manual path: delete every
@Paramusage. Parameter names and types continue to come from the method signature, so nothing else changes.-parametersis 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:
cachedefaults totruewith a 300s TTL, so caching switches on for every existing@Retrieval. Corpus changes are picked up on the next dispatch only if you callRoleRagBinding.invalidate(Class). Otherwise entries clear by TTL or LRU. Setcache = falseto keep the previous behaviour.rerank,queryExpansionandstrategy = GRAPHresolve 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:runwithio.github.tansuasici.rewrite.UpgradeTnsAI_0_13_0(thetnsai-rewritemodule). 73 entries: 61 type moves from thecom.tnsai.quality.*andcom.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 = falseand pin you to the old behaviour, which is the opposite of what you want to decide. It also does not migrate the removedKnowledgeTypevalues (no target to rewrite to). - If you implement
SourceLoader:load(...)and the optionalmetadata(...)hook now takecom.tnsai.rag.KnowledgeSourceConfiginstead of the@KnowledgeSourceannotation.KnowledgeSourceConfig's six components are name-for-name and type-for-type identical to the narrowed annotation's six members, so a body that readsname/type/path/connection/query/enabledcompiles unchanged and only the parameter type in the signature needs editing. If your loader read one of the 11 removed members (topK,provider,embeddingModeland the rest), that is a separate break to resolve. In practice a loader readspath/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.AgentSpecrecord is renamed toAgentDescriptor. (The@AgentSpecannotation is a different type and is unchanged.) -
The
@com.tnsai.roles.annotations.RoleIdentityannotation is renamed to@RoleDeclaration. (The runtimecom.tnsai.models.role.RoleIdentityidentity class is unchanged.) -
Combined guide: 0.11 → 0.12: removed list, newly active wiring, Config records, recipe.
-
Automated path:
mvn rewrite:runwithio.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 aDynamicToolMethoddirectly. - 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
ChatRequestdirectly must switch to the new map shape. - Direct
Toolimplementers 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.
- Consumers still using
AgentBuilder.tool(Tool)keep working through the compatibility surface in this release. - See Changelog → 0.5.7 → Consumer migration.
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 throwsIllegalStateExceptionafter the agent has been stopped (was silent / undefined before).BridgeLLMClient.streamChat()now throwsLLMCapabilityException(was returning a single-element synthetic stream).BridgeLLMClient.chat()now throws typedLLMException(was rawRuntimeException).
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>.
- See Changelog → 0.4.0 → For Consumers for the new dependency snippet.
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:
- For each dispatched
@ActionSpecmethod whose body isreturn null;, extract it to a@Capabilityinterface. - Give the interface method a
default { throw Actions.dispatchedByFramework(); }body. - On the role class, remove the original method entirely and add
implements YourCapability. - Methods that have a meaningful body (typically because they declare an
ActionResultparameter 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.