Project resolution strategies¶
Which surface reads this
The YAML on this page is the file format. On a server the same values live in the database and are edited in the Config studio (the project drawer's routing tab); the CLI reads them from agentsmith.yml. agent-smith config import moves one into the other. See Where configuration lives.
When a ticket event arrives, by webhook or by polling, Agent Smith has to answer one question: which project owns this ticket? Each project answers it with one resolution rule.
This page covers the four strategies (tag, area_path, repo, to_address), when to pick each, and a YAML example for each.
Where the rule lives¶
The short form sits on the project and names the strategy as its key:
projects:
todolist:
agent: default-claude
tracker: acme-jira
repos: [acme/todolist-api]
resolution:
tag: todolist
The long form sits inside a platform trigger block (github_trigger, gitlab_trigger, azuredevops_trigger, jira_trigger), for a project that overrides its tracker's workflow anyway:
Both produce the same rule. The trigger type always follows the tracker's type.
How resolution runs¶
- A ticket event arrives. The platform handler builds an
IncomingTicketEnvelopewith the ticket id, its labels, and, on a webhook, the area path (Azure DevOps) and the source repo URL where the platform sends one. ProjectResolverwalks every project's trigger and asks whether its rule matches the envelope.- Zero matches: a log line per project saying why it didn't match, and a tracker comment if the tracker has
zero_match_comment: true. One match: the ticket is claimed and runs. Two or more: every matching project claims and runs, and theagent_smith_ambiguous_resolution_totalcounter goes up once per matched (project, pipeline).
Within a matched project, the tracker's label map picks the pipeline. When the map has entries and none matches, the ticket is dropped for that project, not sent to a default.
Two things stop a match before any rule is asked. A ticket carrying the spec-epic or phase-epic label marks a record rather than work and is never routed. And a trigger that carries a blocking startup finding (a missing park status, say) doesn't start runs until the finding is fixed.
Polling builds its envelope from the ticket alone, which has labels but no area path and no source repo. So on the polling path only tag can match.
tag: the common one¶
A label on the ticket marks it for this project. The rule matches when any of the ticket's labels equals value, ignoring case.
When to use¶
The default choice. Works on all four trackers (GitHub and GitLab labels, Azure DevOps tags, Jira labels), on webhooks and on polling. It fits the shared-tracker pattern: one board holding work for many teams, each team's project picked out by a per-team tag.
YAML example¶
Three projects against one Jira site, each claiming tickets tagged with its own slug:
trackers:
shared-jira:
type: jira
url: https://acme.atlassian.net/
project: TL
auth: jira_token
open_states: ["To Do", "In Progress"]
done_status: "In Review"
pipeline_from_label:
bug: code
feature: code
projects:
todolist-backend:
agent: claude-default
tracker: shared-jira
repos: [acme/todolist-api]
resolution:
tag: todolist-backend
todolist-frontend:
agent: claude-default
tracker: shared-jira
repos: [acme/todolist-web]
resolution:
tag: todolist-frontend
todolist-sdk:
agent: claude-default
tracker: shared-jira
repos: [acme/todolist-sdk]
resolution:
tag: todolist-sdk
Worked example¶
A Jira issue carries the labels bug and todolist-backend and is assigned to the Agent Smith user.
- The Jira webhook arrives at
/webhook/jira.JiraAssigneeWebhookHandlerconfirms the assignee and builds an envelope withLabels = ["bug", "todolist-backend"]. ProjectResolverfinds one match,todolist-backend.- The tracker's label map turns
bugintocode, and onecoderun starts fortodolist-backend.
Labelled bug, todolist-backend and todolist-sdk, the issue would have matched two projects and started two runs, and the ambiguity counter would have gone up twice.
area_path: Azure DevOps, webhooks¶
The work item's System.AreaPath matches value, hierarchically: a configured path matches itself and every path below it, ignoring case.
When to use¶
Azure DevOps organisations where one project hosts many teams' work and each team owns a subtree of the area-path hierarchy. Only the Azure DevOps webhook carries an area path; the other trackers, and polling, have none, so a project resolving by area path is reached by Azure DevOps webhooks only.
YAML example¶
trackers:
contoso-ado:
type: azure_devops
organization: contoso
project: ContosoMain
auth: ado_pat
open_states: ["New", "Active", "Committed"]
done_status: "In Review"
projects:
contoso-billing:
agent: claude-default
tracker: contoso-ado
repos: [billing-repo]
resolution:
area_path: 'ContosoMain\Billing'
Worked example¶
A work item filed under ContosoMain\Billing\Invoicing is updated.
- The
workitem.updatedwebhook arrives.AzureDevOpsWorkItemWebhookHandlerbuilds an envelope with that area path. ContosoMain\Billing\Invoicingis underContosoMain\Billing, socontoso-billingmatches.
A work item filed directly at ContosoMain\Billing matches too. One at ContosoMain\Shipping doesn't.
Pitfall, YAML escaping: backslash is the area-path separator. Write the value single-quoted with single backslashes,
'ContosoMain\Billing'. In double quotes every backslash has to be doubled,"ContosoMain\\Billing"./and\are treated alike, so'ContosoMain/Billing'works as well and dodges the problem.
repo: the ticket's own repo, single-repo projects¶
The ticket's source repo URL, as the webhook payload carries it, equals the URL of the project's only repository (compared ignoring case). The configured value names the rule; what is compared is the project's repo.
When to use¶
A single-repo GitHub or GitLab project whose issues are filed on the repo itself, where you'd rather not label anything. Webhooks only, since a polled ticket carries no source repo.
Validator constraint¶
repo needs a project with exactly one entry in repos:. A project with more is refused when the configuration loads:
Project 'X': github project_resolution.strategy=repo requires exactly one entry in repos (has 3). Use strategy=tag or area_path for multi-repo projects, since the webhook payload's repo URL alone cannot disambiguate which repo of the project the ticket belongs to.
YAML example¶
repos:
acme-cli:
type: github
url: https://github.com/acme-org/cli
auth: github_token
projects:
acme-cli:
agent: claude-default
tracker: acme-cli-issues # a GitHub tracker on the same repo
repos: [acme-cli]
resolution:
repo: https://github.com/acme-org/cli
Pitfall: the comparison is exact apart from case. Configure the repo with the same URL form the webhook sends (
https://github.com/acme-org/cli); a.gitsuffix or an SSH URL won't match.
to_address: reserved¶
The ticket's to-address equals value, ignoring case. The strategy parses and validates, but none of the four trackers delivers a to-address, so a project resolving by it matches nothing today.
See also¶
- Repos: multi-repo, the multi-repo project model.
- Metrics,
agent_smith_ambiguous_resolution_total. - agentsmith.yml reference, every project key.
- Webhooks, the ingress path that builds the envelope.
- Polling, the other ingress, where only
tagapplies.