# Annotation Runtime Status

A reference of the annotations TnsAI exposes in `com.tnsai.annotations`, grouped by area,
with each one's **runtime status** so you know what actually takes effect.

This page is the published runtime-status catalog (wired / nested-wired / partial /
scaffold). For field tables and usage examples, see the
[Semantic Annotation Framework](catalog.md).

TnsAI is annotation-driven: you describe an agent, role, action, or tool declaratively and
the framework wires the behaviour. A handful of annotations are still being wired or are
on their way out. This page tells you which is which, so you don't reach for one that
silently does nothing.

## Status legend

| Status | What it means for you |
|---|---|
| **Wired** | The runtime honours the annotation and all of its advertised fields. Safe to use. |
| **Nested-wired** | Honoured, but only valid *inside* a parent annotation (e.g. `@LLMSpec` inside `@RoleSpec(llm = …)`), not on its own. |
| **Partial** | Honoured, but only a subset of fields takes effect today, and the rest are no-ops until a later release. Notes call out which. |
| **Scaffold** | Declared but not yet wired, so using it has no runtime effect yet. Planned. |

> Annotations that used to exist but were removed are listed under
> [Recently consolidated](#recently-consolidated) with their replacements. See
> the [Changelog](../help/changelog.md) for the exact release and migration notes.

## Core

The annotations almost every agent uses.

### `@RoleSpec`: Wired
Declares a role: identity, goals, domains, and (nested) LLM/memory config.

```java
@RoleSpec(
    name = "Researcher",
    description = "Answers questions with citations from a local corpus.",
    llm = @LLMSpec(provider = LLMSpec.Provider.OPENAI, model = "gpt-4o-mini", temperature = 0.2f))
public class ResearchRole extends Role { /* @ActionSpec methods … */ }
```

### `@AgentSpec`: Partial

Type-level agent metadata and partial declarative configuration. The runtime
consumes members including `roles` (as a fallback), `maxContextTokens`, and
`toolCallFilter`, but it does not consume the nested `llm` member. There is no
`name` (derived from the class) and no `role` string.

<!-- java-contract: src/main/java/com/example/tnsai/docs/AnnotatedAgentExample.java -->
```java
package com.example.tnsai.docs;

import com.tnsai.accountability.AuthorityScope;
import com.tnsai.actions.ActionExecutor;
import com.tnsai.agents.Agent;
import com.tnsai.annotations.AgentSpec;
import com.tnsai.identity.AgentDescriptor;
import com.tnsai.identity.AgentPrincipal;
import com.tnsai.identity.LocalIdentityProvider;
import com.tnsai.llm.LLMClient;
import com.tnsai.llm.providers.AnthropicClient;
import com.tnsai.roles.Role;

import java.time.Duration;
import java.util.List;

@AgentSpec(description = "A helpful research assistant")
public final class AnnotatedAgentExample extends Agent {

    public AnnotatedAgentExample(AgentPrincipal principal, AuthorityScope scope) {
        super("annotated-agent", null, null, wired(principal, scope));
    }

    private static ActionExecutor wired(AgentPrincipal principal, AuthorityScope scope) {
        ActionExecutor executor = new ActionExecutor();
        executor.setPrincipal(principal);
        executor.setDefaultScope(scope);
        return executor;
    }

    @Override
    protected LLMClient getLLM() {
        return new AnthropicClient("claude-sonnet-4-20250514");
    }

    @Override
    protected List<Role> getRoles() {
        return List.of(Role.create(QuickstartBuilderExample.AssistantRole.class));
    }

    public static void main(String[] args) {
        var principal = new LocalIdentityProvider().issue(
                AgentDescriptor.builder()
                        .agentClass(AnnotatedAgentExample.class.getName())
                        .systemPrompt("You are a helpful research assistant.")
                        .toolNames(List.of("greet"))
                        .model("anthropic:claude-sonnet-4-20250514")
                        .build());
        var agent = new AnnotatedAgentExample(
                principal, AuthorityScope.unrestricted(Duration.ofHours(1)));
        agent.start();
        String answer = agent.chat("What is quantum computing?");
        System.out.println(answer);
    }
}
```

Direct subclasses must implement the abstract `getRoles()` method. A non-empty
result takes precedence over `@AgentSpec.roles`, and an empty result allows the
annotation's public-no-arg role classes to supply the fallback. Provide the
agent-level LLM through `getLLM()` or `AgentBuilder.llm(...)`.

`toolCallFilter` is `Class<? extends ToolCallFilter>`. The shipped default is
`AllowAllToolFilter`. That class is the "not set" sentinel, not an allow-list.
There is no `AllowlistFilter` / `AllowlistToolFilter` type. A real filter is
your own `ToolCallFilter` with a public no-arg constructor.

### `@ActionSpec`: Wired
Marks a method as an agent action/tool, with type, description, and behavioural constraints.

```java
@ActionSpec(type = ActionType.LOCAL, description = "Look up an order",
            mustAlways = {"validate the order id"})
public Order lookup(String orderId) { … }
```

### `@State`: Wired
Marks a **planner** field on a `Role`. The planner reads those fields as a
`Map` to evaluate `@Goal` conditions and action pre/postconditions. This is
not `com.tnsai.models.agent.Belief` and is not BDI extraction.

### `@Contract`: Wired
Design-by-Contract pre/postconditions and invariants (JEXL), enforced around the action.
See [Contracts](../agents/contracts.md).

## Tools

### `@Tool` / `@ToolParam`: Wired
Expose a POJO method (and its parameters) to the LLM as a callable tool. `@Tool` carries
`requiresConfirmation` and `keywords`, which the agent's approval gate (AGENT-V006) reads.

```java
public final class PaymentTools {
    @Tool(description = "Send a wire transfer", requiresConfirmation = true)
    public String wire(@ToolParam("recipient") String to, @ToolParam("amount") double amount) { … }
}
```

TnsAI 0.14.0 (`TnsAI@a070d674`, TAN-3411) fails registration when a
parameter has no `@ToolParam(name)` and javac emitted `argN`. The
positional `@ToolParam("recipient")` form above is already safe.
`timeoutMs` ships in `0.14.1` as an eighth `@Tool` member
(`@since 0.14.0`, [PR #164](https://github.com/TnsAI-Framework/TnsAI/pull/164)
/ [TAN-2958](https://linear.app/tansuasici-workspace-1/issue/TAN-2958)).
A positive value is one
wall-clock budget for the full POJO dispatch, including idempotency work, and
`0` keeps the enclosing agent policy. Expiry is retryable
`ToolTimeoutException`. Interruption is best-effort
([TAN-4387](https://linear.app/tansuasici-workspace-1/issue/TAN-4387)).
Dynamic tools and MCP server deadlines are out of scope. See
[Custom Tools](../tools/custom.md#per-tool-deadlines).

### `@ToolExample`: Nested-wired
Few-shot examples for a tool, read from `@ActionSpec.examples()`.

### `@Idempotent`: Wired
Marks an action safe to retry, consulted by the retry/fallback layer.

## LLM configuration

### `@LLMSpec`: Nested partial

`@LLMSpec` has `@Target({})`, so it is valid only as a member of another
annotation. `@RoleSpec(llm = …)` consumes `provider`, `model`, `temperature`,
`maxTokens`, and `topP` for the role-level client. `frequencyPenalty`,
`presencePenalty`, `apiKeyEnv`, `endpoint`, `timeoutMs`, and `streaming` are
export metadata only, and `fallbackModel` and `systemPrompt` are currently unused.
`provider` is `LLMSpec.Provider`, not a `String`, and that enum is not the
`LLMClientFactory` provider-name inventory.

`@AgentSpec(llm = …)` is also syntactically valid, but the agent initializer
does not consume it today. Use an agent's `getLLM()` override or
`AgentBuilder.llm(...)`. The older type-level `@LLM` was removed (see below).

Both paragraphs describe the **core agent initializer**. The SCOP bridge resolves
separately and does read the agent tier, as well as `endpoint` and `apiKeyEnv`, when
it builds an `LLMConfiguration`, and an external configuration source can override
any of them per agent. See
[External LLM Configuration](../llm/configuration.md).

### `@LLMParam` / `@LLMParams`: Wired
Bind action parameters to LLM-extracted values.

## Memory & retrieval

### `@Memory` / `@VectorMemory`: Wired / Partial
Configure the agent's memory store. Read from the **agent** class only. On a `Role` these are
introspection/export metadata and do not change memory behaviour. `@VectorMemory.provider` has no
provider SPI lookup yet: `inmemory` uses the base store with TF-IDF search, and every other value
falls back to that same store after logging a notice. `@Memory.vector()` has no runtime consumer at
all.

### `@MemorySpec`: Nested-wired
Memory config nested in `@AgentSpec(memory = …)`, also available programmatically as
`MemoryConfig.builder()` via `AgentBuilder.memoryConfig(...)`.

### `@Knowledge`: Wired
Declares a knowledge source on `@AgentSpec(knowledge = ...)` or
`@RoleSpec(knowledge = ...)`, and an action names entries with
`@ActionSpec(knowledge = ...)`. `use` picks `SEARCH`, `DIRECT`, or
`SEQUENTIAL` per source. `FILE` and `URL` have bundled loaders, and `DATABASE` is
a `SourceLoader` SPI extension point an optional module registers against.
`MEMORY` is chat knowledge the application binds at run time, and `GRAPH` is a
handle to an installed `GraphStoreProvider`.

A source whose type has no installed loader is **skipped with a warning**. The
Role still binds from whichever sources did load. Binding fails only when
**every** enabled source is unloadable. A missing loader costs part of the
corpus. It does not fail dispatch by itself.

### `@Search`: Wired
Retrieval tuning for a `SEARCH` source, nested in `@Knowledge(search = ...)`.
Every member is honoured and all 8 strategies have a runtime path. `queryParam` selects a named action argument, while `navigatorModel`
selects the installed `TreeNavigator` for `Search.Strategy.REASONING`. `GRAPH` resolves
the bundled FILE-corpus, KnowledgeTools, or Neo4j adapters,
while an explicit application provider overrides the framework defaults.
Reranking, query expansion (including `MULTI_QUERY`), and `REASONING` resolve
model SPIs that TnsAI deliberately ships no provider for. The framework side
(resolution, validation, capping, and error classification) is complete.

In `0.14.1`, an unservable `rerankerModel` or
`queryExpansionModel` fails at dispatch, inside the span
`@Retrieval.onFailure` (now `@Search.onFailure`) guards. `CONTINUE` can then emit an ungrounded
answer. `GRAPH` already treats a missing provider as a configuration
error above that guard.

Since `0.15.0` (`TnsAI@cb14410d`,
[PR #198](https://github.com/TnsAI-Framework/TnsAI/pull/198) /
[TAN-5887](https://linear.app/tansuasici-workspace-1/issue/TAN-5887))
those two model names resolve when `Role.create` establishes the
retrieval configuration. An unservable name is a wiring-time
configuration error and is not reachable through `onFailure`. Failures
a provider raises *while* ranking or expanding still follow
`onFailure`. Roles with no effective `@Search`, and declarations
that do not ask for rerank or expansion, do not load those provider
graphs. To inspect what resolved without throwing, see
[RAG diagnostics](../rag/diagnostics.md)
(`RagDiagnostics`, `@since 0.15.0`).

## Guardrails & safety

### `@InputGuardrail` / `@OutputGuardrail`: Wired
Validate/transform input before, and output after, an action. These annotations
are not the supervisor `Guardrail` interface and not the quality tracing
`Guardrail` SPI. See [Guardrails](../security/guardrails.md).

### `@Security`: Wired
Permission checks and sandboxing, enforced by `SecurityEnforcer`.

### `@ApprovalRequired`: Wired
Requires human approval before an action runs (raises `ApprovalRequiredException`).

## Resilience & observability

### `@Resilience`: Partial
Umbrella resilience policy. **Retry** and **timeout** are enforced today. Circuit-breaker,
rate-limit, and bulkhead fields are scaffolded for a later release. The builder counterpart
is `RoleBuilder.resilience(ResilienceConfig…)`.

### `@Traced` / `@Metered`: Wired
Tracing and metrics decorators around actions.

### `@Fallback`: Wired
Declares a fallback method, resolved by `FallbackResolver`.

## Lifecycle, events & coordination

### `@Lifecycle`: Wired
Lifecycle phase hooks.

### `@EventHandler` / `@EventEmitter`: Wired / Scaffold
`@EventHandler` methods are dispatched by `EventHandlerProcessor`. `@EventEmitter` is not yet wired.

### `@Coordination`: Partial
Multi-agent coordination. Only the nested `negotiation()` configuration is honoured today.

### `@Communication` / `@CommunicationStyle`: Wired
Agent communication style, tone, and languages.

## Identity

### `@DIDSpec`: Nested-wired
W3C Decentralised Identifier config, nested in `@AgentSpec(did = …)`.

### `@GroupMember`: Nested-wired
Group membership, nested in `@AgentSpec(groups = …)`.

## Scaffold & being-consolidated

These exist but have **no runtime effect yet** (wiring planned), so don't rely on them:
`@Norm` / `@Norms` (deontic logic), `@EvalMetric` (custom evaluation metrics),
`@QualityGate` (release gates), `@AuditLog` (declarative audit), `@OutputFormatSpec`
(`prettyPrint`/`schema` not yet honoured).

## Recently consolidated

During the 0.11.x annotation cleanup, **23 unwired annotations were removed** in favour of
their working counterparts. **0.14.0** continues the same delete-don't-deprecate
policy for `@Param`. If you were using any of these, switch to the replacement:

| Removed | Use instead |
|---|---|
| `@Param` (removed in 0.14.0) | the Java parameter name (`-parameters`), or `@ToolParam` / `@LLMParam` if you need a live annotation |
| `@LLM` (type-level) | `@LLMSpec` nested in `@RoleSpec`/`@AgentSpec` |
| `@RateLimited` | `@Resilience(rateLimit = …)` |
| `@OnConnect` / `@OnDisconnect` / `@OnMessage` | the `Channel` interface methods |
| `@FSMState` / `@FSMTransition` / `@FSMStates` / `@FSMTransitions` | `StateMachine.builder()` |
| `@SystemPrompt` | `@RoleSpec.description` (or `@LLMSpec.systemPrompt`) |
| `@ChannelSpec` | the `Channel` interface |
| `@Pipeline` / `@PipelineStep` | `PipelineBuilder` |
| `@RequiresPairing` | `@Security` |
| `@ContextCompaction`, `@SlashCommand`, `@WorkspaceSpec`, `@Property`, `@ConfigProperty`, `@Trigger`, `@Delegate`, `@Sanitize`, `@ContentFilter` | removed, since no runtime consumer existed |

Each removal is a `### Removed` entry in the [Changelog](../help/changelog.md) with the rationale.

## See also

- [Semantic Annotation Framework](catalog.md): field tables and usage examples
- [Contracts](../agents/contracts.md): `@Contract` Design-by-Contract
- [Validation](../security/validation.md): `@State` invariants with `@InvariantCheck`
- [Changelog](../help/changelog.md): per-release breaking changes & migration notes
