# Roles

A `Role` defines what an agent can do. Each role has an identity (name, goal, domain) and discoverable actions. Safety constraints live on those actions (`@ActionSpec.mustNever` / `mustAlways`), not on the role itself. Roles generate the system prompt that instructs the LLM. Actions are methods annotated with `@ActionSpec` — they are discovered at runtime via reflection and routed to one of four executor types.

## Creating Roles

There are two ways to create a role: programmatically with `RoleBuilder`, or by subclassing `Role` (optionally with `@RoleSpec`). `RoleBuilder` sets identity, LLM, guardrails, and programmatic actions. Builder-built roles do not carry safety constraints — for `mustNever` / `mustAlways` behavior, subclass `Role` and annotate the action methods.

### With RoleBuilder (programmatic)

Use `RoleBuilder` when you want to define a role inline -- for example in tests, scripts, or when the role configuration is loaded dynamically at runtime. `create()` takes no arguments; set the name with `.name(...)`.

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

import com.tnsai.llm.providers.AnthropicClient;
import com.tnsai.roles.Role;
import com.tnsai.roles.RoleBuilder;

public final class RolesBuilderExample {
    private RolesBuilderExample() {}

    public static Role createResearcher() {
        return RoleBuilder.create()
            .name("Researcher")
            .goal("Find and synthesize information from academic sources")
            .domain("academic-research")
            .llm(new AnthropicClient("claude-sonnet-4-20250514"))
            .build();
    }
}
```

### With Annotations (declarative)

Use `@RoleSpec` when you want the role definition to live directly on the class. This is the preferred approach for production roles because identity, LLM config, and per-action safety constraints are visible at a glance.

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

import com.tnsai.annotations.ActionSpec;
import com.tnsai.annotations.Goal;
import com.tnsai.annotations.LLMSpec;
import com.tnsai.annotations.RoleSpec;
import com.tnsai.enums.ActionType;
import com.tnsai.models.role.RoleIdentity;
import com.tnsai.roles.Role;

@RoleSpec(
    name = "Researcher",
    description = "Finds and synthesizes academic information",
    domains = {"academic", "research"},
    goals = {
        @Goal(
            name = "find_papers",
            condition = "relevant_papers_found",
            description = "Locate and synthesize relevant research"
        )
    },
    llm = @LLMSpec(provider = LLMSpec.Provider.ANTHROPIC, model = "claude-sonnet-4-20250514")
)
public final class RolesSpecExample extends Role {

    @Override
    public RoleIdentity getIdentity() {
        return new RoleIdentity("Researcher", "Find papers", "academic");
    }

    @ActionSpec(
        type = ActionType.LOCAL,
        description = "Search for academic papers on a topic",
        mustNever = {"fabricate references", "plagiarize content"},
        mustAlways = {"cite sources properly"}
    )
    public String searchPapers(String query) {
        return query;
    }
}
```

## Actions

An `Action` is a method annotated with `@ActionSpec` on a Role class. Actions are routed to one of four executor types based on their `ActionType`:

| Type | Executor | Description |
|------|----------|-------------|
| `LOCAL` | `TypedActionExecutor` | Direct method invocation via reflection |
| `WEB_SERVICE` | `WebServiceExecutor` | HTTP REST API calls |
| `LLM` | `LLMRoleExecutor` | Single-shot LLM call; tool dispatch via the agent's `ToolMethodDispatcher` |
| `MCP_TOOL` | `McpToolExecutor` | Model Context Protocol tools |

### Defining Actions

Annotate any method on your Role class with `@ActionSpec` to expose it as an action. The `type` field tells the framework which executor handles the call.

```java
@ActionSpec(
    description = "Search for academic papers on a topic",
    type = ActionType.LLM
)
public String searchPapers(String query) {
    return query;
}
```

## ActionResult

When an action delegates to an external system (HTTP call, LLM tool, MCP tool), the framework executes the call and makes the raw result available as an `ActionResult`. There are two usage patterns:

### Pure Delegate (Abstract, No Body)

If the method has no body (abstract or the framework handles it entirely), the framework executes the action and returns the result directly. No `ActionResult` parameter is needed:

