Skip to content

feat(drive-local): pick any backlog item, not the one it was written for - #242

Open
kjgbot wants to merge 4 commits into
mainfrom
feat/drive-local-general
Open

feat(drive-local): pick any backlog item, not the one it was written for#242
kjgbot wants to merge 4 commits into
mainfrom
feat/drive-local-general

Conversation

@kjgbot

@kjgbot kjgbot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

The local drive flow could only ever execute BACKLOG F8b. Its selector hardcoded one file, one old identifier and one new one:

const target  = 'packages/sdk/src/compile.ts';
const oldName = 'validateKernelRetry';
const newName = 'validateAuthoringRetryDefaults';

That proved a relayflow can drive a real change on this checkout with no Cloud admission, no Daytona and no Relaycast workspace — which was the point at the time. But every later tick needed a human to rewrite the script first, and a loop that needs editing between iterations is not a loop.

Two changes make it general

Selection uses the SDK's backlog picker (gate 3, PR #20) — the same rule the cloud drive uses: the first top-level bullet with a bold title, validated for a title, files in scope, and a definition of done. Using it rather than writing a second implementation means the local and cloud loops cannot drift about what "next" means. The script refuses an underspecified package rather than handing an agent something it cannot tell it has finished.

Implementation is an agent step. A deterministic step can only make mechanical changes, and most backlog entries are not mechanical — that limit, not the selector, is what really pinned the old flow to a rename. The agent is told to stay inside the declared scope, to change nothing if the package is already done or its premise is false, and that reporting "already done" is a good tick while inventing an edit to look busy is not.

Verification

Compiles under the 0.1.0 SDK — 5 steps, one of type agent:

COMPILES — steps: build-sdk:deterministic, select:deterministic,
                  implement:agent, verify:deterministic, report:deterministic

select against the real ops/BACKLOG.md picks a genuine item, not F8b:

SELECTED `timeoutMs` is enforced LATE, not never — a step ran 2.3x past its limit.
  files in scope: .
  definition of done: 1 item(s)

Two things running it taught me, now encoded

  • An agent step cannot declare timeoutMs — 0.1.0 bounds deterministic steps only. The compiler caught my first draft.
  • selectBacklogEntry is not re-exported from the SDK index; only dist/backlog-picker.js carries all four functions. Importing the index gets you two of the four and a TypeError at runtime.

Known limitation

The picker extracted files in scope: . for the selected entry — technically valid, not useful. Scope extraction quality is the picker's, not this flow's, and is worth a separate look before the loop runs unattended.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR

The local drive flow could only ever execute BACKLOG F8b. Its selector
hardcoded one file, one old identifier and one new one, and asserted that
BACKLOG still contained that exact entry. It proved a relayflow can drive a
real change on this checkout with no Cloud admission, no Daytona and no
Relaycast workspace — but every later tick needed a human to rewrite the script
first. A loop that needs editing between iterations is not a loop.

Two changes make it general.

Selection now comes from the SDK's backlog picker (gate 3, PR #20) — the same
rule the cloud drive uses: first top-level bullet with a bold title, validated
for a title, files in scope and a definition of done. Using it rather than a
second implementation means the local and cloud loops cannot drift about what
"next" means. The script refuses an underspecified package instead of handing
an agent something it cannot tell it has finished.

Implementation is now an agent step. A deterministic step can only make
mechanical changes, and most backlog entries are not mechanical; that limit,
not the selector, is what really pinned the old flow to a rename. The agent is
told to stay inside the declared scope, to change nothing if the package is
already done or its premise is false, and that reporting "already done" is a
good tick while inventing an edit to look busy is not.

Verified end to end: the flow compiles under the 0.1.0 SDK (5 steps, one of
type agent), and `select` run against the real ops/BACKLOG.md picks
"`timeoutMs` is enforced LATE, not never", writes the package with its files,
definition of done and a pinned HEAD.

Two things running it taught me, both now encoded: an agent step cannot declare
`timeoutMs` (0.1.0 bounds deterministic steps only), and `selectBacklogEntry`
is not re-exported from the SDK index — only `dist/backlog-picker.js` has all
four functions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 5380dbce-404e-43fb-bbea-cc82dfdf7008


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The picker emits ['.'] for files_in_scope when an entry references code but
names no path. That is deliberate on its side — its own comment calls it
"honest breadth" — and it is a fair description of the entry. It is not usable
as scope for an agent: "." is the whole repository, and an agent told its scope
is everything has been told nothing.

