Skip to main content

Agent SDK

The Agent SDK lets you customize and extend the RBS coding agent with project-specific capabilities. Define custom tools, system prompts, teammate roles, subagents, and slash commands — all in your WORKSPACE.rbs or BUILD.rbs files using the RBS DSL.

Overview

ExtensionAPIPurpose
Toolsnative.define_agent_tool()Custom actions the agent can take.
Promptsnative.define_agent_prompt()Specialized system prompts with linked tools.
Subagentsnative.define_agent_subagent()One-shot specialist agents for isolated tasks.
Teammate rolesnative.define_agent_teammate_role()Reusable roles for multi-agent teams.
Skillsnative.define_agent_skill()Slash commands for common workflows.

Loading built-in tools

Load the built-in agent tools (Git status, Git log, file analysis, etc.) in your workspace:
# WORKSPACE.rbs
load("@rbs//agent/tools.rbs", "AGENT_TOOLS_LOADED")

Custom tools

Basic tool with a shell command

native.define_agent_tool(
    name = "list_processes",
    description = "List running processes on the system",
    shell_cmd = "ps aux | head -20",
)

Tool with parameters

native.define_agent_tool(
    name = "search_code",
    description = "Search for a pattern in source files",
    attrs = {
        "pattern": attr.string(
            doc = "The regex pattern to search for",
            mandatory = True,
        ),
        "file_type": attr.string(
            doc = "File extension to search (e.g., 'py', 'java')",
            default = "",
        ),
    },
    shell_cmd = "rg '{pattern}' --type {file_type}",
)

Tool with implementation function

For complex logic, use an implementation function with full ctx access:
def _analyze_file_impl(ctx):
    """Analyze a file and return statistics."""
    path = ctx.attr.path

    # Read file content
    content = ctx.file.read(path)
    if content == None:
        return ctx.output.error("File not found: " + path)

    # Calculate statistics
    lines = content.split("\n")
    line_count = len(lines)
    char_count = len(content)

    # Run additional analysis
    result = ctx.shell.capture(command = "wc -w " + path)
    word_count = result.strip().split()[0] if result else "0"

    return ctx.output.success(
        message = "File analyzed",
        data = {
            "path": path,
            "lines": line_count,
            "characters": char_count,
            "words": word_count,
        },
    )

native.define_agent_tool(
    name = "analyze_file",
    description = "Analyze a file and return statistics (line count, word count, etc.)",
    attrs = {
        "path": attr.string(
            doc = "Path to the file to analyze",
            mandatory = True,
        ),
    },
    implementation = _analyze_file_impl,
)

The ctx object for tools

When using implementation, your function receives a ctx object with these modules:

ctx.attr — Access parameters

def _my_tool(ctx):
    path = ctx.attr.path           # String attribute
    count = ctx.attr.count         # Integer attribute
    files = ctx.attr.files         # List attribute
    options = ctx.attr.options     # Dict attribute

ctx.file — File operations

def _my_tool(ctx):
    content = ctx.file.read("path/to/file.txt")       # Read a file
    ctx.file.write("output.txt", "Hello World")        # Write a file
    exists = ctx.file.exists("config.json")            # Check existence
    files = ctx.file.glob("src/**/*.py")               # List files matching pattern

ctx.dir — Directory operations

def _my_tool(ctx):
    entries = ctx.dir.list("src/")                     # List directory
    ctx.dir.create("output/reports")                   # Create directory
    exists = ctx.dir.exists("build/")                  # Check existence
    ctx.dir.delete("tmp/", recursive = True)           # Delete directory

ctx.shell — Execute commands

def _my_tool(ctx):
    output = ctx.shell.capture(command = "git status --short")     # Capture output
    exit_code = ctx.shell.run(command = "make build")              # Run without capturing
    output = ctx.shell.capture(command = "npm test", cwd = "frontend/") # With working dir

ctx.path — Path manipulation

def _my_tool(ctx):
    full_path = ctx.path.join("src", "main", "app.py")    # Join paths
    dir_name = ctx.path.dir("src/main/app.py")             # Get directory
    base = ctx.path.base("src/main/app.py")                # Get base name
    ext = ctx.path.ext("app.py")                           # Get extension
    abs_path = ctx.path.abs("relative/path")               # Absolute path

ctx.json — JSON operations

