Agents are AI-powered assistants that automate tasks on your GitLab instance. Each agent specializes in a specific workflow (code review, pipeline debugging, etc.) and is orchestrated by the Codeward runtime.
Webhook Event (MR / Note / Pipeline / Job) Slash command on issue/MR
│ │
▼ ▼
┌──────────┐ ┌────────────┐ ┌───────────────┐ ┌──────────────┐
│ Gateway │────▶│Orchestrator│────▶│ Agent Runner │ │ Flow Runner │
│ router.py│ │ │ │ (Agno Agent) │◀──│ (Redis) │
└──────────┘ └────────────┘ └──────┬────────┘ └──────────────┘
│ │
┌─────┴──────┐ ┌────┴───────────┐
│ • Match │ │ Agent Toolset │
│ • Cooldown │ │ ┌────────────┐ │
│ • Rate lim │ │ │GitLabToolk.│ │
│ • Context │ │ ├────────────┤ │
│ • Record │ │ │External │ │
│ run │ │ │MCP tools │ │
└────────────┘ │ │(per agent) │ │
│ └────────────┘ │
└────────────────┘
-
Gateway receives a GitLab webhook and dispatches to the orchestrator as a background task.
-
Orchestrator loads the project config (
.codeward.yml), matches the event to agents, checks cooldowns/rate limits, builds context viaContextEngine, then runs each matched agent. -
Context Engine builds an
AgentContextappropriate to the event type — MR diffs for code review, parsed job logs for pipeline debugging, security report artifacts for triage, etc. -
Agent is an Agno
Agentwhose toolset is the union of:GitLabToolkit— posting inline comments, notes, labels, and fetching files from the GitLab REST API.- External MCP tools — any tools exported by the MCP servers the agent declares under
mcp_servers:(see Using External MCP Servers from Agents). These are merged in byMCPConnectionManager.get_tools_for_agentincodeward/mcp_client/manager.py.
The agent returns a structured result (
ReviewResult,PipelineDebugResult, orSecurityTriageResult) for the summary. -
Flow Runner (separate path, in
codeward/flows/runner.py) is invoked by slash commands like@codeward /full-reviewand chains multiple agent runs and GitLab actions through a Redis-backed state store. See Multi-Agent Flows below.
Automatically reviews merge request diffs when an MR is opened or updated.
Triggers: merge_request.open, merge_request.update
Model alias: code (configurable in codeward.yml)
Skips: Draft MRs, WIP MRs (configurable)
Cooldown: 120 seconds between re-reviews on the same MR
What it does:
- Posts inline diff comments on specific lines using the
post_inline_commenttool - Posts a summary note with a severity table
- Adds/removes labels based on findings
- Respects
skip_pathsandreview_rulesfrom the project's.codeward.yml
Responds to @codeward mentions in MR comments and issue discussions.
Triggers: note (with @codeward mention required)
Model alias: strong
Cooldown: N/A (conversation-driven)
What it does:
- Answers questions about the MR diff, project code, or CI pipeline
- Supports slash commands:
/review,/debug,/security - Maintains conversation context via the conversation store
- Replies in the same discussion thread
Automatically diagnoses CI/CD pipeline failures by analyzing job logs and .gitlab-ci.yml.
Triggers: pipeline.failed
Model alias: strong
Cooldown: 300 seconds
Slash command: @codeward /debug
What it does:
- Analyzes failed job logs using the log parser (error classification, context extraction)
- Fetches and reviews
.gitlab-ci.ymlfor misconfigurations - Classifies errors by type: test failure, dependency, docker, timeout, syntax, permission, network, config
- Extracts exit codes and identifies flaky failures
- Correlates failures with the MR diff (if a merge request triggered the pipeline)
- Posts a structured summary note with per-job root cause analysis and fix suggestions
- Falls back to posting a commit comment when no MR is associated
Output schema: PipelineDebugResult — summary, is_flaky flag, per-job analyses (FailedJobAnalysis), suggested actions.
Triages findings from GitLab security scanners (SAST, DAST, dependency scanning, secret detection, container scanning).
Triggers: build.success (filtered by job_names)
Job names: semgrep-sast, sast, bandit-sast, gemnasium-dependency_scanning, dependency_scanning, dast, secret_detection, container_scanning
Model alias: strong
Cooldown: 600 seconds
Slash command: @codeward /security
What it does:
- Fetches security report artifacts from completed scan jobs (e.g.,
gl-sast-report.json) - Parses GitLab's standard security report format (v14.x/v15.x)
- Prioritizes findings by severity (Critical > High > Medium > Low)
- Identifies false positives based on common scanner patterns
- Posts a structured triage report with risk level, confirmed findings, and false positive justifications
- Applies labels when enabled (e.g.,
security::high,security::critical)
Opt-in: Auto-triggering is opt-in per project. Add security-triage to enabled_agents in .codeward.yml to activate:
codeward:
enabled_agents:
- code-reviewer
- security-triageOutput schema: SecurityTriageResult — summary, risk level, per-finding triage (SecurityFindingResult), false positive count, label suggestions.
Agents are defined as YAML files in the directory specified by CODEWARD_AGENTS_DIR (default: ./agents).
# Required
name: my-agent # Unique identifier
type: my-agent # Agent type — maps to a CodewardAgent subclass (see dispatch.py)
version: "1.0" # Semver string
description: > # What the agent does
Human-readable description.
# When to trigger
trigger:
events: # List of "event_type.action" strings
- merge_request.open # MR opened
- merge_request.update # MR updated (new commits)
- pipeline.failed # Pipeline finished with failure
- build.success # Job (build) completed successfully
- note # Comment/note posted (usually with mention)
job_names: # Filter build events to specific job names (optional)
- semgrep-sast
- sast
cooldown: 120 # Seconds between re-runs on the same target
skip_draft: true # Skip draft MRs (MR events only)
skip_wip: true # Skip WIP-prefixed MRs (MR events only)
# LLM model
model:
default: code # Model alias from codeward.yml
# What context the agent needs
context:
needs_diff: true
needs_file_context: true
needs_pipeline_logs: false
needs_ci_config: false
needs_security_reports: false
max_files: 15
# System prompt template
# Available variables: {project_path}, {project_rules}, {source_branch}, {target_branch}
system_prompt: |
You are an expert code reviewer for {project_path}.
...
# Output schema (for structured response)
output:
format: json
# What actions the agent is allowed to perform
actions:
inline_comments: true # Post inline diff comments
summary_note: true # Post a summary comment
labels: true # Add/remove labels
approve: false # Auto-approve MRs (use with caution)
# Optional: external MCP servers whose tools should be exposed to this agent.
# Each name must match a key in `codeward.mcp_servers` in codeward.yml.
mcp_servers:
- context7
- sentry| Event | object_kind |
Actions | Description |
|---|---|---|---|
| Merge Request | merge_request |
open, update, close, reopen, merge |
MR lifecycle events |
| Note | note |
— | Comments on MRs, issues, commits. Use mention: true to require @codeward |
| Pipeline | pipeline |
failed, success, running |
Pipeline status changes |
| Job (Build) | build |
success, failed |
Individual CI job completion. Use job_names to filter |
| Push | push |
— | Code pushed to a branch |
Agents can be granted tools from any external Model Context Protocol server (e.g. Context7, Sentry, a vendor-specific knowledge base) without writing any Python. The flow is:
- Declare the server under
codeward.mcp_serversincodeward.yml. - Reference it from the agent YAML's
mcp_servers:list. - At agent run time,
MCPConnectionManager(incodeward/mcp_client/manager.py) opens the connection (lazily, cached, with health checks) and merges the server's tools into the Agno agent's toolset alongsideGitLabToolkit.
The authoritative shape lives in
codeward/mcp_client/registry.py (MCPServerConfig).
codeward:
mcp_servers:
context7:
url: https://mcp.context7.com/mcp
transport: streamable-http # streamable-http | sse
auth_type: bearer # bearer | header | none
auth_token_env: CONTEXT7_TOKEN # env var holding the bearer token
enabled: true
description: Context7 documentation lookups
internal-kb:
url: https://kb.internal/mcp
transport: streamable-http
auth_type: header
headers:
X-API-Key: ${INTERNAL_KB_KEY} # ${VAR} expansion is supported| Field | Type | Notes |
|---|---|---|
url |
string | The MCP endpoint. ${ENV_VAR} is expanded from the process environment. |
transport |
string | streamable-http (default) or sse. |
auth_type |
string | bearer, header, or none. |
auth_token_env |
string | For bearer: the env var that holds the token. |
headers |
mapping | For header: the literal headers to send. ${ENV_VAR} is expanded. |
enabled |
bool | Disable a server without removing the entry. Defaults to true. |
description |
string | Free-form, surfaced in logs. |
Projects can replace the MCP server list per agent through their
.codeward.yml:
codeward:
mcp_servers:
chat: # agent name
- context7 # only context7 for the chat agent in this repoIf mcp_servers is set for an agent in a project's .codeward.yml,
that list completely replaces the agent-level list (it is not
merged). See MCPConnectionManager.get_tools_for_agent for the exact
precedence.
Codeward also acts as an MCP server itself, mounted at /mcp by
codeward/main.py and built with FastMCP in
codeward/mcp_server/server.py. Any MCP-aware client (Claude Desktop,
Cursor, an Agno agent in another project, etc.) can connect to it and
use Codeward as a turn-key GitLab tool provider.
Highlights:
- Transport: streamable HTTP at
http://<host>:8420/mcp. - Auth: optional bearer token. Set
CODEWARD_MCP_KEYin.envand theMCPAuthMiddlewarewill reject requests without the matchingAuthorization: Bearer …header. If unset, the mount is open. - Tools (
codeward/mcp_server/tools.py):list_merge_requests,get_merge_request,get_merge_request_diff,post_merge_request_comment,list_issues,get_issue,create_issue,get_file_content,get_repository_tree,search_code,get_pipeline_status,get_failed_pipeline_logs,list_project_labels. - Resources (
codeward/mcp_server/resources.py):gitlab://project/{path}/readme,gitlab://project/{path}/ci-config,gitlab://project/{path}/merge-requests/open,gitlab://project/{path}/issues/open,gitlab://project/{path}/pipelines/latest. - Prompts (
codeward/mcp_server/prompts.py):review_merge_request,explain_pipeline_failure,summarize_issue.
See docs/mcp.md for client configuration examples and a worked walkthrough.
A flow is a YAML-defined sequence of agent runs and GitLab API
actions, executed by the Redis-backed worker (make worker). Flows are
how Codeward implements the slash commands that touch more than one
agent — e.g. running a code review, then a security triage, then
posting a combined summary.
Built-in flows in agents/flows/:
| Flow | Trigger | Steps |
|---|---|---|
full-review |
@codeward /full-review on an MR |
code-reviewer → security-triage (skipped on failure) → combined summary comment |
issue-to-mr |
@codeward /implement on an issue |
analyse issue → plan comment → branch → generate code → self-review → open MR → notify |
pipeline-fix |
@codeward /fix-pipeline on an MR |
pipeline-debugger → diagnosis comment |
Flows differ from individual agents in three ways:
- They are command-driven, not webhook-driven (each flow declares a
slash command in its YAML
trigger:block). - They have state that survives across steps, persisted to Redis
(
codeward/flows/state.py). Subsequent steps can reference earlier outputs via{step_name.field}template placeholders. - They run in the flow worker process (
make worker), not inline in the webhook handler — so the GitLab webhook returns immediately while the flow continues asynchronously. Track progress viaGET /flows/{flow_id}.
Full reference and YAML schema: docs/flows.md.
Agents have access to a GitLabToolkit with the following tools during LLM execution:
| Tool | Description | Parameters |
|---|---|---|
post_inline_comment |
Post a review comment on a specific diff line | file, line, line_type (new/old), body, suggestion |
post_note |
Post a general comment on the MR | body |
add_label |
Add a label to the MR | label |
remove_label |
Remove a label from the MR | label |
get_file_content |
Fetch a file from the repository | file_path, ref |
The toolkit automatically constructs GitLab position dicts for inline comments using the MR's diff_refs (base_sha, head_sha, start_sha). If positioning fails, it falls back to posting a general note.
Projects can customize Codeward behavior by placing a .codeward.yml file in their repository root:
# Top-level or nested under "codeward:" key
codeward:
# Control which agents run
enabled_agents: # If set, only these agents are allowed
- code-reviewer
disabled_agents: # Agents to disable
- security-scanner
# Code review customization
review_rules: # Appended to the review agent's prompt
- "Always use type hints for function signatures"
- "Prefer dataclasses over plain dicts for structured data"
- "All public functions must have docstrings"
skip_paths: # Glob patterns for files to exclude from review
- "*.generated.py"
- "vendor/**"
- "migrations/**"The config is cached for 5 minutes per project to avoid API spam.
Rate limits are configured in codeward.yml and enforced via the agent_runs database table:
| Limit | Default | Description |
|---|---|---|
per_project_per_hour |
30 | Max agent runs per project per hour |
per_user_per_hour |
20 | Max agent runs per user per hour |
global_per_minute |
10 | Max agent runs globally per minute |
Cooldown prevents the same agent from re-running on the same MR within a configurable window (default: 120 seconds). Only successful runs count toward cooldown.
Codeward prevents infinite loops by checking is_bot_event() in the gateway:
- Events authored by the bot username (
CODEWARD_BOT_USERNAME) are skipped - MR updates where the last commit author matches the bot are skipped
When a developer pushes new commits to an MR that was already reviewed:
- The orchestrator checks
ReviewStatefor the MR (last reviewed SHA + discussion IDs) - If the SHA is unchanged, the review is skipped entirely
- If the SHA changed, an inter-diff is fetched via GitLab's
/repository/compareAPI - Only the new changes are sent to the LLM for review
- Previous bot discussions on modified files are auto-resolved with a note:
"This appears to have been addressed in the latest push. Resolving automatically." - The summary note indicates it's an incremental review with the count of resolved threads
- If the inter-diff fetch fails, Codeward falls back to a full review
Thread resolution uses file-level matching: if the file referenced by a bot discussion was modified in the new push, the issue is assumed addressed.
If an agent fails during execution, the error is classified:
| Status | Trigger | User Note |
|---|---|---|
timeout |
Agent exceeds configurable timeout (default: 120s) | "Timed out -- diff may be too large" |
rate_limit |
LLM provider returns 429 or rate limit error | "Rate limit hit -- will retry on next push" |
error |
Any other exception | Error type and message shown |
The agent_runs table records the specific status (timeout, rate_limit, or error) with the error message. The orchestrator continues to the next matched agent.
-
Create a YAML file in the
agents/directory (orCODEWARD_AGENTS_DIR):agents/my-custom-agent.yml -
Define triggers, model, system prompt, and actions (see schema above).
-
Restart Codeward - the registry loads all
*.ymlfiles at startup. -
The agent will automatically trigger on matching events. Use
.codeward.ymlin projects to enable/disable it per-repository.
| File | Purpose |
|---|---|
codeward/agents/base.py |
CodewardAgent base class - wraps Agno Agent, handles structured output |
codeward/agents/code_reviewer.py |
CodeReviewAgent - MR diff review |
codeward/agents/chat.py |
ChatAgent - conversational @codeward mentions |
codeward/agents/pipeline_debugger.py |
PipelineDebuggerAgent - CI failure diagnosis |
codeward/agents/security_triage.py |
SecurityTriageAgent - security scan triage |
codeward/agents/models.py |
ReviewResult, PipelineDebugResult, SecurityTriageResult, AgentResult |
codeward/agents/registry.py |
AgentRegistry - loads YAML definitions, TriggerSpec with job_names |
codeward/agents/dispatch.py |
Agent type -> class mapping |
codeward/agents/tools/gitlab_tools.py |
GitLabToolkit - Agno tools for GitLab |
codeward/context/engine.py |
ContextEngine - builds AgentContext for each event type |
codeward/context/log_parser.py |
CI log parsing - error classification, context extraction |
codeward/context/security_report.py |
GitLab security report parsing (SAST, DAST, dependency) |
codeward/orchestrator/orchestrator.py |
Orchestrator - event dispatch + result posting |
codeward/orchestrator/matcher.py |
Event-to-agent matching (MR, Note, Pipeline, Job) |
codeward/orchestrator/project_config.py |
Per-repo .codeward.yml loader |
codeward/flows/runner.py |
FlowRunner — executes a flow's steps, applies templates and conditions |
codeward/flows/loader.py |
Loads flow YAML definitions from agents/flows/ |
codeward/flows/state.py |
Redis-backed flow state store |
codeward/worker/app.py |
Background worker process started by make worker |
codeward/mcp_server/server.py |
FastMCP server + MCPAuthMiddleware, mounted at /mcp from main.py |
codeward/mcp_server/{tools,resources,prompts}.py |
Registered tools / resources / prompts exposed to MCP clients |
codeward/mcp_client/registry.py |
MCPRegistry and MCPServerConfig — parses codeward.mcp_servers |
codeward/mcp_client/manager.py |
MCPConnectionManager — lazy connect, caching, per-agent tool merge |