# Capabilities

A capability is a reusable action with no body. You declare `@ActionSpec` methods once on a `@Capability` interface, and every role that implements the interface gains those actions. The framework dispatches each call, so no role writes a method body.

Use a capability for `LLM`, `MCP_TOOL` and `WEB_SERVICE` actions that two or more roles share. For those types the framework never runs the method body when the method has no `ActionResult` parameter, so a concrete `return null;` body is dead code. To move existing `return null;` methods onto capabilities, see [From `return null;` bodies to `@Capability`](../upgrade/index.md#from-return-null-bodies-to-capability).

## Declare and adopt a capability

The interface method is `default`, and its body throws `Actions.dispatchedByFramework()`. That body runs only if someone calls the method directly instead of through the agent, and then it throws `DispatchedByFrameworkException` rather than returning a silent `null`.

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

import com.tnsai.actions.Actions;
import com.tnsai.annotations.ActionSpec;
import com.tnsai.capabilities.Capability;
import com.tnsai.enums.ActionType;
import com.tnsai.models.role.RoleIdentity;
import com.tnsai.roles.Role;

public final class CapabilitiesExample {
    private CapabilitiesExample() {}

    @Capability
    public interface Summarizer {
        @ActionSpec(type = ActionType.LLM,
                    description = "Summarise the text in one short paragraph",
                    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 the text to the target language")
        default String translate(String text, String targetLanguage) {
            throw Actions.dispatchedByFramework();
        }
    }

    // Two capabilities, no action bodies on the role.
    public static final class AssistantRole extends Role implements Summarizer, Translator {
        @Override
        public RoleIdentity getIdentity() {
            return new RoleIdentity("assistant", "Handle ad-hoc requests", "general");
        }
    }

    // The role's own method replaces the capability's LLM action with a LOCAL one.
    public static final class StrictEditorRole extends Role implements Summarizer {
        @Override
        public RoleIdentity getIdentity() {
            return new RoleIdentity("strict-editor", "Shorten text deterministically", "editing");
        }

        @Override
        @ActionSpec(type = ActionType.LOCAL, description = "Truncate the text to 60 characters")
        public String summarize(String text) {
            return text.length() <= 60 ? text : text.substring(0, 60) + "...";
        }
    }
}
```

- `AssistantRole` gets two `LLM` actions, `summarize` and `translate`, from its interfaces.
- Discovery walks the role's interfaces, its superclasses and the interfaces those extend, and collects every one annotated with `@Capability`.
- `StrictEditorRole` declares `summarize` itself. Discovery records the role's own signatures first and skips a capability default with the same signature, so the role has one `summarize` action and its type is `LOCAL`.

## Rules checked at discovery

Discovery throws `IllegalStateException` naming the interface and method when a capability method breaks either rule.

- **It must be `default`.** An abstract method would force every role to write a body:

  ```text
  @Capability interface com.example.Summarizer method summarize must be a default method. Abstract capability methods force every adopting role to write a body, defeating the purpose of the annotation. Use `default { throw Actions.dispatchedByFramework(); }` as the body.
  ```

- **It must not take an `ActionResult` parameter.** Post-processing of the raw result belongs on a concrete method of the role:

  ```text
  @Capability interface com.example.Summarizer method summarize must not declare an ActionResult parameter. Capabilities are pure dispatch — move post-processing (ActionResult-based) to a concrete method on the role class itself.
  ```

A role can mix capability actions and its own methods freely.

## When not to use one

- **`LOCAL` actions.** Their body is the action, so keep them on the role.
- **Actions that post-process an `ActionResult`.** The validator rejects them.
- **An action only one role uses.** An interface with one implementer adds indirection and nothing else.

## Capabilities, `@RoleDeclaration` and `@RoleSpec`

These answer different questions, and a role can carry all three.

- A **capability** adds actions.
- **`@RoleDeclaration`** holds identity metadata (name, goal, domain, description, version) that role exporters read. It does not replace `getIdentity()`, which every `Role` subclass still implements.
- **`@RoleSpec`** configures the role: its model (`llm`), knowledge, goals and config. See [Roles](roles.md).

## Related

- [Actions](actions.md): action types and how each one runs.
- [Roles](roles.md): role identity and `@RoleSpec`.
- [Actions and tools](actions-and-tools.md): when an action is the right choice.
