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: 11 additions & 0 deletions .agent-zero.example.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,11 @@ version: 1
# observe and suggest can never write. fix and autonomous also require autofix.enabled below.
mode: observe

# Inspect pull-request diffs when authenticated pull_request webhooks arrive. The configured mode
# above controls whether the run reports only or requests autofix authority.
proactive:
enabled: false

# Commands used to verify a change. Leave empty to discover this repository's own
# lint, typecheck, test, and build scripts. Commands run without a shell, so
# operators such as &&, |, ;, and $() are rejected.
Expand All@@ -12,6 +17,12 @@ autofix:
enabled: false
# Confidence required before Agent Zero may change files.
minConfidence: 0.85
# mechanical is the conservative default. behavioral may be added explicitly; high-impact
# changes always require human approval and cannot be enabled here.
allowedChangeRisks:
- mechanical
# Proactive and autonomous writes require a runner that can prove isolation.
requireIsolated: true

# How a reviewer's claim is checked against the repository before it is acted on.
validation:
Expand Down
1 change: 1 addition & 0 deletions .gitattributes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
*text=autoeol=lf
16 changes: 10 additions & 6 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,11 +17,13 @@

## Overview

Agent Zero runs one trustworthy loop: ingest review feedback, validate the claim, apply a narrowly scoped fix, run the repository's real checks, inspect the resulting diff, and produce evidence.
Agent Zero runs one trustworthy loop: ingest review feedback or inspect a pull-request diff proactively, validate the finding, apply a narrowly scoped policy-approved fix, run the repository's real checks, inspect the resulting diff, and produce evidence.

Feedback is never treated as truth merely because it came from a human or an AI reviewer.

- **Evidence over assertion** – every fix carries the commands that verified it.
- **Proactive, not speculative** – diff review reports the highest-priority finding only when checkout evidence supports it.
- **Confidence and impact gates** – automatic fixes require confidence, an allowed change-risk class, repository permission, and verification.
- **`observe` by default** – the safe mode inspects and reports, and never writes to a target repository.
- **One execution boundary** – `packages/runner` is the only code allowed to run commands or mutate a checkout.
- **Adapters at the edges** – the runtime stays independent of HTTP, GitHub, terminal UI, and model providers.
Expand DownExpand Up@@ -72,6 +74,7 @@ cp .env.example .env
aube test
aube run zero doctor
aube run zero review --feedback "Possible null dereference in src/user.ts"
aube run zero review --proactive
aube run dev
```

Expand All@@ -85,12 +88,12 @@ aube run dev
zero init create .agent-zero.yml
zero --version print the injected CLI version
zero doctor [--json] inspect the local environment
zero review [--feedback X] validate feedback without editing
zero fix [--feedback X] validate, edit, and verify (policy permitting)
zero run [--feedback X] run using the configured mode
zero review (--feedback X | --proactive) inspect without editing
zero fix (--feedback X | --proactive) validate, edit, and verify (policy permitting)
zero run (--feedback X | --proactive) run using the configured mode
```