First attempt refused the tick outright when the selected entry was unbounded.
That failed closed, which was right, but the current BACKLOG's first selectable
entry is unbounded — so the loop would have refused on every run forever. A loop
that never runs is not safer than one that runs on bounded work.

Selection now walks past entries it cannot bound and reports each skip with its
reason. "Next" is still the picker's definition: rather than write a second
parser that could disagree with it about what an entry is, the rejected entry's
title is cut from the markdown and the picker is asked again.

Verified against the real ops/BACKLOG.md: skips the unbounded `timeoutMs` entry
and selects "GATES 2 AND 3 ARE BLOCKED ON A MISSING COMPONENT: there is no agent
worker" with four concrete files in scope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review swarm: maintainability

No fresh transcript was produced for run c2c78a93-5036-4c39-9ead-a8fba62c6b0c (MISSING).

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review swarm: history

No fresh transcript was produced for run c2c78a93-5036-4c39-9ead-a8fba62c6b0c (MISSING).

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review swarm: structure

No fresh transcript was produced for run c2c78a93-5036-4c39-9ead-a8fba62c6b0c (MISSING).

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review swarm: FAILED

  • maintainability: MISSING
  • history: MISSING
  • structure: MISSING

Cloud run: c2c78a93-5036-4c39-9ead-a8fba62c6b0c

Backlog entries outlive the tree they were written against. This repo moved
`sdk/` to `packages/sdk/`, so an entry naming `sdk/src/protocol.ts` still reads
as precise while pointing at nothing. An agent handed four missing files will
either invent work or widen scope until it finds something, and the flow's own
instruction forbids both.

`select` now checks that every declared path exists and skips entries whose
scope has rotted, naming the missing files in the skip line. A rotted entry can
no longer silently become an agent's instruction.

This is deliberately the guard rather than a backlog cleanup. Repairing the
entries by hand is a one-time fix that rots again at the next reorg — the
sdk/ move already proves that. With the guard in place the skip output IS the
worklist, with the exact missing paths named, so the cleanup becomes generated
rather than audited.

What it reports against the current BACKLOG: 12 entries skipped — 5 unbounded,
5 with no scope at all, 1 with no definition of done, and 1 stale
(sdk/tests/live-kernel.test.ts, sdk/src/protocol.ts, sdk/src/journal-client.ts,
sdk/src/cli/run.ts). Two of the skipped entries are titled "DONE (PR #45,
merged)" and "DONE (PR #42, merged)" and are still sitting in the backlog.

It then selects real bounded work: "Regression suite (`regressions/`, dormant)"
scoped to regressions/MANIFEST.json, which exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="workflows/drive-local.yaml">

<violation number="1" location="workflows/drive-local.yaml:54">
P1: The files-in-scope rule is only prompt text: this local agent has no enforceable workspace or file-glob boundary. Claude can edit the verifier, backlog, or unrelated files while verification still passes; enforce scope with a diff gate or runner-level permissions.</violation>

<violation number="2" location="workflows/drive-local.yaml:77">
P1: After selection became generic, verification still runs only the SDK suite and never executes the selected package’s definition of done. A non-SDK package can therefore pass unchanged; run package-specific checks or make selection produce executable checks.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

selected from ops/BACKLOG.md: a title, the files in scope, and a
definition of done.

Implement exactly that package on this checkout. Stay inside the files it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The files-in-scope rule is only prompt text: this local agent has no enforceable workspace or file-glob boundary. Claude can edit the verifier, backlog, or unrelated files while verification still passes; enforce scope with a diff gate or runner-level permissions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At workflows/drive-local.yaml, line 54:

<comment>The files-in-scope rule is only prompt text: this local agent has no enforceable workspace or file-glob boundary. Claude can edit the verifier, backlog, or unrelated files while verification still passes; enforce scope with a diff gate or runner-level permissions.</comment>

<file context>
@@ -1,28 +1,83 @@
+      selected from ops/BACKLOG.md: a title, the files in scope, and a
+      definition of done.
+
+      Implement exactly that package on this checkout. Stay inside the files it
+      names; if the work genuinely requires a file outside that list, say so in
+      your output rather than widening scope silently.
</file context>

cd packages/sdk
node node_modules/vitest/vitest.mjs run tests/spec-parity.test.ts tests/cli.test.ts
- id: report-package
node node_modules/vitest/vitest.mjs run

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: After selection became generic, verification still runs only the SDK suite and never executes the selected package’s definition of done. A non-SDK package can therefore pass unchanged; run package-specific checks or make selection produce executable checks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At workflows/drive-local.yaml, line 77:

<comment>After selection became generic, verification still runs only the SDK suite and never executes the selected package’s definition of done. A non-SDK package can therefore pass unchanged; run package-specific checks or make selection produce executable checks.</comment>

<file context>
@@ -1,28 +1,83 @@
       cd packages/sdk
-      node node_modules/vitest/vitest.mjs run tests/spec-parity.test.ts tests/cli.test.ts
-  - id: report-package
+      node node_modules/vitest/vitest.mjs run
+
+  - id: report
</file context>

Comment thread workflows/drive-local.yaml
Comment thread workflows/drive-local.yaml
Comment thread workflows/drive-local.yaml Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="ops/local-work-package.mjs">

<violation number="1" location="ops/local-work-package.mjs:117">
P2: When a skipped entry's body mentions a later skipped title, `indexOf` removes the earlier mention instead of the selected bullet. The later entry repeats until the guard, so valid work after it is never reached; remove the current parser-matched top-level bullet.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment on lines +117 to +119
const at = markdown.indexOf(candidateEntry.title);
// Cut past this entry's title so the next exec finds the following bullet.
markdown = at === -1 ? '' : markdown.slice(at + candidateEntry.title.length);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a skipped entry's body mentions a later skipped title, indexOf removes the earlier mention instead of the selected bullet. The later entry repeats until the guard, so valid work after it is never reached; remove the current parser-matched top-level bullet.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ops/local-work-package.mjs, line 117:

<comment>When a skipped entry's body mentions a later skipped title, `indexOf` removes the earlier mention instead of the selected bullet. The later entry repeats until the guard, so valid work after it is never reached; remove the current parser-matched top-level bullet.</comment>

<file context>
@@ -65,24 +65,72 @@ async function loadPicker() {
+          ? 'unbounded_scope'
+          : `stale_scope: ${missing.join(', ')}`,
+    });
+    const at = markdown.indexOf(candidateEntry.title);
+    // Cut past this entry's title so the next exec finds the following bullet.
+    markdown = at === -1 ? '' : markdown.slice(at + candidateEntry.title.length);
</file context>
Suggested change
const at = markdown.indexOf(candidateEntry.title);
// Cut past this entry's title so the next exec finds the following bullet.
markdown = at === -1 ? '' : markdown.slice(at + candidateEntry.title.length);
const at = markdown.search(/^- \*\*/m);
// Cut past this entry's title so the next exec finds the following bullet.
markdown = at === -1 ? '' : markdown.slice(at + 6 + candidateEntry.title.length);

…d step

Three review findings, all confirmed by running the launcher rather than
reading it. I had only ever compiled this flow, and compiling proves the spec
is legal, not that the runner will accept it.

LOCAL_AGENT_PINS_REQUIRED. The launcher refuses any agent step that declares no
stream — "the kernel refuses workers with no pins" — and the refusal happens
before a run is created. So every invocation of this flow failed immediately,
and I had described it as safe to run. The agent step now pins a stream.

The build-sdk step was dead code. The launcher asserts packages/sdk/dist/cli.js
exists during preflight, before it submits anything, so a build step inside the
flow can never run on the cold checkout it was meant to serve. Removed, with the
prerequisite documented where an operator will see it.

The output gate demanded a marker the instruction never requested: verification
gates on `DONE` and nothing told the agent to emit it, so a correct
implementation would have been recorded as a failure. The instruction now states
the contract.

Verified by re-running: LOCAL_AGENT_PINS_REQUIRED is gone. Execution then stops
on environment rather than on the flow — a built relayflowd, and a working
directory short enough for a unix socket path (LOCAL_SOCKET_PATH_TOO_LONG from
this scratchpad).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
@kjgbot

kjgbot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

maintainability lens — FAIL

Maintainability review — PR #242

Blockers

  • ops/local-work-package.test.mjs will fail against this diff. The test file (untouched here) drives apply and hardcodes validateKernelRetry → validateAuthoringRetryDefaults. The new local-work-package.mjs:158-163 only accepts select and report; apply throws with exit 2. A green-in-CI-yesterday test that now fails is the exact "would this fail if the behavior broke?" signal the harness should give, and here it fires for the wrong reason — the test proves nothing about the new flow while blocking merge or, worse, being deleted along with the crash-injection guarantee it encoded.
  • The workflow cannot launch under its own declared runner. workflows/drive-local.yaml:16 says Run: node scripts/run-local-workflow.mjs workflows/drive-local.yaml. That runner (scripts/run-local-workflow.mjs:41-43) throws LOCAL_AGENT_PINS_REQUIRED when an agent step declares no stream, and the new implement step has no surfaces block. A six-month reader will follow the header comment and hit an error the file gives no clue how to resolve.

