Annotation Interface AgentSpec


@Documented @Inherited @Retention(RUNTIME) @Target(TYPE) public @interface AgentSpec
Defines agent specification including identity and default configuration.

This is the primary annotation for defining agent identity with:

  • Description: What the agent is for
  • DID: W3C Decentralized Identifier for authentication
  • Communication: Default communication style (fallback for roles)
  • LLM: Language model configuration

Name Derivation

Agent name is automatically derived from class name:

  • ResearchAgent → "Research"
  • CustomerSupportAgent → "CustomerSupport"

Usage


 @AgentSpec(
     description = "Research assistant for academic work",
     llm = @LLMSpec(
         provider = Provider.OPENAI,
         model = "gpt-4o",
         temperature = 0.7f
     )
 )
 public class ResearchAgent extends Agent { }
 

Inheritance

The annotation is Inherited, as RoleSpec is: an agent class without its own @AgentSpec takes the nearest superclass's whole, and every reader of the annotation sees it as the subclass's own. That includes its knowledge, llm and config file, its did with its public key and service endpoint (a fixed agentId is an identity and is not inherited: a class that would take one fails at start, even when it is the only subclass, so a class that wants a fixed DID declares it on its own @AgentSpec), its groups, joined on start when autoJoin is set, its roles, played when no roles are given in code, and its autoStart, idleTimeoutMs, toolCallFilter, memory and every other element. An inherited memory store or communication gives way to the subclass's own: a getMemoryStore() or getCommunication() override in any class below the annotated superclass, or a @CommunicationStyle on the agent's own class, wins over it, while a class that declares its own @AgentSpec keeps its annotation's memory and communication first, so an override of either method on the annotated superclass itself gives way, in its subclasses, to the annotation they inherit. The inherited memory's maxContextTokens still applies unless the subclass overrides getMaxContextTokens(), which wins over the annotation either way. Such an override replaces the inherited store or style rather than wrapping it: super.getMemoryStore() and super.getCommunication() return the superclass's implementation, Agent's default unless a class in between overrides it, not the annotation's. Bridge and synthetic methods do not count as an override. A generated subclass, such as a proxy, that declares either method counts as an override too, so a proxy that intercepts either method, even only to call super, gives the agent the superclass's implementation instead of the annotation's; a proxy that must keep the annotation's leaves both methods out of what it intercepts. A generator that copies the class's annotations onto its generated class, such as Mockito's subclass mock maker, makes that class declare the @AgentSpec, so there the subclass's own override gives way to the annotation. Each concrete subclass binds and indexes the inherited knowledge on its own, as two annotated classes declaring the same source do. Fields are not merged: a subclass that declares @AgentSpec replaces the superclass's entirely, so it repeats whatever it keeps, and a subclass that must not take the superclass's settings declares one of its own. An @AgentSpec on an interface is not inherited.

The same holds for a playground class that SCOPBridge resolves: one without its own @AgentSpec gives its superclass's llm as the playground tier.

Messages that name where knowledge is declared, and run file sections, name the subclass: a section keyed by the superclass configures only the superclass, and a section keyed by the subclass may give it knowledge. Knowledge a subclass gives by overriding getKnowledge() layers by name over the inherited knowledge. A subclass that declares only @RoleSpec is a class with both annotations, not a role class: its knowledge is its role's and the inherited agent's together, its role knowledge reaches its chat too, and the rules over a class's whole knowledge run over both, so it is refused at start when, for example, both annotations name a @Config file or declare knowledge of one name. A run file section takes its simple name as an agent's, so another agent class of the run with that simple name makes the key ambiguous.

An inherited @Config file's roles sections bind the subclass's roles, so a subclass that plays fewer roles than they name fails at start. A run file roles section keyed by the superclass is not the subclass's: when the superclass and a subclass playing that role through the inherited roles start in one run, the subclass's key repeats the section, or the second to start fails. An anonymous subclass takes the annotation too, but no run file section names it.

The annotation configures the agent; it does not wire accountability. Role actions run only on an executor with a principal and a default authority scope, which AgentBuilder sets and a direct subclass passes through the four-argument Agent constructor; see com.tnsai.agents.

