# Fundamentals

The core moving parts of a single agent. This page covers the `Agent` class
itself — construction, chat, memory, lifecycle. See the other pages in this
section for [Roles](roles.md), the [Action System](action-system.md),
[Capabilities](capabilities.md), and [Events](events.md).

An `Agent` is the top-level orchestrator in TnsAI. It owns an LLM client, one or more roles, a memory store, and an event system. Agents handle the full chat loop: receiving a message, consulting their roles for available actions, calling the LLM, executing tool calls, and returning a response.

## Quick Start

The fastest way to create an agent is with `AgentBuilder`. `build()` requires the accountability trio — `principal`, `liabilitySink`, and `authorityScope` — in addition to at least one role. Without all three, it throws `IllegalStateException` (TNS-298). See [Accountability](../../security/accountability.md).

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

import com.tnsai.accountability.AuthorityScope;
import com.tnsai.accountability.RecordingLiabilitySink;
import com.tnsai.agents.Agent;
import com.tnsai.agents.AgentBuilder;
import com.tnsai.identity.AgentDescriptor;
import com.tnsai.identity.LocalIdentityProvider;
import com.tnsai.llm.LLMClientFactory;
import com.tnsai.roles.RoleBuilder;

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

public final class FundamentalsBuilderExample {
    private FundamentalsBuilderExample() {}

    public static Agent build() {
        var principal = new LocalIdentityProvider().issue(
            AgentDescriptor.builder()
                .agentClass("com.example.AssistantAgent")
                .systemPrompt("You are a helpful assistant.")
                .toolNames(List.of())
                .model("openai:gpt-4o")
                .build());

        return AgentBuilder.create()
            .llm(LLMClientFactory.create("openai", "gpt-4o", 0.7f))
            .role(RoleBuilder.create()
                .name("Assistant")
                .goal("Help users with their questions")
                .build())
            .principal(principal)
            .liabilitySink(new RecordingLiabilitySink())
            .authorityScope(AuthorityScope.unrestricted(Duration.ofHours(1)))
            .build();
    }
}
```

```java
String response = FundamentalsBuilderExample.build().chat("What is BDI architecture?");
```

For more control, extend the `Agent` class directly:

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

import com.tnsai.agents.Agent;
import com.tnsai.annotations.AgentSpec;
import com.tnsai.llm.LLMClient;
import com.tnsai.llm.providers.AnthropicClient;
import com.tnsai.roles.Role;

import java.util.List;

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

    @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 agent = new AnnotatedAgentExample();
        agent.start();
        String answer = agent.chat("What is quantum computing?");
        System.out.println(answer);
    }
}
```

The agent name is derived from the class (`AnnotatedAgentExample` →
`"Annotated Agent Example"`). `@AgentSpec` has no `name` element. A direct
`Agent` subclass must implement `getRoles()`. Override `getLLM()` to provide
its agent-level client; `@AgentSpec.llm` is declared annotation metadata but
is not consumed by the released 0.14.1 runtime or current framework main.

## Creating Agents

There are two ways to create an agent: programmatically with `AgentBuilder`, or declaratively by extending the `Agent` class and using annotations. Use the builder when you want quick, inline setup. Use annotations when you want a reusable agent class with its configuration baked in.

### With AgentBuilder (programmatic)

`AgentBuilder` lets you configure an agent in a single fluent chain. This is the best approach for simple agents or when you want to assemble an agent dynamically at runtime.

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

import com.tnsai.accountability.AuthorityScope;
import com.tnsai.accountability.LiabilitySink;
import com.tnsai.agents.Agent;
import com.tnsai.agents.AgentBuilder;
import com.tnsai.enums.BuiltInTool;
import com.tnsai.identity.AgentPrincipal;
import com.tnsai.llm.providers.OpenAIClient;
import com.tnsai.memory.InMemoryStore;
import com.tnsai.roles.Role;

import java.util.List;

public final class FundamentalsConfigExample {
    private FundamentalsConfigExample() {}

    public static Agent build(
        Role myRole,
        Role role1,
        Role role2,
        Object myDomainTools,
        AgentPrincipal principal,
        LiabilitySink liabilitySink,
        AuthorityScope authorityScope
    ) {
        return AgentBuilder.create()
            .id("agent-001")
            .llm(new OpenAIClient("gpt-4o"))
            .role(myRole)
            .roles(List.of(role1, role2))
            .builtInTools(BuiltInTool.WEB_SEARCH_TOOLS, BuiltInTool.UTILITY_TOOLS)
            .toolPojos(myDomainTools)
            .memoryStore(new InMemoryStore())
            .maxContextTokens(8192)
            .principal(principal)
            .liabilitySink(liabilitySink)
            .authorityScope(authorityScope)
            .build();
    }
}
```

### With Annotations (declarative)

If you prefer a class-per-agent design, extend `Agent` and use `@AgentSpec`
for metadata and the members the runtime consumes. The compiled example above
shows the required template methods. `getRoles()` is abstract: return the
roles directly, or return an empty list to let `@AgentSpec.roles` supply its
fallback role classes. Each annotation-declared role needs a public no-arg
constructor. Roles that take constructor arguments still go through
`AgentBuilder.role(...)` or a non-empty `getRoles()` result.

`@LLMSpec` is nested-only (`@Target({})`), so it is not valid directly on an
agent class. `@RoleSpec(llm = …)` consumes it for a role-level client. Although
`@AgentSpec(llm = …)` is syntactically valid, the agent initializer does not
read that member today; use `getLLM()` or `AgentBuilder.llm(...)` instead.

## Chat Methods

Once you have an agent, you interact with it through chat methods. TnsAI provides several variants depending on whether you need conversation history, streaming output, or visibility into tool calls happening inside the agent loop.

```java
// Simple chat — single turn, uses conversation history
String response = agent.chat("Explain quantum computing");

// Chat without history
String response = agent.chat("Translate this to French", false);

// Streaming — returns tokens as they arrive
Stream<String> tokens = agent.streamChat("Write a poem about Java");
tokens.forEach(System.out::print);

// Event-driven chat — full visibility into the agent loop
String response = agent.chatWithEvents("Research AI safety", event -> {
    switch (event) {
        case ToolCallStartEvent e -> System.out.println("Calling: " + e.toolName());
        case ToolCallEndEvent e -> System.out.println("Result: " + e.result());
        case ErrorEvent e -> System.err.println("Error: " + e.message());
        default -> {}
    }
});
```

## Memory Management

Agents automatically track conversation history so the LLM has context across turns. You can also inspect, modify, or prune this history directly when you need to manage token usage or reset a conversation.

```java
// Get conversation history
List<Map<String, Object>> history = agent.getConversationHistory();

// Clear all history
agent.clearConversationHistory();

// Keep the MemoryStore you passed to AgentBuilder.memoryStore(...) —
// Agent.getMemoryStore() is protected, not a public accessor.
memoryStore.addMessage("user", "Remember this context");

// Prune memory to fit within a token limit (removes oldest messages first)
memoryStore.prune(4096);
```

## Lifecycle

Agents have a start/stop lifecycle. `start()` is a no-op — protocols auto-start when annotations are detected. Call `stop()` to release resources (there is no `shutdown()` or `isRunning()`). Inspect operational health with `getHealthState()`.

```java
agent.start();
AgentHealthState health = agent.getHealthState();
agent.stop();
```

## Configuration Summary

This table lists the common `AgentBuilder` properties. Required at `build()`: at least one role plus the accountability trio. `.llm(...)` is optional for traditional (non-LLM) agents. There is no `.model(String)`, `.systemPrompt(...)`, `.guardrail(...)`, or `.ragPipeline(...)` on `AgentBuilder`.

| Property | Builder Method | Default | Description |
|----------|---------------|---------|-------------|
| ID | `.id(String)` | Auto-generated | Unique agent identifier |
| LLM | `.llm(LLMClient)` | Optional | Language model client (omit for traditional agents) |
| Roles | `.role(Role)` / `.roles(List)` | Required | Agent roles |
| Principal | `.principal(AgentPrincipal)` | Required | Actor identity (TNS-298) |
| Liability sink | `.liabilitySink(LiabilitySink)` | Required | Audit destination |
| Authority scope | `.authorityScope(AuthorityScope)` | Required | What the agent may do |
| Built-in toolkits | `.builtInTools(BuiltInTool...)` | Empty | Shipped POJO toolkits from `tnsai-tools` |
| Custom toolkits | `.toolPojos(Object...)` | Empty | Your own POJOs with `@Tool` methods |
| Runtime tools | `.dynamicTool(DynamicToolMethod)` | Empty | Tools whose identity is only known at runtime (e.g. MCP proxies) |
| Memory | `.memoryStore(MemoryStore)` | `InMemoryStore` | Conversation memory |
| Context limit | `.maxContextTokens(int)` | Provider default | Max context window |
| Chat knowledge | `.chatKnowledge(String)` | None | Names one declared source to ground chat; pair with `.addKnowledgeSource` / `.retrieval`. `.knowledgeBase(KnowledgeBase)` was removed in `0.16.0`. |
| Prompt strategy | `.promptStrategy(PromptStrategy)` | Default | Prompt enhancement |
| Reasoning | `.reasoningStrategy(String)` | None | Reasoning strategy name |

## SPI Extension Points

The Core module defines SPI interfaces that other modules implement. Extensions are discovered automatically via `ServiceLoader`:

| SPI Interface | Purpose | Implementing Module |
|--------------|---------|---------------------|
| `MessageBroker` | Agent communication routing | Coordination |
| `ResilienceStrategy` | Resilience pattern implementations | Quality |
| `CognitiveModel` | Cognitive processing models | Intelligence |
| `CheckpointerFactory` | State checkpointing | Custom |
| `CheckpointerProvider` | Checkpoint storage backends | Custom |

Register an SPI implementation by adding a file to `META-INF/services/`:

```
# META-INF/services/com.tnsai.spi.MessageBroker
com.example.MyCustomMessageBroker
```

## Next in this Section

- [Roles](roles.md) — Bundling capabilities into `Role` classes.
- [Action System](action-system.md) — `@ActionSpec` routing and executor types.
- [Actions vs Tools](actions-vs-tools.md) — `@ActionSpec` vs `@Tool` vs `BuiltInTool`.
- [Capabilities](capabilities.md) — Reusable body-less action contracts via `@Capability` interfaces.
- [Events](events.md) — The agent's lifecycle event bus.