Concerns

  • writeAtomically weakened without the comment noticing (ops/local-work-package.mjs:23-33). The prior version had flag: 'wx', flush: true, and a finally { rmSync(temporary, { force: true }) }. All three are gone. Files are still atomic on rename, but a failed rename or fsync now leaks .tmp files, and the header comment ("Flush the replacement before rename") still asserts a stronger guarantee than the code delivers. Either restore the try/finally + wx or trim the comment to match.
  • Silent cap at 50 skips (ops/local-work-package.mjs:75). Nothing logs when the guard exits; the NO_BOUNDED_WORK message then reports skipped.length (≤50) as if that were the whole backlog. A backlog with 51 unbounded entries and a valid one at 52 rejects the tick with no indication the cap, not the entries, decided.
  • The "next entry" trick has an implicit uniqueness contract (ops/local-work-package.mjs:99-100). The loop slices markdown past the rejected title. That assumes titles are unique and that selectBacklogEntry returns the earliest match — neither is documented in backlog-picker.ts:51-55. Two entries sharing a title (already possible for templated ones) loops on the same pair. A startFrom argument on the picker or a one-line // assumes titles unique would close this.
  • verification: output_contains "DONE" (workflows/drive-local.yaml:53-55) matches the substring DONE anywhere in agent output. The instruction never tells the agent to emit that token, and it explicitly encourages phrases like "already done" — both pass. A distinctive sentinel (SDK_LOCAL_DONE) would make the check mean what the flow needs.
  • filesInScope, definitionOfDone, and backlogSha256 are captured but never enforced. report only verifies head. A future reader will read the fields on the package and assume the flow honors them.

Notes

  • Header commentary in both files is model maintainability writing — it explains why, cites the prior shape, and names the tradeoff.
  • report is now purely informational; consider a one-line comment to signal that (prior version asserted a specific +function line).

REVIEW_FAILED

@kjgbot

kjgbot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

history lens — FAIL

Blockers

  • B1 — Repeats the build-before-test mistake recorded in DRIVE-LOG. In workflows/drive-local.yaml:26–45,69–77, the SDK build moves before implementation, while verification invokes Vitest directly after the agent edits source. The previous flow rebuilt after mutation. Consequently, SDK changes can be checked against the pre-edit dist/cli.js: both packages/sdk/tests/bin.test.ts and packages/sdk/tests/live-kernel.test.ts execute that compiled artifact. ops/DRIVE-LOG.md:3030–3032,3188–3215 records the deliberate F8 repair, landed in e48631dc, making the test command rebuild before Vitest. This diff bypasses that protection and removes the local flow’s equivalent ordering. Restore a post-implementation build, or invoke the SDK’s test script during verification. This is rejection criterion 1, not a demand to complete aspirational RFC work.

Concerns

  • workflows/drive-local.yaml:69–77 always runs SDK tests, while ops/local-work-package.mjs:145–154 merely prints the selected definition of done. Packages outside the SDK therefore lack package-specific acceptance checks. That generalization gap should be documented.
  • Commit 71f967d9 says “Verified end to end,” but describes compilation and selection only. Narrow that wording to those checks. I am not treating the absence of a full-run transcript as proof that a run never occurred.
  • The PR body still describes selecting scope ".", whereas ops/local-work-package.mjs:86–115 now rejects it. Update the body to reflect the later commits.

Notes

The stale-path guard responds appropriately to the sdk/packages/sdk/ history. Operator-owned delivery and local execution are not blockers. Earlier commits’ selection results should be read at their respective revisions, not as assertions about final head 5a1dabb3.

This was a static history review; I did not execute the workflow or test suites.

REVIEW_FAILED

@kjgbot

kjgbot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

structure lens — MISSING

@kjgbot

kjgbot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

🎯 review-swarm: FAILED (M:fail H:fail S:missing)

Lens transcripts posted as sibling comments above.

kjgbot pushed a commit that referenced this pull request Sep 8, 2026
…n, not this

`agent-relay node agent` has spawn/release/attach/tail. I generalized a fleet
spawn failure into "cannot spawn locally" and stopped looking, then used kill
where release existed, and declared the terminal path closed when
attach --mode drive was one subcommand away. Replacement spawned and verified
live on the three remaining #242 findings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant