# Actions vs Tools

Three names sit next to each other in TnsAI. They are not interchangeable.

| Surface | What it is | Who invokes it |
|---|---|---|
| `@ActionSpec` | A Role method the framework routes | `ActionExecutor` (and the LLM, unless `excludeFromLLM`) |
| `@Tool` | A POJO method exposed as an LLM function | The model, through `ToolMethodDispatcher` |
| `BuiltInTool` | An enum entry that instantiates a shipped toolkit | You, at agent construction |

Use **actions** for work the Role owns (local code, HTTP, MCP, or an LLM
turn). Use **tools** for functions the model may call during chat or during
an `ActionType.LLM` action. Use **`BuiltInTool`** only as the compile-safe
handle for a POJO that already lives in `tnsai-tools`.

## `@ActionSpec`

Put this on a **Role** method. Discovery scans the Role at initialization
and `ActionExecutor` dispatches by `ActionType` (`LOCAL`, `WEB_SERVICE`,
`LLM`, `MCP_TOOL`). There is no fifth type.

```java
@ActionSpec(type = ActionType.LOCAL, description = "Look up an order")
public Order findOrder(String orderId) {
    return orders.get(orderId);
}
```

An `LLM` action does **not** register tools. It runs an LLM turn that may
call tools already registered on the **Agent**:

```java
@ActionSpec(
    type = ActionType.LLM,
    description = "Research a topic with the agent's tools")
public String research(String topic) {
    return "Research: " + topic;
}
```

`excludeFromLLM = true` keeps the action callable from code and hides it
from function-calling.

Do not put `@ActionSpec` on a toolkit POJO. That is not how toolkits are
discovered.

## `@Tool`

Put this on a **public method of a POJO**. The method body runs. Register
the instance with `AgentBuilder.toolPojos(...)`. There is no `Tool`
interface and no `Role.tools()` hook on current main.

```java
public final class OrderTools {

    @Tool(name = "order_lookup", description = "Fetch one order by id")
    public Order lookup(@ToolParam(description = "Order id") String id) {
        return store.get(id);
    }
}

Agent agent = AgentBuilder.create()
    .llm(llm)
    .role(orderRole)
    .toolPojos(new OrderTools())
    .build();
```

Each `@Tool` method becomes one JSON-Schema function. The model picks a
name; `ToolMethodDispatcher` invokes the method.

## `BuiltInTool`

One enum constant = one shipped POJO class = that class's `@Tool` methods.
`tnsai-core` stores the FQCN as a string so it does not depend on
`tnsai-tools` at compile time. `instantiate()` uses `Class.forName` and
throws `BuiltInToolInstantiationException` if the artifact is missing.

```java
Agent agent = AgentBuilder.create()
    .llm(llm)
    .role(researchRole)
    .builtInTools(BuiltInTool.WEB_SEARCH_TOOLS, BuiltInTool.PDF_TOOLS)
    .toolPojos(new OrderTools())
    .build();
```

The string `BuiltInTool.WEB_SEARCH_TOOLS.getToolName()` is a catalog key.
It is not a substitute for `@ActionSpec` and it does not open an MCP
server.

## Which one

- **Role-owned step, typed dispatch, contracts, or MCP/HTTP** →
  `@ActionSpec`.
- **Model-callable helper the Role does not own** → `@Tool` on a POJO +
  `toolPojos`.
- **Shipped toolkit already in `tnsai-tools`** → `BuiltInTool` +
  `builtInTools`.
- **`ActionType.LLM` plus web search** → `@ActionSpec(type = LLM)` **and**
  `.builtInTools(BuiltInTool.WEB_SEARCH_TOOLS)`. One does not imply the
  other.

## Related

- [Custom Tools](custom-tools.md)
- [Registration](registration.md)
- [Catalog](catalog.md)
- [Architecture overview](../../start/architecture-overview.md#action-routing)