def _my_tool(ctx):
    data = ctx.json.parse('{"name": "test", "count": 42}')    # Parse JSON
    json_str = ctx.json.stringify({"result": "success"})       # Serialize
    pretty = ctx.json.stringify(data, indent = 2)              # Pretty print

ctx.output — Return results

def _my_tool(ctx):
    # Success with message and data
    return ctx.output.success(
        message = "Found 5 files",
        data = {"files": ["a.py", "b.py"], "count": 5},
    )

    # Error
    return ctx.output.error("File not found: " + path)

Custom prompts

Create specialized system prompts with linked tools:
native.define_agent_prompt(
    name = "code_reviewer",
    system_prompt = """You are a code review expert. Your role is to:
1. Analyze code for bugs and issues
2. Suggest improvements and best practices
3. Check for security vulnerabilities
4. Ensure code follows project conventions

Always explain your reasoning and provide specific examples.""",
    tools = [
        "read_files",
        "code_search",
        "analyze_file",
    ],
)

Custom subagents

Create subagent types for one-shot, isolated specialist tasks:
native.define_agent_subagent(
    name = "security_auditor",
    description = "Performs security analysis of code changes",
    when_to_use = "When reviewing code for security vulnerabilities",
    system_prompt = """You are a security auditor. Analyze the provided code for:
1. SQL injection vulnerabilities
2. XSS attack vectors
3. Authentication bypass risks
4. Sensitive data exposure
5. Dependency vulnerabilities

Report findings with severity levels and remediation steps.""",
    tools = ["read_files", "code_search", "run_terminal_command"],
    read_only = True,
)
ParameterTypeRequiredDescription
namestringYesUnique subagent name.
descriptionstringYesWhat the subagent does.
when_to_usestringNoGuidance for when to invoke this subagent.
system_promptstringYesSystem prompt for the subagent.
toolsstring listNoTools the subagent can use.
read_onlyboolNoIf True, the subagent cannot modify files.

Custom teammate roles

Create reusable roles for multi-agent teams:
native.define_agent_teammate_role(
    name = "ml_engineer",
    description = "Machine learning specialist for model training and optimization",
    system_prompt = """You are an ML engineer specializing in:
- PyTorch and distributed training (DDP, FSDP)
- Model architecture design and optimization
- Training pipeline development
- Hyperparameter tuning and experiment tracking

Always validate training code with small samples before full runs.""",
    tools = ["read_files", "write_file", "run_terminal_command", "code_search"],
    capabilities = ["model_training", "data_pipelines", "optimization"],
)
ParameterTypeRequiredDescription
namestringYesRole name (used in spawn_teammate).
descriptionstringYesWhat this role specializes in.
system_promptstringYesSystem prompt for teammates with this role.
toolsstring listNoTools available to this role.
capabilitiesstring listNoDeclared capabilities for role matching.
Custom roles are automatically available when spawning teammates:
rbs agent "Spawn an ml_engineer teammate to optimize the training pipeline"

Custom skills (slash commands)

Create slash commands for common workflows:
native.define_agent_skill(
    name = "deploy",
    description = "Deploy the application to staging or production",
    usage = "/deploy [staging|production]",
    prompt = """Deploy the application. Steps:
1. Run all tests
2. Build the production binary
3. Push to the container registry
4. Apply Kubernetes manifests
5. Verify the deployment is healthy

Target environment: {args}""",
    tools = ["run_terminal_command", "read_files"],
)
ParameterTypeRequiredDescription
namestringYesSkill name (becomes a /command).
descriptionstringYesWhat the skill does.
usagestringNoUsage example.
promptstringYesPrompt template. Use {args} for user input.
toolsstring listNoTools the skill can use.

Long-term memory

The coding agent has a persistent memory system that remembers things across sessions — design patterns, user corrections, file-specific notes, and architectural decisions. Memory is stored as human-editable markdown in .rbs/memory/.

How memory works

Session Start
  ├─→ Load rules (.agent-rules/, .cursorrules, etc.)
  ├─→ Load general memory (.rbs/memory/general.md)
  └─→ Agent loop
        ├─→ read_files("src/api.py")
        │     → File content returned
        │     + .rbs/memory/src/api.py.md (auto-injected)
        │     + .rbs/memory/src/_package.md (auto-injected)
        ├─→ Agent discovers pattern → memory_save(...)
        ├─→ User corrects agent → agent saves correction
        └─→ Session ends → memory persists

