Skip to content
tnsaijava agent framework

Quickstart

Build your first TnsAI agent in 5 minutes. Prerequisites: Installation.

Your First Agent

Option 1: AgentBuilder (no subclassing)

package com.example.tnsai.docs;

import com.tnsai.accountability.AuthorityScope;
import com.tnsai.accountability.RecordingLiabilitySink;
import com.tnsai.actions.ActionDiscovery;
import com.tnsai.agents.Agent;
import com.tnsai.agents.AgentBuilder;
import com.tnsai.annotations.ActionSpec;
import com.tnsai.enums.ActionType;
import com.tnsai.identity.AgentDescriptor;
import com.tnsai.identity.LocalIdentityProvider;
import com.tnsai.llm.providers.AnthropicClient;
import com.tnsai.models.role.RoleIdentity;
import com.tnsai.roles.Role;

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

public final class QuickstartBuilderExample {

    private QuickstartBuilderExample() {
    }

    public static final class AssistantRole extends Role {
        @Override
        public RoleIdentity getIdentity() {
            return new RoleIdentity(
                    "assistant",
                    "Friendly conversational agent",
                    "support");
        }

        @ActionSpec(
                type = ActionType.LOCAL,
                description = "Greet the user by name")
        public String greet(String name) {
            return "Hello, " + name + "!";
        }
    }

    public static Agent buildAgent() {
        String model = "claude-sonnet-4-20250514";
        var role = Role.create(AssistantRole.class);
        var actionNames = ActionDiscovery.discoverActions(role).getActions().stream()
                .map(action -> action.getName())
                .toList();
        var principal = new LocalIdentityProvider().issue(
                AgentDescriptor.builder()
                        .agentClass(AssistantRole.class.getName())
                        .systemPrompt("You are a friendly assistant.")
                        .toolNames(actionNames)
                        .model("anthropic:" + model)
                        .build());

        return AgentBuilder.create()
                .llm(new AnthropicClient(model))
                .role(role)
                .principal(principal)
                .liabilitySink(new RecordingLiabilitySink())
                .authorityScope(AuthorityScope.unrestricted(Duration.ofHours(1)))
                .build();
    }

    public static void main(String[] args) {
        Agent agent = buildAgent();
        agent.start();
        String response = agent.chat("Hi, my name is Alice");
        System.out.println(response);
    }
}

Required. The AgentBuilder path needs the accountability trio — principal, liabilitySink, and authorityScope. Without all three, build() throws IllegalStateException (TNS-298); the framework ships no silent no-op default. RecordingLiabilitySink is in-memory — use FilesystemLiabilitySink for production audit trails. See Accountability for the full identity / liability / scope model.

Option 2: Annotated Agent (subclass)

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);
    }
}

Adding Tools

For brevity the examples below omit the .principal(...), .liabilitySink(...), and .authorityScope(...) calls — assume the principal, sink, and scope from Option 1. Every AgentBuilder.build() still requires them.

Register shipped POJO toolkits by enum constant — compile-safe:

import com.tnsai.enums.BuiltInTool;

var agent = AgentBuilder.create()
    .llm(new AnthropicClient("claude-sonnet-4-20250514"))
    .role(new AssistantRole())
    .builtInTools(
        BuiltInTool.WEB_SEARCH_TOOLS,   // brave_search, duckduckgo, wikipedia, …
        BuiltInTool.PDF_TOOLS,          // pdf_extract_text, pdf_metadata, …
        BuiltInTool.MARKDOWN_TOOLS      // markitdown
    )
    .build();

Each enum entry registers every @Tool-annotated method on the backing POJO. The LLM sees them all (e.g. brave_search, pdf_extract_text, markitdown) and picks one per call.

For your own toolkits, register the POJO instances directly:

var agent = AgentBuilder.create()
    .llm(llm)
    .role(role)
    .toolPojos(new MyDomainTools(), new MyAnalyticsTools())
    .build();

See Tools / Catalog for the full shipped catalog and Custom Tools for the @Tool annotation pattern.

Streaming

agent.streamChatWithTools("Summarize this paper", chunk -> {
    if (chunk.isContent()) {
        System.out.print(chunk.getContent());
    }
});

Structured Output

record WeatherInfo(String city, double temperature, String condition) {}

WeatherInfo weather = agent.chatWithFormat(
    "What's the weather in Istanbul?",
    WeatherInfo.class,
    3 // max retries
);

Next Steps