The CLI parses arguments with [`@bomb.sh/args`](https://github.com/bomb-sh/args) and renders with [`@clack/prompts`](https://github.com/bombshell-dev/clack). When `--feedback` is omitted in a terminal, it asks for the task interactively; use `--feedback` and `--json` for scripts and CI.
The CLI parses arguments with [`@bomb.sh/args`](https://github.com/bomb-sh/args) and renders with [`@clack/prompts`](https://github.com/bombshell-dev/clack). Use `--proactive` to inspect the working-tree diff without reviewer feedback. When neither trigger is provided in a terminal, it asks for the task interactively; use `--feedback` or `--proactive` with `--json` for scripts and CI.

---

Expand All@@ -109,9 +112,10 @@ const zero: RouterClient<AppRouter> = createORPCClient(
);

await zero.tasks.create({ repository: '.', feedback: 'Check error handling', mode: 'observe' });
await zero.tasks.create({ repository: '.', trigger: 'proactive', mode: 'observe' });
```

`observe` is the safe default and never writes files. Set `mode: fix` and `autofix.enabled: true` in `.agent-zero.yml` only after configuring a model provider and an isolated runner.
`observe` is the safe default and never writes files. Proactive pull-request webhooks are ignored until `proactive.enabled` is true. Automatic changes additionally require `mode: fix` or `autonomous`, `autofix.enabled`, sufficient confidence, an allowed change-risk class, repository-native checks, and (by default for proactive/autonomous work) an isolated runner. High-impact changes always require human approval.

---

Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "@agent-zero/server",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"scripts": {
"build": "nitro build",
Expand Down
58 changes: 56 additions & 2 deletions apps/server/src/router.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,11 @@ function reviewPayload(overrides: Record<string, unknown> = {}): string {
return JSON.stringify({
action: 'submitted',
repository: { name: 'app', owner: { login: 'acme' } },
pull_request: { number: 7, head: { sha: 'a'.repeat(40) } },
pull_request: {
number: 7,
base: { sha: 'b'.repeat(40) },
head: { sha: 'a'.repeat(40) },
},
review: {
id: 1,
body: 'load() can return null',
Expand DownExpand Up@@ -165,12 +169,56 @@ describe('ingestWebhook', () => {
owner: 'acme',
repo: 'app',
number: 7,
baseSha: 'b'.repeat(40),
headSha: 'a'.repeat(40),
});
expect(outcome.result.runner.writable).toBe(false);
expect(outcome.result.changedFiles).toEqual([]);
expect(outcome.result.summary).toContain('github:acme/app#7');
});

it('ignores proactive pull-request events until repository policy enables them', async () => {
const body = JSON.stringify({
action: 'synchronize',
repository: { name: 'app', owner: { login: 'acme' } },
pull_request: {
number: 7,
base: { sha: 'b'.repeat(40) },
head: { sha: 'a'.repeat(40) },
},
});
await expect(
ingestWebhook({ event: 'pull_request', body, signature: sign(body) }, options()),
).resolves.toEqual({
status: 'ignored',
reason: 'Proactive review is disabled by repository policy',
});
});

it('runs an enabled proactive pull-request review in repository mode', async () => {
await writeFile(
join(checkout, '.agent-zero.yml'),
'version: 1\nproactive:\n enabled: true\nmode: observe\n',
'utf8',
);
const body = JSON.stringify({
action: 'opened',
repository: { name: 'app', owner: { login: 'acme' } },
pull_request: {
number: 7,
base: { sha: 'b'.repeat(40) },
head: { sha: 'a'.repeat(40) },
},
});
const outcome = await ingestWebhook(
{ event: 'pull_request', body, signature: sign(body) },
options(),
);
expect(outcome.status).toBe('accepted');
if (outcome.status !== 'accepted') return;
expect(outcome.result.runner.writable).toBe(false);
expect(getTaskEvidence(outcome.result.id)).toContain('proactive finding');
});
});

type FetchArguments = Parameters<typeof globalThis.fetch>;
Expand DownExpand Up@@ -200,7 +248,13 @@ function recordingFetch(): {
}

describe('publishEvidence', () => {
const target = { owner: 'acme', repo: 'app', number: 7, headSha: 'a'.repeat(40) };
const target = {
owner: 'acme',
repo: 'app',
number: 7,
baseSha: 'b'.repeat(40),
headSha: 'a'.repeat(40),
};

it('skips publishing rather than faking a check without a token', async () => {
const result = await runTask({ repository: checkout, feedback: 'x', mode: 'observe' });
Expand Down
43 changes: 30 additions & 13 deletions apps/server/src/router.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,16 +26,22 @@ export interface StoredTask {

export const tasks = new Map<string, StoredTask>();

export const taskInput = z.object({
repository: z.string().min(1),
feedback: z.string().min(1),
mode: z.enum(['observe', 'suggest', 'fix', 'autonomous']),
source: z.string().optional(),
files: z.array(z.string()).optional(),
});
export const taskInput = z
.object({
repository: z.string().min(1),
feedback: z.string().min(1).optional(),
trigger: z.enum(['feedback', 'proactive']).default('feedback'),
mode: z.enum(['observe', 'suggest', 'fix', 'autonomous']),
source: z.string().optional(),
files: z.array(z.string()).optional(),
})
.superRefine((input, context) => {
if (input.trigger !== 'proactive' && input.feedback === undefined)
context.addIssue({ code: 'custom', path: ['feedback'], message: 'Feedback is required' });
});

export function health() {
return { status: 'ok' as const, service: 'agent-zero', version: '0.1.0' };
return { status: 'ok' as const, service: 'agent-zero', version: '0.2.0' };
}

export function listTasks() {
Expand All@@ -55,8 +61,9 @@ export function getTaskEvidence(id: string): string | undefined {
export async function createTask(input: z.infer<typeof taskInput>): Promise<TaskResult> {
return runTask({
repository: input.repository,
feedback: input.feedback,
mode: input.mode,
trigger: input.trigger,
...(input.feedback ? { feedback: input.feedback } : {}),
...(input.source ? { source: input.source } : {}),
...(input.files ? { files: input.files } : {}),
});
Expand DownExpand Up@@ -109,9 +116,9 @@ export type WebhookOutcome =
/**
* Handle an inbound GitHub webhook.
*
* The signature is verified before the payload is parsed, and the resulting run always uses
* `observe`. An unauthenticated request can therefore never cause a repository write, and neither
* can an authenticated one without an explicit follow-up.
* The signature is verified before the payload is parsed. Feedback-triggered runs remain
* read-only; proactive pull-request runs use repository mode only after proactive review is
* explicitly enabled in that checkout. Writes still require the independent autofix policy gate.
*/
export async function ingestWebhook(
request: WebhookRequest,
Expand All@@ -134,7 +141,17 @@ export async function ingestWebhook(
);
if (!event) return { status: 'ignored', reason: 'No actionable review feedback in this event' };

const result = await runTask(reviewInputFromEvent(event, { checkoutPath: options.checkoutPath }));
let mode: ReviewInput['mode'] = 'observe';
if (event.trigger === 'proactive') {
const config = await loadConfig(options.checkoutPath);
if (!config.proactive.enabled)
return { status: 'ignored', reason: 'Proactive review is disabled by repository policy' };
mode = config.mode;
}

const result = await runTask(
reviewInputFromEvent(event, { checkoutPath: options.checkoutPath, mode }),
);
return { status: 'accepted', result, pullRequest: event.pullRequest };
}

Expand Down
6 changes: 4 additions & 2 deletions docs/architecture.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,8 +45,8 @@ discover -> understand -> validate -> plan -> execute -> verify -> review

Each stage owns one decision:

- **discover** collects the checkout, its diff, and its native check commands through the runner.
- **understand** asks the model to interpret the untrusted feedback in repository context.
- **discover** collects the checkout, its working-tree or pull-request base-to-head diff, and its native check commands through the runner.
- **understand** asks the model to interpret untrusted feedback or proactively inspect the complete diff in repository context.
- **validate** decides the verdict from repository evidence, never from the reviewer's or the model's assertion.
- **plan** records the plan and resolves authorization. Each refusal is a distinct reportable outcome rather than a silent downgrade.
- **execute** applies changes restricted to the validated scope, through the runner.
Expand All@@ -55,6 +55,8 @@ Each stage owns one decision:

Repair re-enters `plan` with the failing output as context, until `agent.maxAttempts` is spent.

Proactive review is repository opt-in. Its model decision carries severity, confidence, cited evidence, affected files, and a change-risk classification. The runtime validates the evidence independently, then requires confidence and repository policy to allow the risk class. High-impact changes always stop at `needs-human`; proactive or autonomous writes use an isolated runner when policy requires it.

## Verdicts and evidence

Validation lives in `packages/agent/src/validation.ts` and is independent of any provider. It rejects a claim that cites no evidence, names no existing file, or quotes repository content that is not there; it reports a supported but low-confidence claim as inconclusive. Rejection reasons are collected in full rather than short-circuiting on the first, because the report is the product.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "agent-zero",
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"description": "Open-source autonomous engineer that finds, fixes, and verifies problems in pull requests.",
"license": "Apache-2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/agent/package.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
{
"name": "@agent-zero/agent",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
Expand Down
Loading
Loading