Adding Capabilities
Capabilities define what tasks Iris can perform. Each capability is a TOML file containing a prompt and output schema. This guide shows you how to add a new capability to Git-Iris.
What is a Capability?
A capability combines three elements:
- Task Prompt — Instructions for Iris on how to approach the task
- Output Type — Structured format for the response (JSON schema)
- Tool Access — Which tools Iris can use for this task
Capability Structure
Basic TOML Format
name = "my_capability"
description = "Brief description of what this does"
output_type = "MyOutputType"
task_prompt = """
Instructions for Iris go here.
## Tools Available
- `git_diff()` - Get code changes
- `file_read(path="...")` - Analyze specific files
## Output Requirements
Your requirements for the output format.
## JSON Output
Return a `MyOutputType` with: field1, field2, etc.
"""Real Example: Commit Message Generation
From src/agents/capabilities/commit.toml:
name = "commit"
description = "Generate commit messages from staged changes"
output_type = "GeneratedMessage"
task_prompt = """
Generate a commit message for the staged changes.
## Context Gathering
`project_docs(doc_type="context")` returns a compact snapshot of the README and agent instructions.
Start with `git_diff()` for change evidence, then call `project_docs` when repository conventions or product framing matter.
## Tools Available
- `project_docs(doc_type="context")` - Compact project conventions snapshot; use targeted doc types for full docs
- `git_diff()` - Get staged changes with relevance scores
- `git_log(count=5)` - Recent commits for style reference
## Workflow
1. Call `git_diff()` to see what changed
2. Call `project_docs(doc_type="context")` when repository conventions affect the wording
3. Generate the commit message following project conventions
## Output Requirements
- **Subject line**: Imperative mood, max 72 chars, no period
- **Body**: Explain WHY, not what. Wrap at 72 chars.
- **Plain text only**: No markdown, no code fences
## JSON Output
Return a `GeneratedMessage` with: `emoji` (string or null), `title` (subject), `message` (body)
"""Step-by-Step: Adding a New Capability
Teaching Example
This section walks through creating a hypothetical "Feature Summary" capability. This capability does not exist in the current codebase — it's an example to illustrate the pattern. Follow along to learn how capabilities work.
Step 1: Create the TOML File
Create src/agents/capabilities/feature_summary.toml:
name = "feature_summary"
description = "Generate a high-level summary of a feature branch"
output_type = "FeatureSummary"
task_prompt = """
You are Iris, an AI assistant analyzing a feature branch to create a high-level summary.
## Context Gathering
`project_docs(doc_type="context")` returns a compact project snapshot. Use it when repo terminology, conventions, or workflow rules affect the summary.
## Tools Available
- `project_docs(doc_type="context")` - Get a compact project context snapshot
- `git_diff(from="<default-branch>", to="HEAD")` - Get changes between branches
- `git_log(count=N)` - Get commit history
- `file_read(path="...")` - Analyze specific files in detail
## Workflow
1. Get the diff between the primary branch and the feature branch
2. Call `project_docs(doc_type="context")` when repository conventions or terminology affect the summary
3. Identify key files and patterns
4. Summarize the feature's purpose, implementation approach, and impact
## Output Requirements
- **Purpose**: 1-2 sentences on what this feature does
- **Approach**: Brief technical overview
- **Files Changed**: Count and categorization
- **Impact**: User-facing changes, API changes, internal refactors
## JSON Output
Return a `FeatureSummary` with:
- `purpose` (string)
- `approach` (string)
- `files_changed` (number)
- `impact` (string)
"""Step 2: Define the Output Type
Create or update src/types/feature_summary.rs:
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Feature summary response
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
pub struct FeatureSummary {
/// What this feature does (1-2 sentences)
pub purpose: String,
/// Technical approach overview
pub approach: String,
/// Number of files changed
pub files_changed: usize,
/// User-facing and technical impact
pub impact: String,
}
impl FeatureSummary {
/// Format as markdown for display
pub fn format(&self) -> String {
format!(
"# Feature Summary\n\n\
## Purpose\n{}\n\n\
## Approach\n{}\n\n\
## Impact\n{}\n\n\
Files changed: {}\n",
self.purpose,
self.approach,
self.impact,
self.files_changed
)
}
}Add to src/types/mod.rs:
pub mod feature_summary;
pub use feature_summary::FeatureSummary;Step 3: Register in StructuredResponse
Edit src/agents/iris.rs. The shipped variants today are CommitMessage, PullRequest, Changelog, ReleaseNotes, Review, SemanticBlame, and PlainText — note that the review arm is named Review, not MarkdownReview, and wraps crate::types::Review (a structured type, not a markdown wrapper):
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StructuredResponse {
CommitMessage(crate::types::GeneratedMessage),
PullRequest(crate::types::MarkdownPullRequest),
Changelog(crate::types::MarkdownChangelog),
ReleaseNotes(crate::types::MarkdownReleaseNotes),
/// Structured code review with parseable findings
Review(crate::types::Review),
/// Semantic blame explanation (plain text)
SemanticBlame(String),
PlainText(String),
// Add your new type:
FeatureSummary(crate::types::FeatureSummary),
}
impl fmt::Display for StructuredResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
// ... existing arms ...
StructuredResponse::FeatureSummary(summary) => {
write!(f, "{}", summary.format())
}
}
}
}Also extend serialize_artifact_for_critic if the critic should be able to inspect your new variant — see Critic Verification below.
Step 4: Load the Capability
Add the embedded TOML constant in src/agents/iris.rs alongside the existing ones:
const CAPABILITY_COMMIT: &str = include_str!("capabilities/commit.toml");
const CAPABILITY_PR: &str = include_str!("capabilities/pr.toml");
// ... existing capabilities ...
const CAPABILITY_FEATURE_SUMMARY: &str = include_str!("capabilities/feature_summary.toml");Then register the new capability inside load_capability_config(). The loader uses a plain match against the capability name, not a HashMap insertion:
let content = match capability {
"commit" => CAPABILITY_COMMIT,
"pr" => CAPABILITY_PR,
"review" => CAPABILITY_REVIEW,
"changelog" => CAPABILITY_CHANGELOG,
"release_notes" => CAPABILITY_RELEASE_NOTES,
"chat" => CAPABILITY_CHAT,
"semantic_blame" => CAPABILITY_SEMANTIC_BLAME,
"feature_summary" => CAPABILITY_FEATURE_SUMMARY, // Add your arm
_ => {
// Unknown capabilities fall back to a generic prompt + PlainText output type
return Ok((
format!(
"You are helping with a {capability} task. Use the available Git tools to assist the user."
),
"PlainText".to_string(),
));
}
};Step 5: Add Execution Logic
Iris dispatches on the capability's declared output_type (the string in the TOML), not on the capability name. Find execute_output_type(&self, output_type: &str, system_prompt: &str, user_prompt: &str) in src/agents/iris.rs and add a match arm. It delegates the schema-driven call to the internal execute_with_agent::<T>() helper:
async fn execute_output_type(
&self,
output_type: &str,
system_prompt: &str,
user_prompt: &str,
) -> Result<StructuredResponse> {
match output_type {
"GeneratedMessage" => {
let response = self
.execute_with_agent::<crate::types::GeneratedMessage>(
system_prompt,
user_prompt,
)
.await?;
Ok(StructuredResponse::CommitMessage(response))
}
// ... existing arms (MarkdownPullRequest, MarkdownChangelog, MarkdownReleaseNotes, Review, SemanticBlame) ...
"FeatureSummary" => {
let response = self
.execute_with_agent::<crate::types::FeatureSummary>(
system_prompt,
user_prompt,
)
.await?;
Ok(StructuredResponse::FeatureSummary(response))
}
_ => {
let agent = self.build_agent()?;
let full_prompt = format!("{system_prompt}\n\n{user_prompt}");
let response = agent.prompt_multi_turn(&full_prompt, 50).await?;
Ok(StructuredResponse::PlainText(response))
}
}
}Step 6: Test Your Capability
# Build
just build
# Test in CLI (you may need to add a CLI command for your capability)
just run -- feature-summary main..feature-branch
# Or test in Studio (if you add a mode for it)
just studioBest Practices
Prompt Engineering
Be specific about workflow:
## Workflow
1. Call `project_docs(doc_type="context")` first
2. Get the diff with `git_diff()`
3. For files over 500 lines, use `file_read(path="...", start_line=1, num_lines=200)` for targeted analysis
4. Synthesize findings into output formatProvide clear output requirements:
## Output Requirements
- **Title**: Max 100 chars, action-oriented
- **Summary**: 2-3 paragraphs, focus on impact
- **Be precise**: State verified facts clearly and call out inferences when evidence is incompleteGive examples:
Example output:
{
"title": "Add user authentication with JWT",
"summary": "Implements JWT-based authentication...",
"impact": "Breaking: All API endpoints now require auth headers"
}Context Strategy
Guide Iris on how to handle different changeset sizes:
## Context Strategy by Size
- **Small** (≤3 files, <100 lines): Consider all changes equally
- **Medium** (≤10 files, <500 lines): Focus on files with >60% relevance
- **Large** (>10 files or >500 lines): Use top 5-7 highest-relevance files
- **Very Large** (>20 files): Use `parallel_analyze` to distribute workTool Selection
Only list tools relevant to the task:
## Tools Available
- `git_diff()` - Get changes (use detail="summary" first)
- `git_log(count=5)` - Recent commits for context
- `file_read(path="...")` - Deep analysis of specific files
# Don't list every possible tool—keep it focusedCertainty Standards
Enforce definitive language:
## Writing Standards
- Be precise about confidence. If evidence is incomplete, gather more context and call out what is verified versus inferred.
- If unsure, use tools to gather more contextOutput Type Patterns
Simple JSON Schema
For structured data:
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
pub struct SimpleOutput {
pub field1: String,
pub field2: Vec<String>,
pub field3: Option<usize>,
}Markdown Wrapper
For LLM-driven formatting:
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
pub struct MarkdownOutput {
/// Markdown content (LLM controls structure)
pub content: String,
}
impl MarkdownOutput {
pub fn raw_content(&self) -> &str {
&self.content
}
}Use markdown wrappers when you want the LLM to control the exact structure while still having parseable output.
Common Patterns
Multi-Stage Analysis
## Workflow
1. Initial scan: `git_diff(detail="summary")` for overview
2. Identify key areas from relevance scores
3. Deep dive: `file_read(path="...", start_line=1, num_lines=200)` on top 5 files
4. Synthesize into structured outputParallel Processing
For large changesets:
## Very Large Changesets (>20 files)
Use `parallel_analyze` to distribute work:
parallel_analyze({
"tasks": [
"Analyze API changes in src/api/",
"Review database schema changes",
"Check frontend component updates"
]
})
Each subagent analyzes independently, then you synthesize.Style Adaptation
Allow preset-based customization:
## Style Adaptation
If STYLE INSTRUCTIONS are provided, prioritize that style in your output.
The structural requirements still apply, but adapt tone and word choice.Critic Verification (Internal Output Types)
Not every output_type corresponds to a user-facing StructuredResponse variant. The shipped verify capability (src/agents/capabilities/verify.toml) returns a Critique value that is consumed entirely inside iris.rs:
should_run_critic(capability, output_type)decides whether the critic should run for(commit, GeneratedMessage),(review, Review),(pr, MarkdownPullRequest),(changelog, MarkdownChangelog), or(release_notes, MarkdownReleaseNotes).verify_response_if_enabled()loads theverifycapability, runsexecute_with_agent::<Critique>(), and either accepts the response or re-runsexecute_output_type()with a revision prompt.Critiqueis a private internal type — it is never serialized intoStructuredResponse.
Use this pattern when you want a capability that audits or post-processes another capability's artifact without surfacing its raw output to the user. Add the capability TOML, wire it into load_capability_config(), but call it explicitly from another method rather than threading it through execute_output_type().
Chat and Content Updates
The chat capability (src/agents/capabilities/chat.toml) is the conversational entry point users open with / in Studio. It does not return a StructuredResponse variant — instead, Iris mutates the active mode's artifact through dedicated tools defined in src/agents/tools/content_update.rs:
update_commit(emoji, title, message)— rewrites the commit messageupdate_pr(content)— rewrites the PR descriptionupdate_review(content)— rewrites the review
Each tool ships a ContentUpdate value over an mpsc::Sender<ContentUpdate> that the Studio app drains on its event loop, applying the change to the relevant *State struct. When you want chat to manipulate a new artifact type, add a corresponding Update<X>Tool next to the existing trio, extend the ContentUpdate enum with the new variant, and handle the new variant in the Studio receiver. The chat capability's TOML lists these tools so the LLM knows when to invoke them.
Integration with Studio
If you add a Studio mode for your capability:
- Add mode variant to
Modeenum insrc/studio/state/mod.rs - Create state struct in
src/studio/state/modes.rs - Implement handler and renderer (see Adding Studio Modes)
Troubleshooting
Iris doesn't use the right tools
Make the tool list more explicit and add workflow steps that require specific tools.
Output parsing fails
Ensure your JSON schema matches exactly. Test with --debug to see raw responses.
Responses are too vague
Add certainty standards and specific output requirements. Show examples.
Context is incomplete
Guide Iris on when to gather more context. Add size-based strategies.
Examples in Codebase
Study these real capabilities:
commit.toml— Simple structured output, style adaptationreview.toml— Markdown wrapper, parallel analysis, size strategiespr.toml— Multi-stage workflow, context gatheringchangelog.toml— Version comparison, structured formatting
Next Steps
- Add tools to give Iris new context sources → Adding Tools
- Create a mode to surface your capability in Studio → Adding Studio Modes
- Contribute your capability back → Contributing
