Skip to content

feat(daemon): add fixed repo mode for agents#3154

Open
lucccf wants to merge 18 commits into
multica-ai:mainfrom
lucccf:feat/fixed-repo-mode
Open

feat(daemon): add fixed repo mode for agents#3154
lucccf wants to merge 18 commits into
multica-ai:mainfrom
lucccf:feat/fixed-repo-mode

Conversation

@lucccf
Copy link
Copy Markdown

@lucccf lucccf commented May 24, 2026

What does this PR do?

Adds a "fixed repo" mode that lets agents work in pre-existing local directories instead of per-task worktrees. Currently, every task goes through bare clone → worktree checkout, which fails for large monorepos, non-git VCS (Perforce, SVN), non-GitHub repos, and unversioned directories.

When an agent is configured with fixed_repo_enabled, the daemon selects an idle local path, locks it, injects VCS-aware guidance, and lets the agent work in-place — zero clone overhead.

Resolves #3153.

Type of Change

  • New feature (non-breaking change that adds functionality)

Changes Made

Database:

  • Added migration 108: four new columns on agent table (fixed_repo_enabled, fixed_repo_paths, vcs_type, cleanup_script)
  • Added migration 109: init_script column (entry hook, symmetric to cleanup_script)
  • Updated CreateAgent / UpdateAgent SQL queries with COALESCE-based partial update

Handler (API):

  • Added fixed repo fields (including init_script) to CreateAgentRequest / UpdateAgentRequest / AgentResponse structs
  • UpdateAgent uses pointer fields (*bool, *string) for patch semantics — omitted fields are not changed
  • Added vcs_type whitelist validation (git, p4, svn, none) returning 400 on invalid values

Daemon (task execution):

  • New fixedRepoLockTable with tryLock/unlock and waitAndLock for path-level exclusive access
    • When all paths are locked, waitAndLock blocks in dispatched state until a path frees (channel notification) instead of failing the task
  • runTask branches on Agent.FixedRepoEnabled: fixed-repo path skips execenv.Prepare/Reuse, uses selected path directly
  • Optional init script runs before the agent starts; failure blocks the task with reason init_script_failed (5-minute timeout)
  • Optional cleanup script runs after the agent exits, regardless of outcome (5-minute timeout)
  • Both scripts receive MULTICA_WORK_DIR, MULTICA_TASK_ID, MULTICA_AGENT_ID, MULTICA_AGENT_NAME, MULTICA_VCS_TYPE
  • Injects MULTICA_WORK_DIR + MULTICA_FIXED_REPO env vars into agent environment
  • Registers task in fixedRepoTasks map for /repo/checkout gate

Health endpoint (repo checkout gate):

  • /repo/checkout returns 400 for fixed-repo tasks with a clear error message

Agent brief injection:

  • New "Fixed Repository" section replaces "Repositories" when enabled
  • New writeVCSGuidance function injects VCS-specific rules (git/p4/svn) with safety constraints
  • Unknown VCS types get a generic fallback

CLI:

  • Added --fixed-repo-enabled, --fixed-repo-path (repeatable), --vcs-type, --cleanup-script, --init-script flags to agent create and agent update
  • Shortened "no fields to update" error message to point to --help

UI (agent detail inspector):

  • New FixedRepoSection component in inspector between Properties and Details
  • Switch toggle to enable/disable fixed repo mode
  • When enabled, pencil icon opens config dialog with VCS type dropdown, path list (add/remove), init/cleanup script inputs
  • i18n strings for English and Simplified Chinese

Tests:

  • fixed_repo_lock_test.go: exclusive locking, concurrent contention, unknown path handling, waitAndLock blocking/unblock/cancel, multi-path selection, multiple waiter competition
  • agent_fixed_repo_test.go: request/response JSON round-trip for all fixed repo fields (including init_script)
  • runtime_config_test.go: VCS guidance output verification, fixed repo brief section
  • Updated TypeScript test fixtures with new Agent type fields

How to Test

cd server && go build ./...
go test ./internal/daemon/ -run FixedRepo
go test ./internal/daemon/execenv/ -run "VCS|FixedRepo"
go test ./internal/handler/ -run "FixedRepo"

Manual (requires daemon + agent runtime):

  1. Create a fixed-repo agent:
    multica agent create --name writer-01 --runtime-id <id>      --fixed-repo-enabled --fixed-repo-path /data/repos/project      --vcs-type git --init-script /data/repos/init.sh --cleanup-script /data/repos/cleanup.sh
  2. Assign an issue to the agent → daemon selects the path, agent works in /data/repos/project
  3. Verify MULTICA_WORK_DIR is set in agent environment
  4. Verify agent brief contains "## Fixed Repository" and "## VCS: git" sections
  5. Verify multica repo checkout returns 400 during the task
  6. After task ends, verify cleanup script runs and path lock is released
  7. With two paths configured, run two concurrent tasks → each gets its own path
  8. With all paths locked, assign another task → verify it stays in "dispatched" (Starting) and proceeds when a path frees

UI testing:

  1. Open agent detail page in web/desktop
  2. Find "Fixed Repository" section between Properties and Details
  3. Toggle the switch to enable → verify "Edit" button appears
  4. Click Edit → verify dialog opens with VCS Type, Paths, Init Script, Cleanup Script fields
  5. Add/remove paths, change VCS type, save → verify changes persisted

Checklist

  • I have considered and documented any risks above
  • Backward compatible: fixed_repo_enabled=false (default) preserves existing behavior
  • Migration is additive only (new columns with defaults)
  • I will address all reviewer comments before requesting merge

AI Disclosure

AI tool used: Claude Code (Claude Opus 4.7)

lucccf and others added 2 commits May 24, 2026 14:53
…ectories

Add four new agent fields: fixed_repo_enabled, fixed_repo_paths, vcs_type,
and cleanup_script. When enabled, the agent works directly in a local
directory instead of going through bare clone → worktree checkout.

This supports large repos, non-git VCS (p4/svn), and non-VCS directories
where the per-task worktree model is impractical.

- DB: migration 108 adds four columns to the agent table
- Server: agent create/update handlers accept and return new fields
- Daemon: handleTask selects an idle path, acquires an exclusive lock,
  and injects MULTICA_WORK_DIR into the agent environment
- CLAUDE.md: inject ## Fixed Repository and ## VCS guidance sections
- CLI: agent create/update gain --fixed-repo-* flags
- /repo/checkout: returns a clear error when called by a fixed-repo agent
- GC: skips fixed-repo paths (they are not managed by the daemon)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…able leak

- Move InjectRuntimeConfig call after if/else block, deduplicating
  fixed-repo and normal branches. Use taskLog consistently for logging.
- Add default branch to writeVCSGuidance for unknown VCS types.
- Delete lock entry from map on unlock to prevent unbounded growth.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 24, 2026

@lucccf is attempting to deploy a commit to the IndexLabs Team on Vercel.

A member of the Team first needs to authorize it.

lucccf and others added 10 commits May 24, 2026 15:27
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The sqlc-generated agent.sql.go had SQL string constants with only 22
columns (up to thinking_level) while the Scan calls expected 26 fields
(including fixed_repo_enabled, fixed_repo_paths, vcs_type, cleanup_script).
This caused "number of field descriptions must equal number of
destinations, got 22 and 26" errors in CI handler tests.

Regenerated with sqlc v1.31.1 against the full migration set including
108_agent_fixed_repo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The sqlc-generated INSERT now explicitly lists all 20 columns
(including the 4 new fixed_repo fields), so the DB column defaults
no longer apply. The onboarding shim and agent template paths were
missing these fields, causing NOT NULL constraint violations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
JSON omitempty leaves []string as nil when the field is absent from the
request body. pgx translates nil slice to NULL, which violates the NOT
NULL constraint. Add defaultSlice helper to coerce nil → empty slice.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The default switch case produced output for empty vcs_type ("").
Add an explicit empty-string case that produces no output.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…vars

- Add init_script field (migration 109, handler, daemon, CLI) symmetric to cleanup_script
- Replace tryLock with waitAndLock: tasks block in dispatched state until a path frees
- Enhance script env vars: MULTICA_TASK_ID, AGENT_ID, AGENT_NAME, VCS_TYPE

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add init_script assertions to all 6 fixed repo handler test cases
- Add waitAndLock test for partially available paths (skips locked, takes free)
- Add waitAndLock test for multiple waiters competing for one path

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…efaults

- Fix brace nesting in runAgentUpdate where --init-script was only
  processed when --cleanup-script was also passed
- Add explicit InitScript: "" to CreateAgentFromTemplate and
  BootstrapOnboardingRuntime for consistency with other fixed repo fields

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tryLock is one-shot — goroutines that fail to acquire just exit.
Added spin-retry with runtime.Gosched() so all 10 goroutines
eventually succeed serially, matching the test assertion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Validate vcs_type against whitelist (git, p4, svn, none) in
  CreateAgent and UpdateAgent handlers, returning 400 on invalid values
- Shorten agent update "no fields" error to point to --help instead of
  listing every flag inline

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@lucccf lucccf force-pushed the feat/fixed-repo-mode branch from 63660a8 to 1d22ad0 Compare May 24, 2026 09:47
lucccf and others added 2 commits May 24, 2026 18:13
- Add fixed_repo_enabled, fixed_repo_paths, vcs_type, init_script,
  cleanup_script to Agent and UpdateAgentRequest TypeScript types
- New FixedRepoSection component with Switch toggle and config dialog
- Dialog includes VCS type dropdown, path list with add/remove,
  init/cleanup script inputs
- i18n strings for en and zh-Hans
- Wire into agent detail inspector between Properties and Details
- Update test fixtures with new Agent type fields

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add fixed_repo_enabled, fixed_repo_paths, vcs_type, init_script,
cleanup_script to AgentSchema to match the new Agent TypeScript type.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@lucccf lucccf force-pushed the feat/fixed-repo-mode branch from e1486d5 to e5993f5 Compare May 24, 2026 10:21
lucccf and others added 4 commits May 24, 2026 18:29
- Fix Select onValueChange type to handle null from Base UI Select
- Add fixed_repo fields to Agent mock fixtures in 4 test files

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add unit tests for the FixedRepoSection component covering readonly mode,
edit mode, dialog interactions, path add/remove, and save behavior.
Fix missing Agent type fields in web test helpers fixture.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Use @multica/core/i18n/react for I18nProvider and inline TEST_RESOURCES,
matching the pattern used by other inspector tests. Remove unused imports.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Switch from userEvent to fireEvent for reliable jsdom interaction
- Add afterEach cleanup to prevent DOM leakage between tests
- Use Enter key for path addition instead of finding plus button
- Remove Select interaction test (Base UI portal not testable in jsdom)
- Add jsdom environment directive

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

Support agents working in pre-existing local directories (fixed repo mode)

1 participant