Memory file format

Memory files are plain markdown, stored alongside your code:
.rbs/memory/
├── general.md                   # Project-wide notes
├── src/
│   └── api.py.md                # Notes about src/api.py
└── services/
    └── auth/
        ├── _package.md          # Notes about the auth package
        └── handler.py.md        # Notes about handler.py
Each entry uses ## [m-xxxxxxxx] headers:
# General Project Memory

## [m-abc12345] 2026-02-17 10:30
> Tags: architecture, convention

This project uses dependency injection throughout.
Never create singletons — pass dependencies through constructors.

## [m-def67890] 2026-02-17 14:15
> Tags: user-correction

User correction: Use interfaces for the repository pattern,
not concrete implementations.

Memory tools

ToolDescription
memory_saveSave a new memory entry.
memory_recallRecall memories for a file or package.
memory_updateUpdate an existing entry by ID.
memory_deleteDelete a stale or incorrect entry.
memory_searchSearch across all memory files.
memory_listList all stored memory files.

Editing memory directly

Memory files are just markdown. You can read, edit, add, and delete entries directly in your editor — the agent respects your changes.
## [m-user0001] 2026-02-17 16:00
> Tags: convention

We use dependency injection throughout the project.
Never create singletons or global state.

Complete example

# WORKSPACE.rbs
load("@rbs//agent/tools.rbs", "AGENT_TOOLS_LOADED")

# Custom tool: Check test coverage
def _coverage_check_impl(ctx):
    result = ctx.shell.capture(command = "rbs test //... --coverage")
    if result == None:
        return ctx.output.error("Failed to run tests")

    return ctx.output.success(
        message = "Coverage report generated",
        data = {"output": result},
    )

native.define_agent_tool(
    name = "check_coverage",
    description = "Run tests and check code coverage",
    implementation = _coverage_check_impl,
)

# Custom tool: Generate changelog
native.define_agent_tool(
    name = "changelog",
    description = "Generate changelog from recent git commits",
    attrs = {
        "since": attr.string(
            doc = "Git ref to start from (e.g., 'v1.0.0', 'HEAD~10')",
            default = "HEAD~10",
        ),
    },
    shell_cmd = "git log --oneline {since}..HEAD",
)

# Custom prompt for this project
native.define_agent_prompt(
    name = "project_assistant",
    system_prompt = """You are an assistant for this codebase.

Key conventions:
- Write tests for all new code
- Use dependency injection
- Update CHANGELOG.md for changes

When making changes, always run tests first.""",
    tools = [
        "read_files",
        "write_file",
        "str_replace",
        "run_terminal_command",
        "check_coverage",
        "changelog",
    ],
)

# Custom teammate role
native.define_agent_teammate_role(
    name = "data_engineer",
    description = "Data pipeline and preprocessing specialist",
    system_prompt = "You build efficient data loading and preprocessing pipelines.",
    tools = ["read_files", "write_file", "run_terminal_command"],
    capabilities = ["data_loading", "preprocessing", "augmentation"],
)

# Custom skill
native.define_agent_skill(
    name = "release",
    description = "Create a release",
    usage = "/release [version]",
    prompt = "Create release {args}: run tests, update changelog, tag, build, and publish.",
    tools = ["run_terminal_command", "read_files", "write_file"],
)

Attribute types reference

TypeDescriptionExample
attr.string()String value."hello"
attr.int()Integer value.42
attr.bool()Boolean value.True
attr.string_list()List of strings.["a", "b"]
attr.label()Build target label."//pkg:target"
attr.label_list()List of labels.["//a:x", "//b:y"]
attr.string_dict()String dictionary.{"key": "value"}
All attribute types support:
  • doc (string) — Description shown to the AI model.
  • mandatory (bool) — Whether the parameter is required.
  • default (any) — Default value.

Best practices

  1. Clear descriptions — Write descriptions that help the AI model understand when to use the tool.
  2. Document parameters — Use doc for all attributes.
  3. Handle errors — Always check for errors and return meaningful messages.
  4. Keep tools focused — Each tool should do one thing well.
  5. Define roles for your team — Create custom teammate roles matching your project needs.
  6. Use subagents for isolation — Use read-only subagents for analysis tasks.
  7. Create skills for workflows — Turn common multi-step workflows into slash commands.