Author:
TnsAI Team
See Also:
  • Element Details

    • description

      String description
      Agent description.

      Used in system prompts, documentation, and export.

      Returns:
      description
      Default:
      ""
    • did

      DIDSpec did
      DID (Decentralized Identifier) configuration.

      W3C-compliant identifier for agent authentication and inter-agent communication.

      Returns:
      DID configuration
      See Also:
      Default:
      @com.tnsai.annotations.DIDSpec
    • communication

      Communication communication
      Default communication style (fallback).

      Used when a Role doesn't define its own @Communication.

      Returns:
      default communication configuration
      See Also:
      Default:
      @com.tnsai.annotations.Communication
    • version

      String version
      Agent version (semantic versioning).
      Returns:
      version string
      Default:
      "1.0.0"
    • autoStart

      boolean autoStart
      Whether agent should auto-start on creation.

      If true, lifecycle INIT and START phases run automatically.

      Returns:
      true for auto-start
      Default:
      true
    • idleTimeoutMs

      int idleTimeoutMs
      Maximum idle time before agent suspension (milliseconds).

      0 means never suspend.

      Returns:
      idle timeout in ms
      Default:
      0
    • llm

      LLMSpec llm
      LLM configuration for this agent.
      
       @AgentSpec(
           description = "Support agent",
           llm = @LLMSpec(
               provider = Provider.OLLAMA,
               model = "llama3.2",
               temperature = 0.7f
           )
       )
       
      Returns:
      LLM configuration
      See Also:
      Default:
      @com.tnsai.annotations.LLMSpec
    • memory

      MemorySpec memory
      Memory configuration for this agent.
      Returns:
      Memory configuration
      See Also:
      Default:
      @com.tnsai.annotations.MemorySpec
    • toolCallFilter

      Class<? extends ToolCallFilter> toolCallFilter
      Declarative ToolCallFilter for this agent (TNS-611).

      The top-level annotation counterpart of AgentBuilder.toolCallFilter(...). The supplied class is instantiated via its public no-arg constructor at agent initialization and wired into the orchestrator before the first tool call. The default — AllowAllToolFilter — is treated as the "not set" sentinel and leaves the agent with no filter wired (the historical behaviour), so existing agents are unaffected and the AGENT-V006 approval-gate warning still fires for confirmation-required tools without an explicit filter.

      
       @AgentSpec(role = "filtered", toolCallFilter = AllowlistToolFilter.class)
       public class FilteredAgent extends Agent { ... }
       

      Precedence: an explicit AgentBuilder.toolCallFilter(...) (or Agent.setToolCallFilter(...)) call wins over this annotation. Filter classes that lack a public no-arg constructor fail with the same AGENT-V009 error as @AgentSpec.roles.

      Returns:
      the filter class to instantiate, or AllowAllToolFilter when no declarative filter is configured
      Default:
      com.tnsai.agents.execution.AllowAllToolFilter.class
    • maxContextTokens

      int maxContextTokens
      Maximum context-window budget in tokens for this agent (TNS-609).

      The top-level, declarative counterpart of AgentBuilder.maxContextTokens(int): when set (> 0) the agent's conversation history is pruned to this budget before each LLM call (the same pruning the builder shortcut drives). 0 means no agent-level cap.

      
       @AgentSpec(role = "long-context", maxContextTokens = 200_000)
       public class LongContextAgent extends Agent { ... }
       

      This is a shortcut for the common "just set the budget" case. It takes precedence over the nested MemorySpec.maxContextTokens() (set via memory()) when both are present; a template-method override still wins over both. Setting both the shortcut and the nested value to different numbers is discouraged — prefer one.

      Returns:
      the agent-level context-token budget (0 = no cap)
      See Also:
      Default:
      0
    • groups

      GroupMember groups
      Group membership configuration.
      Returns:
      Group membership
      See Also:
      Default:
      @com.tnsai.annotations.GroupMember(groups={})
    • roles

      Class<? extends Role>[] roles
      Role classes this agent plays (TNS-536) — the annotation counterpart of AgentBuilder.role(Role) / roles(List). Each class is instantiated via its public no-arg constructor during agent initialization. Roles that need constructor arguments must use AgentBuilder.role(new MyRole(...)) instead.

      Precedence: if the agent also supplies roles programmatically (builder or a getRoles() override), those win and these are ignored.

      Returns:
      role classes to instantiate (empty by default)
      Since:
      0.11.0
      See Also:
      Default:
      {}
    • knowledge

      Knowledge[] knowledge
      Knowledge this agent's actions and chat turn can use.

      Each entry declares a source once; an action sees a source only when its ActionSpec.knowledge() names it, and the chat turn sees the enabled entries. Two entries with the same Knowledge.name() fail at startup. A builder that already added sources wins.

      Returns:
      knowledge declarations (empty by default)
      Since:
      0.14.0
      See Also:
      Default:
      {}
    • knowledgePolicy

      KnowledgePolicy[] knowledgePolicy
      Presence policies for the sources knowledge() and the class configuration file declare. A source without a policy must load.
      Returns:
      knowledge policies (empty by default)
      Since:
      0.18.1
      See Also:
      Default:
      {}
    • config

      Config config
      The class configuration file, which can override and add to this agent's llm and knowledge and its roles'. No path means no file.
      Returns:
      the configuration file
      Since:
      0.18.0
      See Also:
      Default:
      @com.tnsai.annotations.Config