|
| 1 | +--- |
| 2 | +title: Agent Skills Authoring |
| 3 | +last_updated: 2026-05-01 |
| 4 | +related_files: |
| 5 | + - Rock/AI/Agent/AgentSkillComponent.cs |
| 6 | + - Rock/AI/Agent/AgentSkillContainer.cs |
| 7 | + - Rock/AI/Agent/AgentSkillSettings.cs |
| 8 | + - Rock/AI/Agent/AgentToolHelper.cs |
| 9 | + - Rock/AI/Agent/AgentToolResult.cs |
| 10 | + - Rock/AI/Agent/Annotations/AgentPurposeAttribute.cs |
| 11 | + - Rock/AI/Agent/Annotations/AgentSkillNameAttribute.cs |
| 12 | + - Rock/AI/Agent/Annotations/AgentToolNameAttribute.cs |
| 13 | + - Rock/AI/Agent/Annotations/AgentToolPreambleAttribute.cs |
| 14 | + - Rock/AI/Agent/Annotations/AgentToolPrerequisiteAttribute.cs |
| 15 | + - Rock/AI/Agent/Annotations/AgentUsageAttribute.cs |
| 16 | + - Rock/AI/Agent/Annotations/AgentGuardrailAttribute.cs |
| 17 | +--- |
| 18 | + |
| 19 | +# Agent Skills Authoring |
| 20 | + |
| 21 | +## Overview |
| 22 | + |
| 23 | +An Agent Skill is a domain-grouped bundle of tools the LLM can invoke through Rock's ChatAgent. A `AgentSkillComponent` subclass declares the skill name, purpose, and discoverable tools (methods decorated with the agent annotations). When a chat session activates a skill, the agent infrastructure exposes the skill's tools to the LLM, which calls them like function tools and receives structured `AgentToolResult` responses. Skills are the primary extension point for "let the agent do this kind of work." |
| 24 | + |
| 25 | +The AI subsystem is moving fast (per the deprecation work in commits `f950ad5dfe`, `a374159b01` from April 2026); this doc captures the current authoring shape but expect the API to evolve. |
| 26 | + |
| 27 | +## Why It Exists |
| 28 | + |
| 29 | +Raw LLM tool-calling against Rock data would force per-tool integration with security, context, pagination, and result shaping. The skill abstraction solves these once: the AgentSkillComponent gets the request context (current Person, page entities, security gates), the AgentToolHelper provides pagination and result formatting, the Result types give the chat UI consistent rendering. Authors write tool methods; the infrastructure does the rest. |
| 30 | + |
| 31 | +## Mental Model |
| 32 | + |
| 33 | +```mermaid |
| 34 | +flowchart LR |
| 35 | + Skill[AgentSkillComponent subclass] --> Annotations[Attribute decorations] |
| 36 | + Skill --> Methods[Tool methods] |
| 37 | + Methods --> Helper[AgentToolHelper] |
| 38 | + Helper --> Result[AgentToolResult subclass] |
| 39 | + Container[AgentSkillContainer] --> Skills[All registered skills] |
| 40 | + Container --> Agent[ChatAgent uses skills] |
| 41 | + Agent --> LLM[LLM tool-calling] |
| 42 | +``` |
| 43 | + |
| 44 | +A skill class has decorated methods. The container registers; the agent exposes them; the LLM invokes by name; the methods return Result types that the chat surface renders. |
| 45 | + |
| 46 | +## What You Need to Know |
| 47 | + |
| 48 | +**Subclass `AgentSkillComponent`.** Standard component pattern; one class per skill. |
| 49 | + |
| 50 | +**Decorate the class with `[AgentSkillNameAttribute]` and `[AgentPurposeAttribute]`.** The skill name is what the agent uses to address the skill; purpose tells the LLM what the skill is for. |
| 51 | + |
| 52 | +**Methods are the tools.** Each method becomes a tool the LLM can invoke. Decorate with `[AgentToolNameAttribute]`. |
| 53 | + |
| 54 | +**`[AgentToolPreambleAttribute]` on a tool describes the tool to the LLM.** A short description of what the tool does and when to use it. |
| 55 | + |
| 56 | +**`[AgentToolPrerequisiteAttribute]` declares prerequisites.** "This tool requires the agent to have called X first." Helps the LLM compose multi-step calls. |
| 57 | + |
| 58 | +**`[AgentUsageAttribute]` describes parameters.** Tells the LLM what each parameter is for. Decorates the method or its parameters. |
| 59 | + |
| 60 | +**`[AgentGuardrailAttribute]` enforces safety.** Per-tool guardrails: rate limits, parameter validation, security checks. |
| 61 | + |
| 62 | +**Return `AgentToolResult` subclasses.** Don't return free-form strings. The Result type system is what makes the chat UI render consistently across tools. |
| 63 | + |
| 64 | +**Use `AgentToolHelper` for pagination.** Cursor-based pagination since `7db0e22828` (2026-02-11). Helper methods construct paginated results, summary results, etc. |
| 65 | + |
| 66 | +**Custom Result types extend `AgentToolResult`.** For domain-specific shapes (a custom analytics result, a deployment-specific entity result), subclass. |
| 67 | + |
| 68 | +**Tool calls run with the request context.** `AgentRequestContext` provides current Person, page entities (`ContextAnchor`), and security state. Tools should respect security explicitly. |
| 69 | + |
| 70 | +**Per-tool security checks.** A tool that returns financial data should consult `IsAuthorized` on the relevant entities before returning. Don't rely on agent-level security alone. |
| 71 | + |
| 72 | +**Skills are registered via `AgentSkillContainer`.** Standard container pattern. Subclass + register; agents auto-discover. |
| 73 | + |
| 74 | +**Tool results should be small.** LLM context windows are bounded. Helper methods enforce reasonable result sizes; large results trigger summarization or pagination. |
| 75 | + |
| 76 | +**`AgentToolResult` is auto-converted to NoData.** Per `d0fbc3e5da` (2026-03-27), the AgentToolResult Lava filter automatically creates a NoData result when appropriate. Custom code returning empty data sets benefits. |
| 77 | + |
| 78 | +## Common Scenarios |
| 79 | + |
| 80 | +**"Build a custom 'Get Recent Visitors' skill."** |
| 81 | + |
| 82 | +```csharp |
| 83 | +[AgentSkillName( "RecentVisitors" )] |
| 84 | +[AgentPurpose( "Find recent church visitors and their information." )] |
| 85 | +public class RecentVisitorsSkill : AgentSkillComponent |
| 86 | +{ |
| 87 | + [AgentToolName( "GetRecentVisitors" )] |
| 88 | + [AgentToolPreamble( "Returns visitors from the past N days." )] |
| 89 | + public PaginatedResult<PersonResult> GetRecentVisitors( |
| 90 | + [AgentUsage("Number of days to look back")] int days, |
| 91 | + [AgentUsage("Max results")] int? limit = 50 ) |
| 92 | + { |
| 93 | + // ... query Persons, return paginated result ... |
| 94 | + } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +Register; agent picks up. |
| 99 | + |
| 100 | +**"Add a custom Result type for a domain-specific entity."** |
| 101 | + |
| 102 | +```csharp |
| 103 | +public class SermonResult : EntityResultBase |
| 104 | +{ |
| 105 | + public string Speaker { get; set; } |
| 106 | + public DateTime PreachedDate { get; set; } |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +Use in tool method return types. |
| 111 | + |
| 112 | +**"Validate a parameter."** Use `[AgentGuardrailAttribute]` or in-method validation. Throw if invalid. |
| 113 | + |
| 114 | +**"Check security on a tool."** Inside the method, consult `IsAuthorized` against the entity. Return forbidden / empty if not authorized. |
| 115 | + |
| 116 | +**"Use cursor pagination."** Use `AgentToolHelper.PaginatedResult` factory methods. Pass the cursor; receive the next page. |
| 117 | + |
| 118 | +**"Test a custom skill."** Mock the request context; instantiate the skill; call the method; assert the Result. |
| 119 | + |
| 120 | +## Key Architectural Decisions |
| 121 | + |
| 122 | +### Skills as classes, tools as decorated methods |
| 123 | + |
| 124 | +Familiar C# shape; reflection-based discovery. Cheap authoring path. |
| 125 | + |
| 126 | +### Annotation-based metadata |
| 127 | + |
| 128 | +Compile-time-checked, IDE-discoverable. Each annotation has a clear purpose. |
| 129 | + |
| 130 | +### Structured Result types |
| 131 | + |
| 132 | +Free-form text would lose Lava and chat-UI rendering. Structured types are the right contract. |
| 133 | + |
| 134 | +### `AgentToolHelper` for pagination |
| 135 | + |
| 136 | +Helper enforces consistent shape. Authors don't reinvent pagination per tool. |
| 137 | + |
| 138 | +### Per-tool security checks |
| 139 | + |
| 140 | +Authors must explicitly check; agent-level security is insufficient for fine-grained data. |
| 141 | + |
| 142 | +## Considered but Rejected |
| 143 | + |
| 144 | +### Free-form text Results |
| 145 | + |
| 146 | +Rejected. Loss of structured rendering. |
| 147 | + |
| 148 | +### Single skill class for all tools |
| 149 | + |
| 150 | +Rejected. Domain-grouped skills match the LLM's mental model better. |
| 151 | + |
| 152 | +### Auto-derived security from method visibility |
| 153 | + |
| 154 | +Rejected. Authors must consciously consider security per tool. |
| 155 | + |
| 156 | +## Technical Reference |
| 157 | + |
| 158 | +### Class |
| 159 | + |
| 160 | +`AgentSkillComponent` ([Rock/AI/Agent/AgentSkillComponent.cs](../../Rock/AI/Agent/AgentSkillComponent.cs)): the base. |
| 161 | + |
| 162 | +### Annotations |
| 163 | + |
| 164 | +`Rock/AI/Agent/Annotations/`: |
| 165 | +- `AgentPurposeAttribute`, `AgentSkillNameAttribute` |
| 166 | +- `AgentToolNameAttribute`, `AgentToolPreambleAttribute`, `AgentToolPrerequisiteAttribute`, `AgentToolReturnDescriptionAttribute`, `AgentToolExampleAttribute` |
| 167 | +- `AgentUsageAttribute` |
| 168 | +- `AgentGuardrailAttribute` |
| 169 | +- `JsonIgnoreAgentTypeAttribute`, `JsonIgnoreAudienceTypeAttribute` |
| 170 | + |
| 171 | +### Helper |
| 172 | + |
| 173 | +`AgentToolHelper` ([Rock/AI/Agent/AgentToolHelper.cs](../../Rock/AI/Agent/AgentToolHelper.cs)): pagination, summarization, NoData helpers. Cursor-based pagination since `7db0e22828`. |
| 174 | + |
| 175 | +### Result Types |
| 176 | + |
| 177 | +`Rock/AI/Agent/Classes/Common/`: `PaginatedResult`, `SummaryResult`, `KeyNameResult`. |
| 178 | +`Rock/AI/Agent/Classes/Entity/`: PersonResult, GroupResult, FinancialAccountResult, etc. |
| 179 | + |
| 180 | +### Container |
| 181 | + |
| 182 | +`AgentSkillContainer` ([Rock/AI/Agent/AgentSkillContainer.cs](../../Rock/AI/Agent/AgentSkillContainer.cs)): standard component container. |
| 183 | + |
| 184 | +### Affected Blocks |
| 185 | + |
| 186 | +- **Admin:** AI Agent Detail / List, AI Skill Configuration. |
| 187 | +- **Operational:** Chat block (consumes skills). |
| 188 | + |
| 189 | +### Related Docs |
| 190 | + |
| 191 | +- [docs/ai/ai-overview.md](ai-overview.md) |
| 192 | +- [docs/ai/mcp-integration.md](mcp-integration.md) |
| 193 | +- [docs/lava/writing-filters.md](../lava/writing-filters.md) for Lava integration via the AgentToolResult filter. |
| 194 | + |
| 195 | +## Recent Impactful Changes |
| 196 | + |
| 197 | +- **2026-04-16** ([commit `f950ad5dfe`](https://github.com/SparkDevNetwork/Rock/commit/f950ad5dfe)). Initial work on deprecating the legacy AI code/pattern. |
| 198 | +- **2026-04-10** ([commit `23dc466a00`](https://github.com/SparkDevNetwork/Rock/commit/23dc466a00)). Auto-summarize threshold raised from 2,000 to 60,000 tokens. |
| 199 | +- **2026-03-27** ([commit `d0fbc3e5da`](https://github.com/SparkDevNetwork/Rock/commit/d0fbc3e5da)). AgentToolResult Lava filter automatically creates NoData result when appropriate. |
| 200 | +- **2026-02-11** ([commit `7db0e22828`](https://github.com/SparkDevNetwork/Rock/commit/7db0e22828)). Agent AI logic switched to cursor-based pagination. |
0 commit comments