Every LLM agent library gives you tools. Few give you a coherent system for organizing those tools into reusable, context-aware skill clusters that can adapt to the business problem at hand — not the other way around. Solon AI's Talent system is that organization layer.
In this post I'll walk through what a Talent actually is, how it differs from a plain tool, and how Solon ships over thirty built-in Talents covering everything from code search and memory management to enterprise messaging and file operations. Then I'll show how to compose them to solve real business problems: sending a report email with attachments, pushing a deployment notification to your team chat, and running a self-improving agent that learns from past conversations.
What a Talent actually is
In Solon AI, a Talent is a bundle of three things:
-
Tools — the
FunctionToolmethods the LLM can call (annotated with@ToolMapping). - Instructions — a dynamic prompt fragment injected into the system message when the Talent is active, telling the model how to use those tools.
- Metadata — name, description, and an enable/disable flag.
The key difference from a plain tool is context awareness. A Talent can decide whether it's relevant to the current conversation via isSupported(Prompt), initialize state when attached via onAttach(Prompt), and even swap its tool set dynamically based on what the user is asking.
public class MyTalent extends AbsTalent {
@Override
public boolean isSupported(Prompt prompt) {
String content = prompt.getUserContent().toLowerCase();
return content.contains("代码") || content.contains("code");
}
@Override
public String getInstruction(Prompt prompt) {
return "## 代码搜索指南\n你可以通过 codesearch 工具查找 API 文档和示例代码...\n";
}
@ToolMapping(name = "my_tool", description = "Do something useful")
public String myTool(@Param("param") String param) {
return doSomething(param);
}
}
AbsTalent (the convenience base class) handles the boilerplate: it reflects over @ToolMapping methods to auto-register tools, manages the enabled/disabled lifecycle, and exposes getToolMap() and getToolAry() for introspection.
The built-in Talent ecosystem
Solon AI ships a rich set of Talents across dedicated Maven modules. Here's a survey of the ones most relevant to business applications:
| Talent | Module | What it does |
|---|---|---|
TerminalTalent |
solon-ai-talent-cli |
File read/write/ls/grep/glob + bash execution with sandbox support |
MemoryTalent |
solon-ai-talent-memory |
Long-term memory: extract, recall, search, consolidate, prune |
WebsearchTalent |
solon-ai-talent-web |
Real-time web search via Exa MCP |
CodeSearchTalent |
solon-ai-talent-web |
Code repo search via Exa MCP |
MailTalent |
solon-ai-talent-mail |
Send emails with HTML body and file attachments |
DingTalkTalent |
solon-ai-talent-social |
Push messages to DingTalk (with HMAC signature) |
FeishuTalent |
solon-ai-talent-social |
Push Lark/Feishu cards (with signature) |
WeComTalent |
solon-ai-talent-social |
Push messages to WeCom / Enterprise WeChat |
Text2SqlTalent |
solon-ai-talent-text2sql |
Natural language to SQL with dialect support |
PdfTalent |
solon-ai-talent-pdf |
PDF reading and extraction |
RedisTalent |
solon-ai-talent-data |
Redis read/write operations |
LspTalent |
solon-ai-talent-lsp |
Language Server Protocol integration |
All Talents are opt-in. You挂 them onto a ReActAgent or HarnessEngine explicitly:
ReActAgent agent = ReActAgent.of()
.model(chatModel)
.defaultTalentAdd(
new TerminalTalent(mountManager),
new MemoryTalent(solutionProvider),
new MailTalent(workDir, host, port, user, pass),
new DingTalkTalent(webhookUrl, secret)
)
.build();
Each Talent's isSupported() method is evaluated at call time. If the current prompt matches, the Talent activates, its getInstruction() is injected into the system message, and its tools become available to the model.
Business scenario 1: automated report delivery
A common enterprise pattern: generate a report, attach it, and email it to stakeholders. With Solon AI, this is two Talents working together.
MailTalent mail = new MailTalent(
"/path/to/workdir",
"smtp.company.com", 465,
"agent@company.com", "smtp-password"
);
DingTalkTalent dingtalk = new DingTalkTalent(
"https://oapi.dingtalk.com/robot/send?access_token=xxx",
"SECxxxxxxxx"
);
ReActAgent agent = ReActAgent.of()
.model(chatModel)
.defaultTalentAdd(mail, dingtalk)
.build();
When the user says "Generate the monthly sales report and send it to the team," the agent:
- Uses
TerminalTalentto generate the report file in the work directory. - Calls
send_emailonMailTalent, which auto-detects the attachment path, reads the file bytes, and sends via SMTPS (port 465, TLS). TheMailTalentresolves attachment paths relative to the work directory and validates them against the root path to prevent path traversal. - Calls
send_dingtalkonDingTalkTalent, which packages the summary as a markdown message, computes the HmacSHA256 signature, and pushes to the webhook.
Both operations are synchronous from the agent's perspective. MailTalent uses a connection pool (coreSize=2) for SMTP sessions, so high-frequency report runs don't open a new TCP connection each time.
Business scenario 2: multi-platform team notification
In a multi-office company, different teams use different platforms — DingTalk in China, Feishu in some subsidiaries, WeCom in others. Rather than writing three separate notification integrations, you挂 all three Talents and let the model pick the right one based on context.
ReActAgent agent = ReActAgent.of()
.model(chatModel)
.defaultTalentAdd(
new DingTalkTalent(dingtalkUrl, dingtalkSecret),
new FeishuTalent(feishuUrl, feishuSecret),
new WeComTalent(wecomUrl)
)
.build();
Each Talent overrides isSupported() to match keywords in the user's message:
-
DingTalkTalent: matches "钉钉" or "ding" -
FeishuTalent: matches "飞书", "lark", or "feishu" -
WeComTalent: matches "企微", "企业微信", or "wecom"
This means the model automatically routes to the correct platform. If the user says "Notify the Shanghai team on DingTalk about the deployment," only DingTalkTalent.isSupported() returns true, so its instruction and tools are injected. The other two Talents stay dormant.
FeishuTalent adds a nice touch: if a title is provided, it sends an interactive card (with a blue header and lark_md body); otherwise it falls back to plain text. DingTalkTalent similarly upgrades to markdown mode when a title is present.
Business scenario 3: self-improving agent with long-term memory
Perhaps the most powerful pattern is combining MemoryTalent with a ReActAgent to build an agent that gets smarter over time.
MemorySolutionProvider solutionProvider = /* ... configure ... */;
MemoryTalent memory = new MemoryTalent(solutionProvider)
.relevanceCount(5) // semantic matches from current conversation
.priorityCount(5) // high-importance fallback memories
.relevanceInjection(true); // mix semantics + popularity
ReActAgent agent = ReActAgent.of()
.model(chatModel)
.defaultTalentAdd(memory)
.build();
MemoryTalent exposes five tools:
-
memory_extract— store a fact with an importance score (1-10). Returns the old value for comparison if the key already exists. -
memory_recall— exact lookup by key. -
memory_search— semantic search by natural language query, or*to list all keys. -
memory_consolidate— merge multiple low-importance fragments into a single high-importance insight (importance automatically set to 10). -
memory_prune— delete a stale or incorrect entry.
The getInstruction() method of MemoryTalent does something important: it injects relevant memories into the system prompt at the start of each turn. It fetches up to relevanceCount semantically similar memories and up to priorityCount high-importance memories, deduplicates them, and formats them into the prompt. This means the agent doesn't need to explicitly call memory_search on every turn — the most relevant context is already there.
The agent is also guided by the injected instructions to proactively call memory_extract when it learns something worth remembering, and memory_consolidate when it notices碎片 (fragments) piling up.
The mount system: connecting Talents to the filesystem
TerminalTalent (the file system and bash Talent) works in concert with MountManager, which maps logical paths (@solon-source, @workspace-agents, etc.) to physical directories. When sandbox mode is enabled, TerminalTalent restricts all file operations to the work directory and explicitly listed mounts — no absolute paths, no cross-mount writes unless the mount is marked writeable="true".
The instruction string that TerminalTalent.getInstruction() generates is dynamic: it lists all active mounts with their aliases, types, and write permissions, so the LLM always knows which paths it can safely use.
<mount_list>
<mount alias="@solon-source" type="FILES" writeable="false" ... />
<mount alias="@workspace-agents" type="AGENTS" writeable="false" ... />
</mount_list>
This means you can give an agent read-only access to your source code repositories while allowing it to write freely in its work directory — all without hardcoding path restrictions in the prompt.
Why composition matters
The Talent system's real power isn't any single Talent — it's that they compose cleanly. Each Talent is an independent module with no hard dependencies on the others. You can mix and match:
- Add
Text2SqlTalentto let the agent query your database in natural language. - Add
PdfTalentto let it read and summarize PDF attachments. - Add
CodeSearchTalentso it can look up API docs before writing code. - Add
MemoryTalentso it remembers your preferences across sessions.
And when a Talent isn't needed, you simply don't挂 it. There's no global registry to clean up, no configuration file to edit. The isSupported() gate ensures even unused Talents impose zero overhead.
All Talents share the same small vocabulary: @ToolMapping for tools, getInstruction() for context, isSupported() for gating. That uniformity is what makes the system feel cohesive rather than a collection of disjoint utilities.
If you want to explore the full list of built-in Talents and their APIs, the source lives at solon.noear.org.
Top comments (0)