The SMF Works Project — Where AI Meets Humanity

Agent Development Guide

Build custom agent types for smf-forge. Extend BaseAgent, implement the run method, and register your agent for use in pipelines.

Agent Architecture

Every agent in smf-forge inherits from BaseAgent and implements a single async method: run(prompt, context). The engine calls this method when executing a pipeline step, passing the rendered prompt and the current pipeline context.

from smf_forge.agents import BaseAgent, AgentConfig

class MyCustomAgent(BaseAgent):
    async def run(self, prompt: str, context: dict | None = None) -> dict:
        # Your logic here
        return {"result": "processed", "agent": self.config.name}

Building a Custom Agent

1. Subclass BaseAgent

Create a new Python file and define your agent class. The only requirement is implementing the async run method.

# my_agents.py
from smf_forge.agents import BaseAgent, AgentConfig, AGENT_TYPES

class DatabaseAgent(BaseAgent):
    """Query a database and return results."""

    async def run(self, prompt: str, context: dict | None = None) -> dict:
        # Read config options
        db_url = self.config.options.get("db_url", "sqlite:///default.db")

        # Your database logic here
        results = await query_database(db_url, prompt)

        return {
            "rows": results,
            "count": len(results),
            "agent": self.config.name,
        }

# 2. Register your agent type
AGENT_TYPES["database"] = DatabaseAgent

2. Register the Agent Type

Add your agent class to the AGENT_TYPES dictionary so the config loader can instantiate it by type name.

# After your class definition:
AGENT_TYPES["database"] = DatabaseAgent

3. Configure in forge.yaml

Use your new type name in the agents section of your config.

agents:
  my-db:
    type: database
    options:
      db_url: "sqlite:///myapp.db"

pipelines:
  query:
    name: query
    steps:
      - name: lookup
        agent: my-db
        prompt: "{{ prompt }}"

Context Passing

The pipeline engine passes context between steps. The output of each step is stored in the context dict under the step name, making it available to downstream steps via Jinja2 template rendering.

# Step output format determines what downstream steps can reference:

# Step "research" returns:
#   { "response": "...", "agent": "researcher" }

# Step "summarize" can reference it with:
#   prompt: "Summarize: {{ research.response }}"

# The context dict after "research" runs:
#   { "research": { "response": "...", "agent": "researcher" } }

Error Handling

Agents can signal failures in two ways:

Raise an exception

The engine catches any exception and marks the step as FAILED with the error message.

raise ValueError("Database connection failed")

Return an error dict

Return a dict with an "error" key and no "response" key. The engine detects this pattern and marks the step FAILED.

return {"error": "No API key configured", "agent": self.config.name}

Next Steps