```java
@ActionSpec(
    type = ActionType.WEB_SERVICE,
    description = "Fetch a user by id",
    webService = @WebService(endpoint = "https://api.example.com/users/{id}")
)
public abstract Object getUser(String id);
```

### Post-Process with ActionResult

Add an `ActionResult` parameter to receive the raw execution result and transform it before returning:

```java
@ActionSpec(
    type = ActionType.WEB_SERVICE,
    description = "Fetch a record by id",
    webService = @WebService(endpoint = "https://api.example.com/data/{id}")
)
public Object getData(String id, ActionResult result) {
    // Return as-is
    return result;

    // Or extract a field
    Map<String, Object> json = result.asMap();
    return json.get("name");
}
```

### ActionResult API

`ActionResult` wraps the raw value returned by the external system and provides convenience methods for common conversions like JSON parsing and type deserialization.

| Method | Return type | Description |
|--------|-------------|-------------|
| `getValue()` | `Object` | Raw result value |
| `asString()` | `String` | Value as String (JSON-serialized if not already a String) |
| `asMap()` | `Map<String, Object>` | Value as Map (parsed from JSON if needed) |
| `asList()` | `List<Object>` | Value as List (parsed from JSON if needed) |
| `asJson()` | `JsonNode` | Jackson `JsonNode` for flexible JSON traversal |
| `as(Class<T>)` | `T` | Deserialize to a specific type |
| `isNull()` | `boolean` | True if the underlying value is null |
| `isEmpty()` | `boolean` | True if null, empty String, empty Map, or empty List |

### Example -- Transforming a Web Service Response

This example shows a common pattern: calling a weather API and reshaping the JSON response into a human-readable string before returning it to the agent.

```java
@ActionSpec(
    type = ActionType.WEB_SERVICE,
    description = "Fetch a weather forecast",
    webService = @WebService(endpoint = "https://api.weather.com/forecast/{city}")
)
public String getForecast(String city, ActionResult result) {
    JsonNode json = result.asJson();
    String temp = json.path("main").path("temp").asText();
    String desc = json.path("weather").get(0).path("description").asText();
    return String.format("Temperature: %s, Conditions: %s", temp, desc);
}
```

## Role Accessors

Once you have a `Role` instance, these methods let you inspect its identity, actions, and generated system prompt. Per-action safety constraints are on `ActionMetadata`, not on the role.

```java
RoleIdentity identity = role.identity();
String name = role.getName();
String goal = role.getGoal();
String domain = role.getDomain();

List<ActionMetadata> actions = role.getActions();
int count = role.getActionCount();
boolean has = role.hasAction("searchPapers");
Optional<ActionMetadata> action = role.getAction("searchPapers");
String[] mustNever = action.map(ActionMetadata::getMustNever).orElse(new String[0]);
String[] mustAlways = action.map(ActionMetadata::getMustAlways).orElse(new String[0]);

String systemPrompt = role.getSystemPrompt();
String minimalPrompt = role.getMinimalPrompt();
```

## BDI Model

`Belief`, `Desire`, `Intention`, and `Capability` remain as model types. They
are **not** seeded on `AgentBuilder`. The fluent methods `.belief(...)`,
`.desire(...)`, `.intention(...)`, and `.capability(...)` were removed in
0.13.0 (TNS-541). `Agent` has no `getBeliefs()` / `getDesires()` /
`getIntentions()`. The chat loop is LLM + tools, not a BDI interpreter.

`AgentBuilder.identity(AgentIdentity)` still exists. Optional BDI *planning*
goes through the `CognitiveModel.bdi()` SPI — see [SPI Reference](../../reference/spi.md).

```java
Agent agent = AgentBuilder.create()
    .llm(llm)
    .role(role)
    .identity(new AgentIdentity("ResearchBot", "AI Research Assistant"))
    .principal(principal)
    .liabilitySink(sink)
    .authorityScope(scope)
    .build();
```

| Concept | Class | Purpose |
|---------|-------|---------|
| **Belief** | `Belief` | What the agent knows (key-value pairs) |
| **Desire** | `Desire` | What the agent wants to achieve (goals) |
| **Intention** | `Intention` | How the agent plans to act (committed plans) |
| **Capability** | `Capability` | What the agent can do (skills) |
| **Plan** | `Plan` | Structured plan for achieving desires |
