feat(daemon): agent-driven repo checkout with bare clone cache
Agents now decide which repo to use based on issue context and check out repos on demand via `multica repo checkout <url>`. Workspace repos are cached locally as bare clones for fast worktree creation. Key changes: - Add repocache package for bare clone management (clone, fetch, worktree) - Add `multica repo checkout` CLI command that talks to local daemon - Add POST /repo/checkout endpoint on daemon health server - Pass workspace repos metadata through register + task claim responses - Remove pre-created worktrees from execenv (workdir starts empty) - Update CLAUDE.md template to instruct agents to use `multica repo checkout` - Pass MULTICA_DAEMON_PORT, WORKSPACE_ID, AGENT_NAME, TASK_ID env vars to agent
This commit is contained in:
parent
ab4058b1e4
commit
cdc1ac708e
15 changed files with 1064 additions and 255 deletions
|
|
@ -1,6 +1,6 @@
|
|||
// Package execenv manages isolated per-task execution environments for the daemon.
|
||||
// Each task gets its own directory with a git worktree (for code tasks) or plain
|
||||
// directory (for non-code tasks), plus injected context files.
|
||||
// Each task gets its own directory with injected context files. Repositories are
|
||||
// checked out on demand by the agent via `multica repo checkout`.
|
||||
package execenv
|
||||
|
||||
import (
|
||||
|
|
@ -10,18 +10,15 @@ import (
|
|||
"path/filepath"
|
||||
)
|
||||
|
||||
// WorkspaceType indicates how the working directory was set up.
|
||||
type WorkspaceType string
|
||||
|
||||
const (
|
||||
WorkspaceTypeGitWorktree WorkspaceType = "git_worktree"
|
||||
WorkspaceTypeDirectory WorkspaceType = "directory"
|
||||
)
|
||||
// RepoContextForEnv describes a workspace repo available for checkout.
|
||||
type RepoContextForEnv struct {
|
||||
URL string // remote URL
|
||||
Description string // human-readable description
|
||||
}
|
||||
|
||||
// PrepareParams holds all inputs needed to set up an execution environment.
|
||||
type PrepareParams struct {
|
||||
WorkspacesRoot string // base path for all envs (e.g., ~/multica_workspaces)
|
||||
RepoPath string // source git repo path (for worktree creation), provided per-task by server
|
||||
TaskID string // task UUID — used for directory name
|
||||
AgentName string // for git branch naming only
|
||||
Provider string // agent provider ("claude", "codex") — determines skill injection paths
|
||||
|
|
@ -34,6 +31,7 @@ type TaskContextForEnv struct {
|
|||
AgentName string
|
||||
AgentInstructions string // agent identity/persona instructions, injected into CLAUDE.md
|
||||
AgentSkills []SkillContextForEnv
|
||||
Repos []RepoContextForEnv // workspace repos available for checkout
|
||||
}
|
||||
|
||||
// SkillContextForEnv represents a skill to be written into the execution environment.
|
||||
|
|
@ -55,18 +53,15 @@ type Environment struct {
|
|||
RootDir string
|
||||
// WorkDir is the directory to pass as Cwd to the agent ({RootDir}/workdir/).
|
||||
WorkDir string
|
||||
// Type indicates git_worktree or directory.
|
||||
Type WorkspaceType
|
||||
// BranchName is the git branch name (empty for directory type).
|
||||
BranchName string
|
||||
// CodexHome is the path to the per-task CODEX_HOME directory (set only for codex provider).
|
||||
CodexHome string
|
||||
|
||||
gitRoot string // source repo root (for cleanup)
|
||||
logger *slog.Logger // for cleanup logging
|
||||
logger *slog.Logger // for cleanup logging
|
||||
}
|
||||
|
||||
// Prepare creates an isolated execution environment for a task.
|
||||
// The workdir starts empty (no repo checkouts). The agent checks out repos
|
||||
// on demand via `multica repo checkout <url>`.
|
||||
func Prepare(params PrepareParams, logger *slog.Logger) (*Environment, error) {
|
||||
if params.WorkspacesRoot == "" {
|
||||
return nil, fmt.Errorf("execenv: workspaces root is required")
|
||||
|
|
@ -95,35 +90,9 @@ func Prepare(params PrepareParams, logger *slog.Logger) (*Environment, error) {
|
|||
env := &Environment{
|
||||
RootDir: envRoot,
|
||||
WorkDir: workDir,
|
||||
Type: WorkspaceTypeDirectory,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
// Detect git repo and set up worktree if available.
|
||||
if params.RepoPath != "" {
|
||||
if gitRoot, ok := detectGitRepo(params.RepoPath); ok {
|
||||
branchName := fmt.Sprintf("agent/%s/%s", sanitizeName(params.AgentName), shortID(params.TaskID))
|
||||
|
||||
// Get the default branch as base ref.
|
||||
baseRef := getDefaultBranch(gitRoot)
|
||||
|
||||
if err := setupGitWorktree(gitRoot, workDir, branchName, baseRef); err != nil {
|
||||
logger.Warn("execenv: git worktree setup failed, falling back to directory mode", "error", err)
|
||||
} else {
|
||||
env.Type = WorkspaceTypeGitWorktree
|
||||
env.BranchName = branchName
|
||||
env.gitRoot = gitRoot
|
||||
|
||||
// Exclude injected directories from git tracking.
|
||||
for _, pattern := range []string{".agent_context", ".claude", "CLAUDE.md", "AGENTS.md"} {
|
||||
if err := excludeFromGit(workDir, pattern); err != nil {
|
||||
logger.Warn("execenv: failed to exclude from git", "pattern", pattern, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write context files into workdir (skills go to provider-native paths).
|
||||
if err := writeContextFiles(workDir, params.Provider, params.Task); err != nil {
|
||||
return nil, fmt.Errorf("execenv: write context files: %w", err)
|
||||
|
|
@ -143,7 +112,7 @@ func Prepare(params PrepareParams, logger *slog.Logger) (*Environment, error) {
|
|||
env.CodexHome = codexHome
|
||||
}
|
||||
|
||||
logger.Info("execenv: prepared env", "root", envRoot, "type", env.Type, "branch", env.BranchName)
|
||||
logger.Info("execenv: prepared env", "root", envRoot, "repos_available", len(params.Task.Repos))
|
||||
return env, nil
|
||||
}
|
||||
|
||||
|
|
@ -155,11 +124,6 @@ func (env *Environment) Cleanup(removeAll bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Remove git worktree first (must happen before directory deletion).
|
||||
if env.Type == WorkspaceTypeGitWorktree && env.gitRoot != "" {
|
||||
removeGitWorktree(env.gitRoot, env.WorkDir, env.BranchName, env.logger)
|
||||
}
|
||||
|
||||
if removeAll {
|
||||
if err := os.RemoveAll(env.RootDir); err != nil {
|
||||
env.logger.Warn("execenv: cleanup removeAll failed", "error", err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue