From 2a9a9e933625e83eabbe09d33eef642094c86daa Mon Sep 17 00:00:00 2001 From: aiedwardyi <41576951+aiedwardyi@users.noreply.github.com> Date: Wed, 22 Jul 2026 05:01:03 +0900 Subject: [PATCH 1/4] fix: sandbox worker sessions --- package.json | 3 +++ src/daemon.js | 10 ++++++++-- test/sandbox.test.js | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 test/sandbox.test.js diff --git a/package.json b/package.json index 555b324..bbc6b01 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "0.1.0", "private": true, "description": "Sequential fresh-context agent orchestrator", + "scripts": { + "test": "node --test test/sandbox.test.js" + }, "engines": { "node": ">=18" } diff --git a/src/daemon.js b/src/daemon.js index ea9c563..a28b257 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -766,7 +766,10 @@ function spawnLoopSession(loop, cycle, role, prompt, onFinish) { const args = [ 'exec', '--json', - '--dangerously-bypass-approvals-and-sandbox', + '--sandbox', 'workspace-write', + '--config', 'approval_policy="on-request"', + '--config', 'approvals_reviewer="auto_review"', + '--config', 'sandbox_workspace_write.network_access=false', '--skip-git-repo-check', '--output-last-message', outputPath, '--model', model, @@ -1258,7 +1261,10 @@ function spawnWorker(task) { const args = [ 'exec', '--json', - '--dangerously-bypass-approvals-and-sandbox', + '--sandbox', 'workspace-write', + '--config', 'approval_policy="on-request"', + '--config', 'approvals_reviewer="auto_review"', + '--config', 'sandbox_workspace_write.network_access=false', '--skip-git-repo-check', '--output-last-message', outputPath, '--model', model, diff --git a/test/sandbox.test.js b/test/sandbox.test.js new file mode 100644 index 0000000..f7a6c04 --- /dev/null +++ b/test/sandbox.test.js @@ -0,0 +1,14 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const daemonSource = fs.readFileSync(path.join(__dirname, '..', 'src', 'daemon.js'), 'utf8'); + +test('Codex sessions use workspace sandboxing', () => { + assert.doesNotMatch(daemonSource, /--dangerously-bypass-approvals-and-sandbox/); + assert.equal((daemonSource.match(/'--sandbox', 'workspace-write'/g) || []).length, 2); + assert.equal((daemonSource.match(/'approval_policy="on-request"'/g) || []).length, 2); + assert.equal((daemonSource.match(/'approvals_reviewer="auto_review"'/g) || []).length, 2); + assert.equal((daemonSource.match(/'sandbox_workspace_write\.network_access=false'/g) || []).length, 2); +}); From bbdc9aac5a7b524c23b5a1c94aeb64d1aceaee1e Mon Sep 17 00:00:00 2001 From: aiedwardyi <41576951+aiedwardyi@users.noreply.github.com> Date: Wed, 22 Jul 2026 05:01:36 +0900 Subject: [PATCH 2/4] test: add query parser evaluation --- examples/query-parser/GUIDELINES.md | 12 ++++++++++++ examples/query-parser/PLAN.md | 8 ++++++++ examples/query-parser/STATE.md | 13 +++++++++++++ examples/query-parser/query-string.js | 9 +++++++++ 4 files changed, 42 insertions(+) create mode 100644 examples/query-parser/GUIDELINES.md create mode 100644 examples/query-parser/PLAN.md create mode 100644 examples/query-parser/STATE.md create mode 100644 examples/query-parser/query-string.js diff --git a/examples/query-parser/GUIDELINES.md b/examples/query-parser/GUIDELINES.md new file mode 100644 index 0000000..2a3928e --- /dev/null +++ b/examples/query-parser/GUIDELINES.md @@ -0,0 +1,12 @@ +# Quality Guidelines + +- `parseQuery(input)` accepts strings and throws `TypeError` for other values. +- A leading `?` is optional; an empty query returns an empty object. +- Keys and values decode percent escapes and convert `+` to spaces. +- Repeated keys become arrays in encounter order. +- A key without `=` receives an empty string value. +- Malformed percent escapes remain readable instead of crashing the parser. +- Keys such as `__proto__`, `constructor`, and `prototype` cannot mutate object prototypes. +- The command-line entry point accepts one query argument, prints JSON, and shows usage with a non-zero exit when missing. +- Tests use the built-in Node test runner and cover every requirement. +- No package dependencies are added. diff --git a/examples/query-parser/PLAN.md b/examples/query-parser/PLAN.md new file mode 100644 index 0000000..fec179c --- /dev/null +++ b/examples/query-parser/PLAN.md @@ -0,0 +1,8 @@ +# Query parser repair + +Repair `query-string.js` for use as a dependable CommonJS utility and command-line tool. + +- Preserve the `parseQuery(input)` export. +- Replace the fragile parsing behavior with a robust implementation. +- Add focused automated tests and concise command-line usage. +- Complete the task in one pass if possible. Do not create artificial cycle boundaries. diff --git a/examples/query-parser/STATE.md b/examples/query-parser/STATE.md new file mode 100644 index 0000000..2d99145 --- /dev/null +++ b/examples/query-parser/STATE.md @@ -0,0 +1,13 @@ +# State + +## Completed + +- Nothing yet. + +## Next + +- Repair the query parser and verify it. + +## Notes + +- No critic feedback yet. diff --git a/examples/query-parser/query-string.js b/examples/query-parser/query-string.js new file mode 100644 index 0000000..35256db --- /dev/null +++ b/examples/query-parser/query-string.js @@ -0,0 +1,9 @@ +function parseQuery(input) { + const query = input.replace(/^\?/, ''); + + return Object.fromEntries(query.split('&').map((part) => ( + part.split('=').map((value) => decodeURIComponent(value)) + ))); +} + +module.exports = { parseQuery }; From 9331de64e9763981b4c8e68a9c923ce08ab357bf Mon Sep 17 00:00:00 2001 From: aiedwardyi <41576951+aiedwardyi@users.noreply.github.com> Date: Wed, 22 Jul 2026 05:02:12 +0900 Subject: [PATCH 3/4] docs: document audience and evaluation --- README.md | 11 +++++++++-- docs/evaluation.md | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 docs/evaluation.md diff --git a/README.md b/README.md index 4af1a73..117e3fc 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Self-improving fresh-context loops for coding work you can watch. Plan a goal in ChatGPT, then let it rip. AgentLoop is a local orchestration daemon for coding agents: each cycle starts a fresh worker, work carries forward in project files, a fresh critic enforces your rubric, and the whole run is watchable on a local dashboard. +AgentLoop is for solo developers who run long Codex tasks across multiple projects and cannot supervise every session. + It exists because running coding agents by hand means shuttling plans between a chat and a terminal all day, and quality slips the moment you stop watching. Your standards live in GUIDELINES.md and the critic enforces them every cycle, so you supervise the work without babysitting it. @@ -32,6 +34,7 @@ ChatGPT -> MCP bridge -> daemon -> worker/critic cycles -> dashboard - **Critic contract** requires the final line to be exactly `VERDICT: PASS` or `VERDICT: FAIL - `. FAIL becomes injected fix notes for the next worker; PASS ends the loop unless polish mode is on. Polish cycles end with `VERDICT: IMPROVE - ` or `VERDICT: SHIP`. `maxCycles` is capped at 1 to 10 and defaults to 3. - **Files are memory.** `PLAN.md`, `STATE.md`, and `GUIDELINES.md` carry the goal, progress, and rubric. A loop project needs `PLAN.md`; missing `STATE.md` and `GUIDELINES.md` files are seeded automatically. - **Messages narrate a run.** A connected chat client can post `info`, `question`, or `results` messages through the bridge. They appear in the dashboard Messages panel. +- **Workers are sandboxed.** Every Codex session uses workspace-write sandboxing, disables network access inside the sandbox, and routes boundary requests through automatic approval review. The daemon is plain Node with no package dependencies. Task state, results, transcripts, events, and messages are stored as JSON or NDJSON files. The dashboard is one local HTML file at `http://127.0.0.1:5757`. @@ -41,6 +44,12 @@ AgentLoop started as my own bottleneck. I was the relay between ChatGPT planning AgentLoop then runs Codex CLI as both its worker and critic engine. Codex built a tool that drives Codex. +## Independent evaluation + +The reproducible [query parser evaluation](examples/query-parser) asked for the full repair in one pass. Cycle 1 produced nine passing tests, but a fresh critic found a mixed percent-decoding defect and returned FAIL. Cycle 2 fixed it, added regression coverage, passed 11 tests, and received PASS from a new critic. + +[Read the evaluation record](docs/evaluation.md). + ## Quickstart Requirements: @@ -131,8 +140,6 @@ On every platform, install and authenticate Codex CLI first. The daemon and brid - **Two-way messages.** The dashboard already receives questions from the chat client; answering from the panel closes the loop. -- **OS-level sandboxing.** Workers are prompt-confined today; a real sandbox hardens long unattended runs. - - **More engines.** The engine layer is pluggable by design. Codex ships first. ## License diff --git a/docs/evaluation.md b/docs/evaluation.md new file mode 100644 index 0000000..a0503bb --- /dev/null +++ b/docs/evaluation.md @@ -0,0 +1,16 @@ +# Independent evaluation + +On July 22, 2026, AgentLoop ran the reproducible [query parser fixture](../examples/query-parser) with three cycles available and polish disabled. The [plan](../examples/query-parser/PLAN.md) requested the complete repair in one pass and prohibited artificial cycle boundaries. The [guidelines](../examples/query-parser/GUIDELINES.md) defined ten acceptance criteria. + +| Cycle | Worker result | Independent critic result | +| --- | --- | --- | +| 1 | Repaired the parser and added nine passing tests. | `FAIL`: valid percent escapes remained encoded when a field also contained malformed escapes. | +| 2 | Fixed tolerant decoding and added regression coverage. | `PASS`: all criteria were satisfied and 11 tests passed. | + +The first worker's own suite passed. A fresh critic tested beyond it, found a real defect, and converted the finding into instructions for the next fresh worker. No package dependencies were added. + +## Reproduce + +1. Start the daemon and select **+ New**, then **Loop**. +2. Set **Project** to `examples/query-parser` and **Max cycles** to `3`. +3. Leave polish disabled and select **Start loop**. From 7fe4f2ea9f85486440d34ff90af41dbe1621601b Mon Sep 17 00:00:00 2001 From: aiedwardyi <41576951+aiedwardyi@users.noreply.github.com> Date: Wed, 22 Jul 2026 05:23:32 +0900 Subject: [PATCH 4/4] fix: strengthen validation coverage --- docs/evaluation.md | 4 +++- package.json | 2 +- test/sandbox.test.js | 31 +++++++++++++++++++++++++++---- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/docs/evaluation.md b/docs/evaluation.md index a0503bb..6290a1d 100644 --- a/docs/evaluation.md +++ b/docs/evaluation.md @@ -1,6 +1,8 @@ # Independent evaluation -On July 22, 2026, AgentLoop ran the reproducible [query parser fixture](../examples/query-parser) with three cycles available and polish disabled. The [plan](../examples/query-parser/PLAN.md) requested the complete repair in one pass and prohibited artificial cycle boundaries. The [guidelines](../examples/query-parser/GUIDELINES.md) defined ten acceptance criteria. +AgentLoop ran the reproducible [query parser fixture](../examples/query-parser) with three cycles available and polish disabled. The [plan](../examples/query-parser/PLAN.md) requested the complete repair in one pass and prohibited artificial cycle boundaries. The [guidelines](../examples/query-parser/GUIDELINES.md) defined ten acceptance criteria. + +The committed fixture is the pre-run starting state. A reproduction run repairs that working copy, adds tests, and updates `STATE.md`. | Cycle | Worker result | Independent critic result | | --- | --- | --- | diff --git a/package.json b/package.json index bbc6b01..c61ccd1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "description": "Sequential fresh-context agent orchestrator", "scripts": { - "test": "node --test test/sandbox.test.js" + "test": "node test/sandbox.test.js" }, "engines": { "node": ">=18" diff --git a/test/sandbox.test.js b/test/sandbox.test.js index f7a6c04..adf746c 100644 --- a/test/sandbox.test.js +++ b/test/sandbox.test.js @@ -4,11 +4,34 @@ const fs = require('node:fs'); const path = require('node:path'); const daemonSource = fs.readFileSync(path.join(__dirname, '..', 'src', 'daemon.js'), 'utf8'); +const sandboxArgs = [ + "'--sandbox', 'workspace-write'", + "'approval_policy=\"on-request\"'", + "'approvals_reviewer=\"auto_review\"'", + "'sandbox_workspace_write.network_access=false'", +]; + +function getSessionArgs(functionName) { + const functionStart = daemonSource.indexOf(`function ${functionName}(`); + const functionEnd = daemonSource.indexOf('\nfunction ', functionStart + 1); + const argsStart = daemonSource.indexOf(' const args = [', functionStart); + const argsEnd = daemonSource.indexOf('\n ];', argsStart); + + assert.notEqual(functionStart, -1); + assert.ok(argsStart > functionStart); + assert.ok(argsEnd > argsStart); + assert.ok(functionEnd === -1 || argsEnd < functionEnd); + return daemonSource.slice(argsStart, argsEnd); +} test('Codex sessions use workspace sandboxing', () => { assert.doesNotMatch(daemonSource, /--dangerously-bypass-approvals-and-sandbox/); - assert.equal((daemonSource.match(/'--sandbox', 'workspace-write'/g) || []).length, 2); - assert.equal((daemonSource.match(/'approval_policy="on-request"'/g) || []).length, 2); - assert.equal((daemonSource.match(/'approvals_reviewer="auto_review"'/g) || []).length, 2); - assert.equal((daemonSource.match(/'sandbox_workspace_write\.network_access=false'/g) || []).length, 2); + + for (const functionName of ['spawnLoopSession', 'spawnWorker']) { + const args = getSessionArgs(functionName); + + for (const expectedArg of sandboxArgs) { + assert.ok(args.includes(expectedArg), `${functionName} is missing ${expectedArg}`); + } + } });