Projects as configuration entries (M1.4, D8)
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 5m7s

- Config: `projects: {name: entry}` with repo, base_branch, workspace,
  build_host, push credential, labels (defaults added to every issue),
  secrets by ${VAR} reference with visibility (commands | harness), and
  egress (recorded, not enforced). Every problem in every entry is
  reported at once; an inline secret, a bad visibility, a repeated repo or
  a non-slug name is an error.
- The old loop runs exactly one project (only_project) and refuses more.
- The new loop is per project: each its own clone, workspace (with its
  commands-visible secrets, redacted on return), push credential and
  milestones; intake polls every project's repository; items are scoped
  to their project for every run, publish and PR watch. Issues are named
  project#14 when there is more than one project, and a bare #14 that
  matches two is asked about. Switching projects parks the one worked on
  last (all others after a boot).
- A restatement that ends in a question is sent back to the driver, not
  posted: the e2e driver kept asking "Is this the right interpretation?",
  which a plain yes can't answer.

Tests: config entries and errors, two-project assembly, two-project loop
tests (intake, own workspaces and repos, #14 in both, do-next, label
defaults, parking across projects), the property walk over two projects
with a new own_repository_only property (an item using the first
project's repo fails 15 of 16 seeds), and the restatement check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-06 01:12:57 +00:00
1 parent 4ecff19933
commit 064d9bf554
17 files changed
+1139 -315

No files matched your search

+18 -1
View File
@@ -58,10 +58,14 @@ class Driver:
workspace: Workspace | None,
handlers: Mapping[str, Callable[[dict[str, Any]], tuple[str, bool]]]
| None = None,
checks: Mapping[str, Callable[[dict[str, Any]], str | None]] | None = None,
) -> RunOutcome:
"""``handlers`` answer control tools that don't end the run (e.g.
start_milestone): each returns a result and whether it succeeded."""
start_milestone): each returns a result and whether it succeeded.
``checks`` vet a waiting tool's arguments: a problem is the call's
failed result and the run goes on, so the model puts it right."""
handlers = handlers or {}
checks = checks or {}
offered = names(tools)
nudged = False
for turn in range(self._max_turns):
@@ -89,6 +93,19 @@ class Driver:
if self._cancel.requested:
skip_calls(transcript, calls[index:])
return RunOutcome("stopped", note=f"during turn {turn + 1}")
problem = (
checks[call.tool](dict(call.arguments))
if call.tool in checks and call.tool in offered
else None
)
if problem is not None:
transcript.append(
ToolResult(call_id=call.call_id, result=problem, success=False)
)
skip_calls(
transcript, calls[index + 1 :], "not run: fix the call above"
)
break
if call.tool in WAITING and call.tool in offered:
transcript.append(
ToolResult(