Agent loop, Concurrent planning #321
Closed
opened 2026-05-28 03:58:54 +00:00 by guettli
·
2 comments
No Branch/Tag Specified
main
issue-563-agentloop-validation
dummy-pr-test
issue-560-fix-firebase-run-url
issue-539-stable-imap-uid
issue-533-shared-email-list
plan-issue-555
drop-nix
plan-issue-484
plan-issue-539
plan-issue-535
plan-issue-474
plan-issue-533
fix-dagger-engineless-precommit
issue-521-fix-deploy-yml-wait-time-api
issue-502-fix-email-id-collision-mailbox
issue-492-eliminate-duplicate-build-runner
issue-494-website-change-detection
issue-491-parallelize-check
issue-478-fix-stalwart-dual-stack-bind
issue-475-allowed-addresses-glob
issue-473-search-result-reorder
issue-453-update-agentloop-defaults
issue-466-structured-search
issue-505-exclude-chaos-monkey-from-regular-ci
issue-509-fix-search-result-sorting
fix-ink-sparkle-remaining-tests
issue-506-fix-search-emails-tests
issue-504-runner-wait-time
issue-488-search-notes
issue-472-changelog-issue-links
issue-501-folder-search-local-sqlite
issue-486-fix-stale-test-shader-mismatch
fix/prevent-settled-search-rerun-473
issue-467-fix-search-stale-results
issue-446-installed-versions-in-changelog
issue-462-fix-pr
issue-448-chaos-monkey-test
issue-436-notes-on-emails
issue-429-unify-mail-display
issue-422-move-to-folder-create-new
issue-414-ensure-not-run-as-root
issue-424-unify-email-list-views
issue-419-trusted-senders-page
issue-425-fix-prs
test-foo
issue-421-bug-report
issue-383-fix-ci
issue-394-fix-deploy-flutter-version
issue-391-fix-ci-double-trigger
issue-376-combined-inbox-v2
issue-376-combined-inbox
issue-384-fix-open-prs
sops-migrate
issue-339-safe-first-on-imap-fetch
issue-340-try-catch-measure-height
issue-342-pin-intl-version
issue-341-guard-threademails-last
issue-335-agentloop-code-test
issue-329-fix
issue-315-fix
issue-320-fix
issue-325-fix
issue-312-fix
issue-311-fix
issue-305-fix
issue-304-fix
issue-299-fix
issue-300-fix
issue-298-fix
issue-296-fix
issue-294-fix
issue-289-fix
issue-288-fix
issue-287-fix
issue-286-fix
issue-277-fix
issue-282-fix
issue-280-fix
issue-272-fix
issue-268-fix
issue-267-fix
issue-266-fix
issue-258-fix
issue-260-fix
issue-257-fix
issue-253-fix
issue-216-fix
issue-251-fix
issue-249-fix
issue-question-fixes
issue-235-fix
issue-236-fix-v2
issue-237-fix
issue-236-fix
issue-228-fix
issue-217-fix
issue-214-fix
issue-213-fix
issue-208-fix
issue-205-fix
issue-204-fix
issue-203-fix
issue-202-fix
issue-129-fix
issue-161-fix
issue-160-fix
issue-201-fix
issue-210-fix
issue-198-fix
issue-200-fix
issue-144-fix
issue-199-fix
fix/playstore-upload-use-requests
issue-193-fix
issue-186-fix
issue-185-fix
issue-192-fix
issue-183-fix
issue-175-fix
issue-172-fix
issue-171-fix
issue-167-fix
issue-136-fix
issue-162-fix
issue-179-fix
issue-155-fix
issue-154-fix
issue-152-fix
issue-151-fix
issue-141-fix
issue-150-fix
issue-164-fix
migrate-to-dagger
task/d1-ci-matrix
task/a4-typeconverter-json
task/u7-onboarding-walkthrough
task/d3-sync-doc
task/a5-layer-boundary-lint
task/t5-golden-tests
task/p5-date-cache
task/s4-link-handling
task/p3-html-parse-isolate
task/u8-mark-all-read
task/u3-recent-searches
task/a3-jmap-injectable-http-client
task/r5-tls-error-handling
fix/playstore-redirect-retry
task/t3-repository-contract-tests
task/p2-email-list-pagination
task/p1-fts5-search
fix/playstore-upload-timeout
task/a1-email-detail-notifier
fix/upgrade-workmanager-0.9
fix/android-core-library-desugaring
task/p4-db-indexes
task/r3-html-error-boundary
task/d2-check-coverage
task/a2-email-tile
task/t4-migration-tests
task/t2-widget-tests
task/t1-email-repo-coverage
task/u6-connection-status
task/u4-push-notifications
task/u2-draft-sync
task/u1-list-unsubscribe
task/s2-hostname-validation
task/r6-reliability-fuzz-tests
task/r4-sync-error-banner
task/r2-force-resync
task/r1-undo-history-persistence
No results found.
Labels
Clear labels
NeedSupervisor
State/InProgress
State/Later
State/Planned
automerge
ci-failure
do-not-merge
loop/code
loop/code-ci-pending
loop/code-done
loop/code-in-process
loop/merge
loop/merge-done
loop/merge-in-process
loop/plan
loop/plan-done
loop/plan-in-process
Issue escalated to a human supervisor; agentloop will skip it until cleared.
Eligible for automatic merge by CI
Issue opened by agentloop to track a failing CI workflow; used for deduplication.
Plan PR — review only, do not merge.
Add to run the built-in "code" prompt; override at prompts/code.md.
Prompt "code" finished; waiting for the PR's CI to pass before advancing.
Prompt "code" finished successfully.
Agent for the "code" prompt is currently running on this issue.
Managed by agentloop
Managed by agentloop
Managed by agentloop
Add to run the built-in "plan" prompt; override at prompts/plan.md.
Prompt "plan" finished successfully.
Agent for the "plan" prompt is currently running on this issue.
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: guettli/sharedinbox#321
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
On hold, think about doing agent loop from scratch, with containers and CRDs.
The current loop runs one agent at a time (including planners). Making planners truly concurrent (e.g., while an implementation CI is pending) requires tracking multiple concurrent agent PIDs.
Create plan with several alternatives.
Implementation Plan: Concurrent Agent Loop
Current Architecture
The sequential bottleneck lives in three layers:
internal/loop/loop.go:processRepo()(line 106) — iterates issues with a plainforloop; each call tohandleIssueblocks until the agent finishes.internal/loop/loop.go:handleIssue()(lines 121–197) — one sequential pipeline: set InProgress → clone → run agent → push → create PR → comment → remove InProgress.internal/agent/claudecode.go:Run()(line 35) — callscmd.Run()which blocks until theclaudeprocess exits (can take minutes).The existing label-based state machine (
State/Ready→State/InProgress) already provides cross-poll deduplication: once an issue isInProgress,ListOpenIssues(Ready)won't return it again. Concurrency therefore only requires makinghandleIssuenon-blocking from the perspective of the outer loop.Alternative A — Goroutines + Semaphore (minimal change)
Files to change:
internal/loop/loop.go,internal/config/config.goApproach:
max_concurrent inttoconfig.Config(default: 3).Runner, add a semaphore:sem chan struct{}(buffered channel of sizemax_concurrent).processRepo(), replace the sequentialforloop with goroutine launches guarded by the semaphore and async.WaitGroup.PID tracking (requested in issue): Add a
sync.MaponRunnerkeyed byissueKey(e.g."owner/repo#N") storing the OS PID of the runningclaudeprocess.agent.ClaudeCode.Run()would need to return the PID or accept a callback; alternatively exposecmd.Process.Pidaftercmd.Start().Advantages: ~30 lines of change, no new state, no new labels.
Risks / open questions:
State/InProgresswill stay there indefinitely — needs human intervention or a startup cleanup sweep.processRepois called on every poll tick (potentially while the previous goroutines are still running), so the semaphore must be onRunner, not local toprocessRepo, to correctly bound total concurrency across overlapping polls. Currentlypoll()itself is synchronous but that too would need protection.Alternative B — Persistent State Files + PID Tracking (crash-safe)
Files to change:
internal/loop/loop.go,internal/config/config.go; new file:internal/state/state.goApproach:
<work_dir>/state/<platform>/<owner>/<repo>/issue-<N>.json:kill(pid, 0)/ reading/proc/<pid>).State/Questionand clean up.pr_createdand recordpr_url.Advantages:
State/InProgressissues are self-healing.Risks / open questions:
agentlooprestarts and the OS has recycled the old PID, a different process might appear alive. Mitigate by also checking process start time or writing a lockfile the child process holds open.Alternative C — CI-Waiting Label (enables true planner/implementation split)
Files to change:
internal/loop/loop.go,internal/config/config.go,internal/platform/platform.go(new method),internal/platform/forgejo.go,internal/platform/github.goApproach:
State/WaitingCIto the config and state machine.State/InProgress, transition the issue toState/WaitingCI.checkWaitingCI()that:State/WaitingCI.GET /repos/{owner}/{repo}/statuses/{sha}; GitHub: Checks API).State/Donelabel).State/Questionand comment with the failure.WaitingCIissues, since those issues are no longer blocking a slot.Platform interface addition needed:
Advantages:
Risks / open questions:
WaitingCIforever — need a timeout or a config option to skip CI gating.Alternative D — Worker Pool (most scalable)
Files to change:
internal/loop/loop.go,cmd/agentloop/main.go,internal/config/config.go; new file:internal/worker/pool.goApproach:
Poolwith N worker goroutines (buffered job channel).Runner.poll()enqueues issues onto the job channel instead of callinghandleIssuedirectly.sync.Map) prevents enqueuing the same issue twice across poll cycles.handleIssueand update the running-set on completion.Advantages:
Risks / open questions:
poll()must either drop the issue (fine, next poll will retry) or block (risk of poll stacking). Recommend a non-blocking send with a drop-and-log.Recommended Path
Alternatives A and B are independent of C and can be shipped first. Alternative C is the feature that makes planners "truly concurrent while CI is pending" as the issue describes — it is likely the primary goal here.
Open Questions
handleIssueagent run? This affects whether we need a separate label/phase.agentloopautomatically retry issues that wereInProgressat startup, or require human triage?Planning complete. To resume this session: