diff --git a/README.md b/README.md index 57920f2..646292e 100644 --- a/README.md +++ b/README.md @@ -38,17 +38,18 @@ Full Self Coding (FSC) is a sophisticated framework designed to automate softwar ### Execution Modes -FSC supports two execution modes — choose based on your environment: +FSC runs agents as native processes by default. Docker is an optional isolation mode, not a requirement. | Mode | Isolation | Overhead | Best For | |------|-----------|----------|----------| -| **Docker** | Full container isolation | ~200MB/agent | Production, untrusted code | -| **bare** (WorktreeExecutor) | Git worktree | ~0 | Development, testing, resource-constrained servers | +| **worktree** (default) | One native git worktree per task, agent runs as a normal process | ~0 | Everyday use, many parallel agents | +| **bare** | Fresh git clone + spawned process | disk only | When worktrees are not suitable | +| **docker** (optional) | Full container isolation | ~200MB/agent | Untrusted code | -Configure via `.fsc/config.json`: +Select with `--mode`, the `FSC_EXECUTION_MODE` environment variable, or `.fsc/config.json`: ```json { - "executionMode": "bare" + "executionMode": "worktree" } ``` diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 54fdcde..5c651a7 100755 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -142,7 +142,7 @@ export function createCLI(): Command { .command('run') .description('Run the full self-coding analysis and task execution') .option('-c, --config ', 'Path to configuration file') - .option('--mode ', 'Execution mode (docker|bare|worktree)', 'bare') + .option('--mode ', 'Execution mode: worktree (native git worktree), bare, or docker (optional)', 'worktree') .action(async (options) => { try { // If config option is provided, add it to process.argv for the main function to pick up diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 462679a..7c3783b 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -143,8 +143,9 @@ export interface Config { customizedMessage?: string; /** - * Execution mode: 'docker' (container isolation) or 'bare' (git clone + Bun.spawn) - * @default 'docker' + * Execution mode: 'worktree' (native git worktree, default), 'bare' (git clone + Bun.spawn) + * or 'docker' (container isolation, optional) + * @default 'worktree' */ executionMode?: ExecutionMode; @@ -222,7 +223,7 @@ export interface Config { export const DEFAULT_CONFIG: Config = { agentType: SWEAgentType.CLAUDE_CODE, maxDockerContainers: 10, - executionMode: 'docker', + executionMode: 'worktree', dockerImageRef: 'node:latest', maxParallelDockerContainers: 10, dockerTimeoutSeconds: 300000, @@ -233,7 +234,7 @@ export const DEFAULT_CONFIG: Config = { workStyle: WorkStyle.DEFAULT, codingStyleLevel: 0, // Harness defaults - harnessMode: 'docker', + harnessMode: 'local', maxParallelAgents: 1, progressDir: './', enableProgrammaticToolCalling: false, diff --git a/packages/core/src/configReader.ts b/packages/core/src/configReader.ts index ab6b96a..47c9417 100644 --- a/packages/core/src/configReader.ts +++ b/packages/core/src/configReader.ts @@ -362,6 +362,14 @@ export class ConfigReader { const configWithEnv = { ...baseConfig }; + const mode = process.env.FSC_EXECUTION_MODE; + if (mode !== undefined) { + if (mode !== 'worktree' && mode !== 'bare' && mode !== 'docker') { + throw new Error(`FSC_EXECUTION_MODE must be worktree, bare or docker (got "${mode}")`); + } + configWithEnv.executionMode = mode; + } + // Apply environment variable overrides for (const [envVar, configKey] of Object.entries(envMappings)) { const envValue = process.env[envVar]; diff --git a/packages/core/src/taskExecutor.ts b/packages/core/src/taskExecutor.ts index 7594f9e..05b6e80 100644 --- a/packages/core/src/taskExecutor.ts +++ b/packages/core/src/taskExecutor.ts @@ -194,10 +194,12 @@ export class BareWorkspaceExecutor extends TaskExecutor { // ─── 工厂函数 ─── -export type ExecutionMode = "docker" | "bare"; +/** worktree = native git worktree per task (default); bare = clone + spawn; docker = container isolation (optional). */ +export type ExecutionMode = "worktree" | "bare" | "docker"; export function createTaskExecutor(mode: ExecutionMode): TaskExecutor { - if (mode === "bare") { + if (mode !== "docker") { + // "worktree" is scheduled by TaskSolverManager.startWorktreeMode; per-task fallback is native too. return new BareWorkspaceExecutor(); } // Docker mode: 动态导入原 DockerInstance 并包装为 TaskExecutor diff --git a/packages/core/src/taskSolver.ts b/packages/core/src/taskSolver.ts index 7eeda06..71219f0 100644 --- a/packages/core/src/taskSolver.ts +++ b/packages/core/src/taskSolver.ts @@ -28,7 +28,7 @@ export class TaskSolver { this.agentType = agentType; this.gitURL = gitURL; this.referenceContext = referenceContext || ""; - this.executor = createTaskExecutor(config.executionMode || 'docker'); + this.executor = createTaskExecutor(config.executionMode || 'worktree'); this.containerName = ""; } diff --git a/packages/core/src/taskSolverManager.ts b/packages/core/src/taskSolverManager.ts index 49ba7b3..793ce5a 100644 --- a/packages/core/src/taskSolverManager.ts +++ b/packages/core/src/taskSolverManager.ts @@ -63,7 +63,7 @@ export class TaskSolverManager { async start() { // Worktree mode: bypass Docker/bare scheduler - if ((this.config as any).executionMode === 'worktree') { + if (this.config.executionMode === 'worktree') { await this.startWorktreeMode(); return; } diff --git a/packages/core/test/configReaderSupplementary.test.ts b/packages/core/test/configReaderSupplementary.test.ts index 0d188d8..c58839d 100644 --- a/packages/core/test/configReaderSupplementary.test.ts +++ b/packages/core/test/configReaderSupplementary.test.ts @@ -178,4 +178,25 @@ describe('ConfigReader with Supplementary Configuration', () => { expect(config.maxDockerContainers).toBe(10); // From default config expect(config.customizedWorkStyle).toBe(undefined); // Should not have supplementary config values }); -}); \ No newline at end of file +}); +describe("executionMode", () => { + test("defaults to the native worktree mode", async () => { + const { DEFAULT_CONFIG } = await import("../src/config"); + expect(DEFAULT_CONFIG.executionMode).toBe("worktree"); + expect(DEFAULT_CONFIG.harnessMode).toBe("local"); + }); + + test("FSC_EXECUTION_MODE overrides the config and is validated", async () => { + const { readConfigWithEnv } = await import("../src/configReader"); + const prev = process.env.FSC_EXECUTION_MODE; + try { + process.env.FSC_EXECUTION_MODE = "docker"; + expect(readConfigWithEnv().executionMode).toBe("docker"); + process.env.FSC_EXECUTION_MODE = "nope"; + expect(() => readConfigWithEnv()).toThrow(/FSC_EXECUTION_MODE/); + } finally { + if (prev === undefined) delete process.env.FSC_EXECUTION_MODE; + else process.env.FSC_EXECUTION_MODE = prev; + } + }); +});