Skip to main content
This page documents proven patterns for building OpenGAP agents. These aren’t rules — they’re recommended starting points for common architectures.

Pattern 1: Single-purpose agent

The simplest pattern. One agent, one job.
customer-support-agent/
├── agent.yaml
└── skills/
    ├── lookup_order.py
    ├── check_shipping.py
    ├── submit_refund.py
    └── escalate_to_human.py
When to use: When the agent has a well-defined, bounded scope. Customer support, code reviewer, data extractor. Key principle: Keep the skill set minimal. An agent with 4 focused skills makes better decisions than one with 20 broad skills.

Pattern 2: Orchestrator + worker agents

A top-level orchestrator calls specialized sub-agents as skills.
research-pipeline/
├── agent.yaml              ← orchestrator
├── skills/
│   ├── run_researcher.py   ← calls researcher/ sub-agent
│   ├── run_writer.py       ← calls writer/ sub-agent
│   └── run_editor.py       ← calls editor/ sub-agent
└── agents/
    ├── researcher/
    │   ├── agent.yaml
    │   └── skills/
    ├── writer/
    │   ├── agent.yaml
    │   └── skills/
    └── editor/
        ├── agent.yaml
        └── skills/
When to use: When different phases of work require different specializations. Research → write → edit. Fetch → transform → load. Key principle: The orchestrator’s system prompt describes when to use each sub-agent, not what the sub-agent does internally.
# skills/run_researcher.py
from gitagent import skill
from gitagent.tools import call_agent

@skill(
    name="run_researcher",
    description="Research a topic deeply. Use this first, before writing."
)
def run_researcher(topic: str) -> str:
    result = call_agent(agent_path="./agents/researcher", prompt=topic)
    return result.output

Pattern 3: Flow-first agent

Use SkillsFlow for the primary execution path; use the LLM loop for exception handling.
data-pipeline-agent/
├── agent.yaml
├── skills/
│   ├── fetch_data.py
│   ├── transform.py
│   └── load.py
└── flows/
    └── etl.yaml            ← primary execution path
When to use: When the sequence of steps is known in advance (ETL pipelines, report generation, scheduled jobs). The LLM loop is still available for unrecognized requests. Key principle: The flow handles the happy path deterministically. The LLM handles edge cases and unexpected inputs.

Pattern 4: Memory-augmented assistant

An agent that learns from interactions and personalizes over time.
personal-assistant/
├── agent.yaml
├── skills/
│   ├── manage_tasks.py
│   ├── search_calendar.py
│   └── send_message.py
└── hooks/
    └── persist_preferences.py   ← writes learned preferences to memory
# hooks/persist_preferences.py
from gitagent import hooks
from gitagent.tools import memory_write

PREFERENCE_PATTERNS = {
    "prefers bullet points": "format.bullets",
    "keep it brief": "format.length.short",
    "be formal": "format.tone.formal",
}

@hooks.after_run
def extract_preferences(context):
    output_lower = context.output.lower()
    for pattern, key in PREFERENCE_PATTERNS.items():
        if pattern in output_lower:
            memory_write(key, True)
When to use: Personal assistants, customer-facing agents with repeat users, agents that should improve with use.

Pattern 5: Plugin-extended runtime

Extend the runtime with a plugin rather than adding logic to skills.
enterprise-agent/
├── agent.yaml
├── skills/
│   └── answer_question.py
└── plugins/
    ├── sso_auth.py         ← injects enterprise SSO token
    ├── audit_logger.py     ← logs all skill calls to SIEM
    └── pii_redactor.py     ← redacts PII from skill outputs
When to use: Cross-cutting concerns (auth, logging, security) that apply to all skills. Keeps skill code clean. Key principle: If a concern applies to all skills, it belongs in a plugin or hook, not duplicated in each skill.

Pattern 6: Test-driven agents

Write tests for skills before implementing them. Run tests in CI on every push.
tdd-agent/
├── agent.yaml
├── skills/
│   ├── extract_entities.py
│   └── classify_intent.py
└── tests/
    ├── test_extract_entities.py
    ├── test_classify_intent.py
    └── conftest.py          ← shared fixtures
# tests/test_classify_intent.py
import pytest
from skills.classify_intent import classify_intent

@pytest.mark.parametrize("text,expected", [
    ("I want to return my order", "return"),
    ("Where is my package?", "tracking"),
    ("I'd like to cancel", "cancel"),
])
def test_intent_classification(text, expected):
    result = classify_intent(text)
    assert result["intent"] == expected
# .github/workflows/test.yml
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install gitagent pytest
      - run: gitagent test

Anti-patterns

Too many skills — More than ~15 skills degrades LLM routing accuracy. Group related actions into sub-agents or SkillsFlows. Skills with side effects in tests — Skills that send emails or write to databases should be mockable. Use dependency injection or the mock-skills flag in gitagent test. Secrets in agent.yaml — Never hardcode API keys. Always use ${ENV_VAR} references. Giant system prompts — A 2,000-word system prompt usually means the agent is trying to do too much. Split into specialized agents. Logic in hooks instead of skills — Hooks are for cross-cutting concerns (logging, auth, security). Business logic belongs in skills where it’s testable and explicit.