Migrate 0.11 → 0.12
This is the combined 0.11.0 → 0.12.0 path. Source of truth is the framework CHANGELOG [0.12.0] - 2026-06-04 (published via Changelog). The current lockstep line is 0.14.0 — after this page, read 0.13.0 and 0.14.0.
Historical Linear ids TNS-541 … TNS-597 belong to the retired
linear.app/tnsai tracker. They are provenance, not clickable issues.
TL;DR
- If you only use
@RoleSpec/@ActionSpec/@Tool: you compile after the bump. Nothing in those three declarations was renamed or removed in 0.12.0. - If you used the
com.tnsai.identity.AgentSpecrecord or@RoleIdentity: rename them (recipe below).@AgentSpecthe annotation is unchanged. - If you applied any of the 23 removed annotations: delete the annotation. They were unwired orphans — removing them does not change runtime behaviour.
@Norm/@Normswere not newly enforced in 0.12.0. They stayed scaffold (TNS-591). Do not expect deontic checks after this bump.@Idempotentwas not removed. It stays a live tool annotation.
Automated path
mvn rewrite:run -Drewrite.activeRecipes=io.github.tansuasici.rewrite.UpgradeTnsAI_0_12_0The tnsai-rewrite recipe on 0.12.0 applies the two simple-name
renames. It does not delete orphan annotations (there is nothing to
rewrite them to). Activate the recipe for the version you are moving
to.
Four naming collisions
| 0.11 name | 0.12 name | Unchanged twin |
|---|---|---|
com.tnsai.identity.AgentSpec record | com.tnsai.identity.AgentDescriptor | @com.tnsai.annotations.AgentSpec |
@com.tnsai.roles.annotations.RoleIdentity | @RoleDeclaration | com.tnsai.models.role.RoleIdentity class |
RoleSpecExtractor.LLMSpec nested record | RoleSpecExtractor.LLMExportSpec | @LLMSpec annotation |
TYPE-level @LLM | removed — use @RoleSpec(llm=@LLMSpec(...)) | nested @LLMSpec (the live config) |
// 0.11
import com.tnsai.identity.AgentSpec;
import com.tnsai.roles.annotations.RoleIdentity;
AgentSpec descriptor = ...;
@RoleIdentity(name = "analyst")
public class AnalystRole extends Role { }
@LLM(provider = "openai", model = "gpt-4o")
public class LegacyRole extends Role { }
// 0.12
import com.tnsai.identity.AgentDescriptor;
import com.tnsai.roles.annotations.RoleDeclaration;
AgentDescriptor descriptor = ...;
@RoleDeclaration(name = "analyst")
public class AnalystRole extends Role { }
@RoleSpec(llm = @LLMSpec(provider = Provider.OPENAI, model = "gpt-4o"))
public class LegacyRole extends Role { }RoleSpecExtractor.hasLLMAnnotation(...) is hasLLMConfig(...). Role
export now reads @RoleSpec.llm() (@LLMSpec), including endpoint →
baseUrl.
Removed annotations (23)
Every row is a pure orphan on 0.11: no reflection reader, no behaviour. Migration is “delete the annotation.” Canonical counterparts (if any) were always the wired surface.
TNS-590 (9)
| Removed | Replacement |
|---|---|
@ContextCompaction | none — delete |
@SlashCommand | none — delete |
@WorkspaceSpec | none — delete |
@Property | none — delete |
@ConfigProperty | none — delete |
@Trigger | none — delete |
@Delegate | none — delete |
@Sanitize | none — delete |
@ContentFilter | none — delete |
TNS-566 / 576 / 578 / 589 (9)
| Removed | Replacement if you still need the idea |
|---|---|
@RateLimited | @Resilience retry/timeout (rate-limit itself stayed unwired) |
@OnConnect / @OnDisconnect / @OnMessage | Channel interface callbacks |
@FSMState / @FSMTransition / @FSMStates / @FSMTransitions | StateMachine.builder() in intelligence |
@RequiresPairing | none in 0.12 — it never gated dispatch. Do not invent @Security(requiredPermissions=...) as a drop-in |
TNS-569 / 573 / 577 (4)
| Removed | Wired counterpart (never read the annotation) |
|---|---|
@SystemPrompt | SystemPromptBuilder / @RoleSpec.description |
@ChannelSpec | Channel SPI |
@Pipeline / @PipelineStep | PipelineBuilder in tnsai-coordination |
TNS-642 (1)
| Removed | Replacement |
|---|---|
@LLM (TYPE-level, export-only) | @RoleSpec(llm=@LLMSpec(provider=..., model=...)) |
@NormType was kept as the value enum for the still-unwired
@Norm / @Norms pair.
Newly active in 0.12 (behaviour you may notice)
These compiled on 0.11 and start doing something on 0.12.
| Surface | What changed |
|---|---|
@AuditLog | SecurityEnforcer.audit now emits a named AuditLogger entry (independent of @Security; honours mask flags). |
@LLMSpec.topP | Role.initializeLLMFromAnnotation() passes topP through. 1.0f still maps to null. Other @LLMSpec fields (frequencyPenalty, presencePenalty, timeoutMs, endpoint, apiKeyEnv) stay inert. |
@AgentSpec.maxContextTokens | > 0 prunes conversation history. Precedence: template > this shortcut > @MemorySpec.maxContextTokens. |
@AgentSpec.toolCallFilter | Instantiated via public no-arg ctor (AGENT-V009). AllowAllToolFilter is the “not set” sentinel. Builder / setToolCallFilter still wins. |
MCP destructiveHint / idempotentHint | In-process McpToolBridge maps them onto requiresConfirmation / idempotent. WebSocket McpProxyTool is unchanged. |
| Dynamic tools + AGENT-V006 | DynamicToolMethod can require confirmation; the builder scan no longer skips MCP/dynamic tools. |
@Contract JEXL | AgentBuilder.build() warns on malformed clauses (AGENT-V013). Suppress with .relaxValidation("AGENT-V013"). |
Programmatic Config records
Builder-built roles and agents can now carry the same gates the annotations carry. Records introduced or completed by 0.11.0–0.12.0:
| Record | Twin annotation | Since |
|---|---|---|
DIDConfig | @DIDSpec | 0.11.0 |
GroupMemberSpec | @GroupMember | 0.11.0 |
ActionConfig | @ActionSpec (LOCAL) | 0.11.0 |
InputGuardrailConfig | @InputGuardrail (enforced subset) | 0.12.0 |
OutputGuardrailConfig | @OutputGuardrail (enforced subset) | 0.12.0 |
MemoryConfig | @MemorySpec | 0.12.0 |
ContractSpec | @Contract | 0.12.0 |
ResilienceConfig via RoleBuilder.resilience(...) | @Resilience (retry + timeout only) | 0.12.0 |
AuthorityScope.permanent() | long-lived beans (no 3650-day hack) | 0.12.0 |
DynamicToolMethod.builder(name) | @Tool(requiresConfirmation, keywords) | 0.12.0 |
Guardrail resolution: method annotation > per-action config >
RoleBuilder config > class annotation. Scaffold fields on the
annotations (maxRetries on output, CB/rate-limit/bulkhead on
resilience) stay inert.
ContractSpec is a record, not an annotation. That name is a
documented outlier of the @*Spec house rule; the rename is
TAN-2938,
not part of this upgrade.
Sona
No Sona consumer PR is recorded against 0.12.0. If a Sona checkout still
imports com.tnsai.identity.AgentSpec or @RoleIdentity, apply the same
two renames. Placeholder until a Sona bump lands.
Issue chain (retired tracker)
The annotation re-verification epic that produced this release lived as
TNS-541 … TNS-597 on linear.app/tnsai. The catalog follow-up is
TAN-2802.
This page is the consumer migration surface of that epic, not the
canonical inventory.
See also
- Migration index — 0.13.0 and earlier breaks
- Changelog 0.12.0
- Annotation runtime status (if present on the site) — wiring today, not 0.12 history