Diff
1diff --git a/README.md b/README.md
2index 55c26ecae1b4958cb000ad86fb3d3f8ff7774d54..847e959b19117660a510c07973a31dae97887a0c 100644
3--- a/README.md
4+++ b/README.md
5@@ -46,6 +46,16 @@ Pi reviewers accept `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `ma
6
7 `promptCacheKey` improves cache routing for Pi models that support it. It does not guarantee a cache hit.
8
9+Disable LLM reviews. Requests that reach this stage ask for approval. Static rules, deterministic rules, and cached decisions still apply.
10+
11+```json
12+{
13+ "reviewer": {
14+ "kind": "none"
15+ }
16+}
17+```
18+
19 Use local llama.cpp. This backend does not use authentication.
20
21 ```json
22diff --git a/src/core/config.ts b/src/core/config.ts
23index 8c585217cb6fe564de3c345e65bd68896317d13f..d9d0f6eb127d08f7f6ce0786393fa6ca0fbe7da8 100644
24--- a/src/core/config.ts
25+++ b/src/core/config.ts
26@@ -41,6 +41,8 @@ export function parseReviewerConfig(value: unknown): ReviewerConfig {
27 reasoningEffort: "minimal",
28 };
29 }
30+ if (reviewer.kind === "none") return { kind: "none" };
31+
32 const model = stringValue(reviewer.model);
33 if (!model) throw new Error("reviewer.model must be a non-empty string");
34 if (reviewer.kind === "llama.cpp") {
35@@ -53,7 +55,7 @@ export function parseReviewerConfig(value: unknown): ReviewerConfig {
36 enableThinking: booleanValue(reviewer.enableThinking, "reviewer.enableThinking", false),
37 };
38 }
39- if (reviewer.kind !== "pi") throw new Error('reviewer.kind must be "pi" or "llama.cpp"');
40+ if (reviewer.kind !== "pi") throw new Error('reviewer.kind must be "none", "pi", or "llama.cpp"');
41
42 const provider = stringValue(reviewer.provider);
43 if (!provider) throw new Error("reviewer.provider must be a non-empty string");
44diff --git a/src/core/review.ts b/src/core/review.ts
45index a66b6335d6f31834c54aaf066a86c3a80884e872..66acdb1732e00008133243f452969635aa6c4d3f 100644
46--- a/src/core/review.ts
47+++ b/src/core/review.ts
48@@ -68,6 +68,16 @@ export function createPolicyReviewer(
49 ): PolicyReviewer {
50 return {
51 async evaluate(request: PolicyRequest, context: PolicyContext): Promise<LLMEvaluationResult> {
52+ if (config.kind === "none") {
53+ return {
54+ decision: {
55+ decision: "ask",
56+ reason: "LLM reviews are disabled",
57+ category: "uncertain",
58+ },
59+ };
60+ }
61+
62 try {
63 const messages = [
64 { role: "system" as const, content: llmPolicyPrompt(externalDirectories) },
65diff --git a/src/core/types.ts b/src/core/types.ts
66index 49b4b1286a8cf6bd80f1719be72afe9e799ef24e..4a08e046b3c9f5c8c1526cb21316a009012f4598 100644
67--- a/src/core/types.ts
68+++ b/src/core/types.ts
69@@ -50,6 +50,9 @@ export type PolicyReviewer = {
70 export type PiReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
71
72 export type ReviewerConfig =
73+ | {
74+ kind: "none";
75+ }
76 | {
77 kind: "pi";
78 provider: string;