Outcome
An agent picking up work can ask the API one question — "what can I start right now?" — and get an answer. Issue dependencies become first-class edges, every issue response reports blocked_by, blocks, and a derived blocked flag, and the issue index accepts ?blocked=false.
Background
Prerequisites exist today only as prose. Issue bodies end with a ## Dependencies paragraph (openagents.com#12: "Builds on the Issues API core (#9)"; openagents#13: "Coordinate with OpenAgentsInc/openagents.com#9"), and #12 proposes syncing - [ ] #N checkboxes so tracking issues stop going stale. Both encode the same relation in text that no query can read.
The consequences are concrete:
- The
issues table has no relation column, and no dependencies or blocked_by route exists. GET /api/v3/repos/OWNER/REPO/issues/13/dependencies returns 406.
- The CLI has no issue commands at all, so a client cannot even fall back to a named workflow.
- An agent that wants unblocked work must read 76 issue bodies and parse English. Aiming parallel agents at the backlog (
#41, #42) depends on that question being cheap.
A label convention such as blocked-by:99 works today and filters through ?labels=, but nothing validates the target exists, closing the blocker does not clear the label, and a cycle is invisible.
Design
Edges in a table, not in prose. A new issue_dependencies table stores one row per edge: the blocked issue, the blocker issue, the repository, and who recorded it. A unique index on the pair makes the write idempotent, and both directions are indexed so blocked_by and blocks read equally cheaply.
Blocked is derived, never stored. An issue is blocked when at least one of its blockers is open. Closing the last open blocker unblocks it with no second write, which is exactly the property the checkbox approach in #12 lacks. Reopening a blocker blocks it again.
Cycles are refused at write time. Recording an edge that would close a cycle returns 422 naming the offending path. A backlog whose dependency graph can contain a cycle cannot be scheduled, so the graph is kept acyclic at the only point where it can become cyclic.
The fields live in the openagents extension namespace from #35. This issue implements that namespace and its governance rules as its first tenant:
- Every
/api/v3 issue response gains a top-level openagents object. GitHub-shaped keys keep their exact shape, so a GitHub client sees only an additional object.
issue.openagents.blocked is a boolean. issue.openagents.blocked_by and issue.openagents.blocks are arrays of {number, title, state} summaries for the same repository.
GET /api/v3 returns a root document enumerating each extension field with its type, enum values, and owning version, so an agent discovers the fields mechanically instead of reading prose.
- Issue responses carry
X-OpenAgents-Extensions naming the populated extensions.
Routes. GET, POST, and DELETE on repos/OWNER/REPO/issues/NUMBER/dependencies, with POST taking {"blocked_by": [12, 35]} and DELETE removing one blocker. Reads follow the visibility predicate every issue read already uses; writes require forge:write and repository membership.
Filter. GET repos/OWNER/REPO/issues?blocked=false lists issues with no open blocker, inside the existing bounded-pagination contract. blocked=true is the complement, and any other value is a stable field-level 422.
Edges are same-repository in this slice. The table records both repository ids so cross-repository prerequisites become an additive change once their visibility rules are settled, alongside #39.
Work
- Migration and schema for
issue_dependencies, with the pair unique index and both directional indexes.
- Context functions to add, remove, and read edges, with self-reference, unknown-number, and cycle rejection.
- The
openagents extension object on issue JSON, the root document field index, and the response header.
- The dependency controller and its three routes.
- The
?blocked= filter on the repository issue index, sharing the one filter chain that the page and the count both read.
- Render the blocked state where issue rows and issue headers already render state, driven by the derived value.
Acceptance criteria
POST .../issues/12/dependencies with {"blocked_by": [9]} makes #12 report blocked: true, and #9 report blocks: [12].
- Closing
#9 makes #12 report blocked: false with no further write. Reopening #9 reverses it.
- An edge that would create a cycle, name a missing issue, or point an issue at itself returns
422 and records nothing.
?blocked=false agrees with the per-issue blocked value for every issue in the repository.
- A GitHub-shaped client sees issue responses unchanged apart from the added
openagents object.
- Dependencies on a private repository's issues never appear to a reader without membership.
Verification
Context tests for the edge lifecycle, derivation, and cycle refusal; controller tests for the three routes, the filter, the JSON shape, and the root document; authority tests for private repositories; mix precommit.
Dependencies
Implements the extension surface proposed in #35. Subsumes the intent of #12, which solves the same staleness with checkbox rewriting. Unblocks the backlog-scheduling work in #41 and #42.
Outcome
An agent picking up work can ask the API one question — "what can I start right now?" — and get an answer. Issue dependencies become first-class edges, every issue response reports
blocked_by,blocks, and a derivedblockedflag, and the issue index accepts?blocked=false.Background
Prerequisites exist today only as prose. Issue bodies end with a
## Dependenciesparagraph (openagents.com#12: "Builds on the Issues API core (#9)";openagents#13: "Coordinate withOpenAgentsInc/openagents.com#9"), and#12proposes syncing- [ ] #Ncheckboxes so tracking issues stop going stale. Both encode the same relation in text that no query can read.The consequences are concrete:
issuestable has no relation column, and nodependenciesorblocked_byroute exists.GET /api/v3/repos/OWNER/REPO/issues/13/dependenciesreturns406.#41,#42) depends on that question being cheap.A label convention such as
blocked-by:99works today and filters through?labels=, but nothing validates the target exists, closing the blocker does not clear the label, and a cycle is invisible.Design
Edges in a table, not in prose. A new
issue_dependenciestable stores one row per edge: the blocked issue, the blocker issue, the repository, and who recorded it. A unique index on the pair makes the write idempotent, and both directions are indexed soblocked_byandblocksread equally cheaply.Blocked is derived, never stored. An issue is blocked when at least one of its blockers is open. Closing the last open blocker unblocks it with no second write, which is exactly the property the checkbox approach in
#12lacks. Reopening a blocker blocks it again.Cycles are refused at write time. Recording an edge that would close a cycle returns
422naming the offending path. A backlog whose dependency graph can contain a cycle cannot be scheduled, so the graph is kept acyclic at the only point where it can become cyclic.The fields live in the
openagentsextension namespace from#35. This issue implements that namespace and its governance rules as its first tenant:/api/v3issue response gains a top-levelopenagentsobject. GitHub-shaped keys keep their exact shape, so a GitHub client sees only an additional object.issue.openagents.blockedis a boolean.issue.openagents.blocked_byandissue.openagents.blocksare arrays of{number, title, state}summaries for the same repository.GET /api/v3returns a root document enumerating each extension field with its type, enum values, and owning version, so an agent discovers the fields mechanically instead of reading prose.X-OpenAgents-Extensionsnaming the populated extensions.Routes.
GET,POST, andDELETEonrepos/OWNER/REPO/issues/NUMBER/dependencies, withPOSTtaking{"blocked_by": [12, 35]}andDELETEremoving one blocker. Reads follow the visibility predicate every issue read already uses; writes requireforge:writeand repository membership.Filter.
GET repos/OWNER/REPO/issues?blocked=falselists issues with no open blocker, inside the existing bounded-pagination contract.blocked=trueis the complement, and any other value is a stable field-level422.Edges are same-repository in this slice. The table records both repository ids so cross-repository prerequisites become an additive change once their visibility rules are settled, alongside
#39.Work
issue_dependencies, with the pair unique index and both directional indexes.openagentsextension object on issue JSON, the root document field index, and the response header.?blocked=filter on the repository issue index, sharing the one filter chain that the page and the count both read.Acceptance criteria
POST .../issues/12/dependencieswith{"blocked_by": [9]}makes#12reportblocked: true, and#9reportblocks: [12].#9makes#12reportblocked: falsewith no further write. Reopening#9reverses it.422and records nothing.?blocked=falseagrees with the per-issueblockedvalue for every issue in the repository.openagentsobject.Verification
Context tests for the edge lifecycle, derivation, and cycle refusal; controller tests for the three routes, the filter, the JSON shape, and the root document; authority tests for private repositories;
mix precommit.Dependencies
Implements the extension surface proposed in
#35. Subsumes the intent of#12, which solves the same staleness with checkbox rewriting. Unblocks the backlog-scheduling work in#41and#42.