Issue and project triage assessment and runbook
Date: 2026-08-21
Status: Current. The gap list from the morning assessment is implemented and covered by tests; this document records what ships, the pull-request product stance, and how to operate triage day to day.
Purpose
OpenAgents cut over to production, imported the first repository
(OpenAgentsInc/openagents.com), and opened the tracker for a public backlog:
anyone can read issues and, once signed in, file them. Maintainers triage.
Pull requests stay closed on this repository while the backlog forms, and the
product direction for them is written down below.
This document has three parts:
- What the issues-and-projects system implements now.
- The pull-request product stance and what deliberately remains unbuilt.
- A triage process and runbook you can operate today.
Part 1: What is implemented
Identity and authority
- Sign-in is GitHub OAuth. Namespaces are keyed to immutable GitHub numeric IDs; logins are mutable projections.
- Browser mutations use the signed session. API writes use a personal access
token (
oa_pat_…) with the exactforge:writescope, created and revoked at/settings/api-tokens; plaintext is shown once. - Repository roles are
owner,maintainer,contributor(writable), andviewer(read-only). Membership is explicit and managed through product surfaces, not consoles.
The participation model
Reading and writing split cleanly, GitHub-style:
| Who | Read issues | File issues | Comment | Triage (label, assign, milestone, close others') |
|---|---|---|---|---|
| Anonymous visitor | Public repositories | No | No | No |
| Signed in, not a member | Public repositories | Yes | Yes | No |
| An issue's author | Their issue | Already filed | Yes | Edit title/body, close, reopen their own |
| Repository member with write role | All visible repositories | Yes | Yes | Yes |
viewer member |
Including private repositories | Yes | Yes | No |
Rules the server enforces, regardless of what any client renders:
- Issue reading runs behind plain
:browser; the route ledger classifies itpublic_readwith scopeforge:issues:web. A hand-crafted event from a viewer without authority is refused with a flash message, never applied. - Filing requires an identity:
/issues/newstays behind sign-in, because an issue without an author cannot be triaged honestly. The author is recorded on the issue. - Every mutation re-checks authority in the view's event handlers. The templates hide controls for viewers who lack them, but hiding is courtesy, not security.
Membership management
/:owner/:repo/members, owner-only, lists members with roles and supports:
- Adding a member by GitHub login (the person must have signed in once).
- Changing roles across the four levels.
- Removing members.
- Last-owner protection: the final
ownercannot be demoted or removed.
Every change flows through OpenAgents.Repositories functions that write
audit records naming the actor, subject, action, and role. Non-owners are
redirected; non-members get the same quiet bounce as a nonexistent
repository, so the page never confirms who has access to what.
Repositories, Git transport, and code browsing
- Create, one-time GitHub import, list, and view work through the browser,
the API, and the published CLI (
@openagentsinc/clion npm). Provisioning runs through a durable outbox. - Git smart HTTP serves
https://openagents.com/{owner}/{repo}.git. Public repositories allow anonymous clone and fetch. Push requires repository membership with a write role, authenticated by PAT over HTTP Basic or the CLI credential helper. - Default labels seed automatically on every repository creation and import:
bug,documentation,duplicate,enhancement,good first issue,help wanted,invalid,question,wontfix.
Issue list ergonomics
The issue index has grown past open/closed tabs:
- Filters by label, assignee, and milestone, plus text search across titles and bodies (wildcards escaped; what you type is matched literally).
- Pagination at 25 issues per page with Previous and Next, and a row count.
- The Open and Closed tab counts respect the active filters, so the counts and the list always agree — they read the same query.
Live updates
Every committed issue write broadcasts on a per-repository topic. The index and detail views subscribe and re-read through the viewer's own authorization, so two people triaging together converge instead of drifting. The message carries the repository id and nothing else; subscribers re-read rather than trust payloads.
JSON API compatibility
Four pinned deviations from GitHub behavior were closed:
- Label URLs percent-encode as paths (
good%20first%20issue), and the advertised URL resolves through the show endpoint. PATCH .../labels/:namerenames throughnew_name.- Adding a nonexistent label to an issue creates it with a generated color, instead of returning 404.
- Removing a label an issue does not wear returns 404, instead of silently succeeding.
Issue creation with unknown labels remains strict (422); the add-to-issue endpoint creates on the fly, matching GitHub. Cross-repository isolation is unchanged: composite foreign keys still make cross-repo references impossible, and a name collision between repositories produces two independent labels rather than a link.
Analytics
PostHog captures issue_created, issue_updated, issue_commented,
label_created, milestone_created, project_created, and
project_item_added at the domain-context choke points.
Tests
The suite covers all of the above: the participation matrix for anonymous visitors, non-member filers, authors, and members; the members page including last-owner protection; filter, search, and pagination semantics; broadcast delivery; and each compatibility fix against the endpoint contract it restores.
Part 2: The pull-request product stance
Recorded direction, stated plainly so nobody has to rediscover it:
- Pull requests will exist as a general repository capability.
- They will be disable-able per repository.
- New repositories default to enabled.
OpenAgentsInc/openagents.comdefaults to disabled: contribution to this repository starts with issues, not patches.- Priority is explicitly lower than smooth issue interaction, which is why it is not part of this pass.
Nothing about that stance needs guarding in the meantime, because the Git
plane already behaves as if pull requests were switched off everywhere: push
requires a writable repository membership, so external participation is
issue-shaped by construction. When the feature lands, the per-repository
switch turns the existing membership gate into a policy knob instead of a
hard rule, and this repository's switch starts at off.
Deliberately not built yet, in priority order:
- Pull requests with a per-repository enable/disable switch.
- Notifications (email or mentions). Acceptable while the community is small and responses happen in chat; revisit before the first hundred external issues.
Part 3: Triage runbook
Roles
| Role | Can do |
|---|---|
owner |
Everything, including managing members |
maintainer |
Triage: label, assign, set milestones, close, edit any issue |
contributor |
Triage, file and comment on issues, and push Git changes |
viewer |
Read visible repositories and join their issue conversations |
One-time setup for this repository
The initial repository was imported before automatic label seeding shipped,
so give it the default vocabulary once. Either use the Labels page
(/:owner/:repo/labels) or run the equivalent calls:
TOKEN=oa_pat_your_token # created at /settings/api-tokens, forge:write scope
BASE=https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com/labels
while IFS='|' read -r name color; do
curl -s -X POST "$BASE" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\": \"$name\", \"color\": \"$color\"}" >/dev/null
done <<'LABELS'
bug|d73a4a
documentation|0075ca
duplicate|cfd3d7
enhancement|a2eeef
good first issue|7057ff
help wanted|008672
invalid|e4e669
question|d876e3
wontfix|ffffff
LABELS
Repositories created from now on get these labels automatically.
Then grant maintainer roles to everyone who will run triage:
- Each person signs in at
openagents.comonce with GitHub. - An owner opens
/:owner/:repo/members, adds their login with themaintainerrole, and saves. - Keep
ownerto two or three people; the last owner is protected, but a single-owner repository still has a bus factor of one.
Finally, create a starting milestone and project so triaged work has a destination:
REPOSITORY_API=https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com
curl -fsS -X POST "$REPOSITORY_API/milestones" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Backlog hygiene", "description": "First triage sweep"}'
PROJECT_NUMBER=$(curl -fsS -X POST \
"$REPOSITORY_API/projectsV2" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "openagents.com roadmap"}' | jq -r '.number')
curl -fsS -X POST \
"$REPOSITORY_API/projectsV2/$PROJECT_NUMBER/fields" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Status", "data_type": "single_select", "options": {"values": ["To Do", "In Progress", "Done"]}}'
The project belongs to the repository in REPOSITORY_API. Add high-signal
issues to the board and set their Status value through the project items API.
Label vocabulary
Use GitHub's defaults and nothing else for now. The standing design ruling is that priority labels are conventions, not contracts, so ordering lives in milestones and the project board, not in label names.
| Label | Means | Who applies it |
|---|---|---|
bug |
Something is broken | Triage |
enhancement |
New capability or improvement | Triage |
documentation |
Docs only | Triage |
good first issue |
Scoped enough for a newcomer | Maintainer |
help wanted |
Community input invited | Maintainer |
question |
Needs an answer before it is work | Triage; close as completed once answered |
duplicate |
Repeats another issue | Triage; close with reason duplicate |
invalid |
Not a real report | Triage; close with reason not_planned |
wontfix |
Real but declined | Maintainer; close with reason not_planned |
The daily triage loop
Ten minutes, ideally same time each day:
- Open
https://openagents.com/OpenAgentsInc/openagents.com/issues?state=open. - For each unlabeled issue: reproduce or reason about the report, then apply
one primary label (
bug,enhancement,documentation, orquestion). If the report cannot be evaluated, ask your question as a comment and leave it labeledquestion. - Assign an owner to everything labeled
bugorenhancementthat you intend to act on. Unassigned means "nobody owns this"; say that honestly rather than leaving it ambiguous. - Close what is not work:
- Duplicate: close with reason
duplicate, comment with the issue number. - Declined: close with reason
not_planned, applywontfix. - Answered questions: close with reason
completed.
- Duplicate: close with reason
- Move anything scheduled onto the project board and into a milestone. The board is the promise; the milestone is the deadline.
- Sweep the Closed tab briefly: a wrong close is cheaper to catch the same day than next week.
Use the filters to work in slices — label:bug for the bug sweep,
assignee:you for your queue, the search box to check whether a new report
duplicates an old one before you label it. The page updates live while other
maintainers triage alongside you.
The weekly review
- Walk the project board column by column. "In Progress" older than a week gets an owner, a milestone, or moves back to "To Do".
- Check the milestone: scope fixed, due date honest, nothing stuck.
- Skim PostHog for
issue_createdvolume versusissue_commented— a rising creation rate with flat response rate means triage is falling behind. - Review the members list on the members page: departed maintainers lose their role the week they leave, not someday.
- File the meta-issue: anything about the triage process itself that hurt
this week becomes an issue labeled
enhancementon this same tracker.
Community submissions
The standing policy, publishable anywhere the tracker is announced:
- Sign in with GitHub at
openagents.com. - Open issues on any public repository — no invitation, no membership.
- Expect triage: a maintainer labels and responds. Comments stay open, so the conversation continues on your issue.
Reporters who later become contributors get roles through the members page; that is a deliberate human decision, not an automatic upgrade.
CLI and API recipes
Use openagents api for the normal terminal workflow. It selects the API
origin, reads the stored oa_pat_ credential, and keeps successful JSON on
standard output.
Read open issues:
openagents api \
'repos/OpenAgentsInc/openagents.com/issues?state=open'
Create an issue with an existing label:
printf '%s' \
'{"title":"Search returns duplicates","body":"Steps to reproduce...","labels":["bug"]}' | \
openagents api -X POST --input - \
repos/OpenAgentsInc/openagents.com/issues
Close an issue as not planned:
printf '%s' '{"state":"closed","state_reason":"not_planned"}' | \
openagents api -X PATCH --input - \
repos/OpenAgentsInc/openagents.com/issues/42
The published CLI does not yet provide named issue or project commands.
See Call the API with the OpenAgents CLI for project,
item, and scripting examples.
Use curl when you need to verify the HTTP boundary without the CLI.
Read (anonymous, public repository):
curl -s "https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com/issues?state=open"
Create (requires forge:write token held by a member):
curl -s -X POST "https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com/issues" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"title": "Search returns duplicates", "body": "Steps to reproduce...", "labels": ["bug"]}'
Note the difference in kind: the JSON API's write path stays membership-gated. The participation model is a browser-session contract; PAT holders remain collaborators.
Close as not planned:
curl -s -X PATCH "https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com/issues/42" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"state": "closed", "state_reason": "not_planned"}'
Label and assign in one issue update: labels takes existing names,
assignees takes logins, and both replace the full set, so send the complete
desired lists. Use POST .../issues/{issue_number}/labels when you want a
missing label to be created as it is added.
Measuring triage health
PostHog events land at the domain boundary, so the funnel is queryable:
- Median time from
issue_createdto firstissue_commentedby a maintainer: your response-time promise. issue_createdcount per week versus issues closed per week: backlog direction.- Share of issues carrying zero labels after 24 hours: triage loop discipline.
Track them as three insights on one dashboard before the next announcement; retrofitting measurement onto a neglected backlog is miserable.
Guardrails
- Do not widen Git push beyond repository members; that is the mechanism that keeps public participation issue-shaped while pull requests remain a planned feature.
- Do not grant
maintainercasually: triage roles can close and rewrite anyone's issues. - Keep every membership change flowing through the members page so audit records accumulate; direct console edits skip the trail.
- When the pull-request switch ships, update this document the day it does:
record where the setting lives, which repositories default which way, and
flip
OpenAgentsInc/openagents.comtooffexplicitly rather than relying on the default.