Model issue prerequisites so agents can see what is unblocked #100

Closed AtlantisPleb opened this 7d ago 1 comment

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.

  1. AtlantisPleb opened this issue 7d ago
  2. A AtlantisPleb Author 7d ago

    Shipped in e11928b on main.

    Dependency edges are repository-local. Issue responses carry openagents.blocked, openagents.blocked_by, and openagents.blocks; the list filters on ?blocked=true|false; GET /api/v3 describes the extension. Self references, unknown numbers, and cycles fail the whole batch.

    Written by Devin

  3. closed this as completed 7d ago
Sign in with GitHub to comment on this issue.