---
title: Migrate 0.11 → 0.12
description: Combined consumer guide for the 0.12.0 annotation-cleanup and name-collision release.
---

# 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](changelog.md#0120--2026-06-04)). The current lockstep line is
**0.14.0** — after this page, read [0.13.0](migration.md#0130--declarative-rag-starts-taking-effect-and-knowledgesource-narrows-to-ingestion) and [0.14.0](migration.md#0140--param-deleted).

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.AgentSpec` record or
  `@RoleIdentity`:** rename them (recipe below). `@AgentSpec` the
  *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` / `@Norms` were not newly enforced in 0.12.0.** They stayed
  scaffold (`TNS-591`). Do not expect deontic checks after this bump.
- **`@Idempotent` was not removed.** It stays a live tool annotation.

## Automated path

```bash
mvn rewrite:run -Drewrite.activeRecipes=io.github.tansuasici.rewrite.UpgradeTnsAI_0_12_0
```

The `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) |

```java
// 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](https://linear.app/tansuasici-workspace-1/issue/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](https://linear.app/tansuasici-workspace-1/issue/TAN-2802).
This page is the consumer migration surface of that epic, not the
canonical inventory.

## See also

- [Migration index](migration.md) — 0.13.0 and earlier breaks
- [Changelog 0.12.0](changelog.md#0120--2026-06-04)
- [Annotation runtime status](../reference/annotations/runtime-status.md)
  (if present on the site) — wiring today, not 0.12 history
