Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
```

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function createCLI(): Command {
.command('run')
.description('Run the full self-coding analysis and task execution')
.option('-c, --config <path>', 'Path to configuration file')
.option('--mode <mode>', 'Execution mode (docker|bare|worktree)', 'bare')
.option('--mode <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
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/configReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/taskExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/taskSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "";
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/taskSolverManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
23 changes: 22 additions & 1 deletion packages/core/test/configReaderSupplementary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
});
});
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;
}
});
});
Loading