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: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 - <concrete fixes>`. FAIL becomes injected fix notes for the next worker; PASS ends the loop unless polish mode is on. Polish cycles end with `VERDICT: IMPROVE - <one improvement>` 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`.

Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions docs/evaluation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Independent evaluation

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 |
| --- | --- | --- |
| 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**.
12 changes: 12 additions & 0 deletions examples/query-parser/GUIDELINES.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions examples/query-parser/PLAN.md
Original file line number Diff line number Diff line change
@@ -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.
13 changes: 13 additions & 0 deletions examples/query-parser/STATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# State

## Completed

- Nothing yet.

## Next

- Repair the query parser and verify it.

## Notes

- No critic feedback yet.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
9 changes: 9 additions & 0 deletions examples/query-parser/query-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function parseQuery(input) {
const query = input.replace(/^\?/, '');

return Object.fromEntries(query.split('&').map((part) => (
part.split('=').map((value) => decodeURIComponent(value))
)));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

module.exports = { parseQuery };
Comment thread
aiedwardyi marked this conversation as resolved.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"version": "0.1.0",
"private": true,
"description": "Sequential fresh-context agent orchestrator",
"scripts": {
"test": "node test/sandbox.test.js"
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"engines": {
"node": ">=18"
}
Expand Down
10 changes: 8 additions & 2 deletions src/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
37 changes: 37 additions & 0 deletions test/sandbox.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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');
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/);

for (const functionName of ['spawnLoopSession', 'spawnWorker']) {
const args = getSessionArgs(functionName);

for (const expectedArg of sandboxArgs) {
assert.ok(args.includes(expectedArg), `${functionName} is missing ${expectedArg}`);
}
}
});