# Plan — wire `effort` (and `thinking`) for claude-cli agents

## Goal
Wire the SDK's `effort` parameter (`'low' | 'medium' | 'high' | 'xhigh' | 'max'`)
through the same priority chain as `thinking` (per-call > session > agent), and
set new defaults on the global `cc-*` agents.

## Background — what already works

- `thinking` is already wired through the resolver
  ([utils/effective-config.js:115-123](utils/effective-config.js#L115)) and
  passed to the SDK at
  [engines/claude-engine.js:320](engines/claude-engine.js#L320).
- The resolver pulls `agent.thinking` directly off the agent object — so any
  agent.json field becomes available on `agent` after `loadAgent()` does
  `{ ...config, ... }` ([core/agent.js:73](core/agent.js#L73)).
- **However** the agent JSON schema at
  [schemas/agent.json](schemas/agent.json) has `"additionalProperties": false`
  and **does NOT currently list `thinking` or `effort`** as known properties.
  This means putting them into a cc-* `agent.json` today would **fail
  validation** — that's why no cc-* agent has them set.
- The SDK accepts:
  - `thinking?: ThinkingConfig` ([sdk.d.ts:1316](node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts#L1316))
  - `effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max'` ([sdk.d.ts:1340](node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts#L1340))
- Per SDK docs:
  - `'xhigh'` — Opus 4.7 only
  - `'max'` — Opus 4.6 / 4.7 only
  - For sonnet/haiku, the practical maximum is `'high'` (which is also the default).
  Setting `'max'` on sonnet/haiku may be silently downgraded by the SDK or
  rejected at runtime. **Per user's instruction "Maximum thinking tag/effort
  whatever it is"** we set the highest value and let the SDK handle model-level
  capability matching.

## Tasks

### Task A — update `schemas/agent.json` to allow `thinking` and `effort`

Add to the top-level `properties` block:

```json
"thinking": {
  "type": "object",
  "additionalProperties": true,
  "description": "Claude SDK thinking config: { type: 'adaptive' | 'enabled' | 'disabled', budgetTokens?: number }. Ignored on non-claude-cli engines."
},
"effort": {
  "type": "string",
  "enum": ["low", "medium", "high", "xhigh", "max"],
  "description": "Claude SDK effort level. 'xhigh' is Opus 4.7 only; 'max' is Opus 4.6/4.7 only. Ignored on non-claude-cli engines."
}
```

`thinking` is left as `additionalProperties: true` to avoid hardcoding the SDK's
internal shape (which evolves: `adaptive`/`enabled`/`disabled`/`budget_tokens`/
`budgetTokens` etc.) — final validation happens in
`utils/effective-config.js:validateOverrides`.

### Task B — extend `utils/effective-config.js`

1. Add `'effort'` to `KNOWN_OVERRIDE_FIELDS` (currently
   [line 34](utils/effective-config.js#L34)).
2. Add validation in `validateOverrides` (mirror the `thinking` block at
   [line 79-89](utils/effective-config.js#L79)):
   ```js
   if (overrides.effort != null) {
     const valid = ['low', 'medium', 'high', 'xhigh', 'max'];
     if (typeof overrides.effort !== 'string' || !valid.includes(overrides.effort)) {
       throw _err('VALIDATION_ERROR',
         `"effort" must be one of: ${valid.join(', ')}`,
         { field: 'effort', scope });
     }
   }
   ```
3. Extend `resolveLLMParams` to resolve `effort` (mirror the `thinking` block
   at [line 116-124](utils/effective-config.js#L116)). Priority: per-call >
   session > agent. **Session-level**: not adding a new column — the user
   asked for agent-level defaults only. Skip session lookup; only agent /
   per-call.
4. Add `effort` to the returned object.

### Task C — pass `effort` to the SDK in `engines/claude-engine.js`

After [line 320](engines/claude-engine.js#L320) (`thinking: ...`):
```js
effort: resolvedParams.effort || undefined,
```

Place it inside the same `sdkOptions` object literal.

### Task D — set defaults on the three global cc-* agents

Update three files in `~/.veil/agents/`:

| Agent | New fields |
|---|---|
| `cc-opus/agent.json` | `"thinking": { "type": "adaptive" }`, `"effort": "xhigh"` |
| `cc-sonnet/agent.json` | `"thinking": { "type": "adaptive" }`, `"effort": "max"` |
| `cc-haiku/agent.json` | `"thinking": { "type": "adaptive" }`, `"effort": "max"` |

**Note on `thinking: adaptive`**: Without it, the SDK uses model defaults.
Setting it explicitly makes the user's intent clear and ensures `effort`
applies to a real thinking pass (per the SDK doc: "Works WITH adaptive
thinking to guide thinking depth").

Per the SDK doc, `max` on sonnet/haiku may be silently downgraded — the user
explicitly asked for "max whatever it is". We respect that. If the SDK rejects
at query time, the error surfaces clearly for the user to adjust.

### Out of scope

- Session-level effort override (would require a new column). Only agent /
  per-call. Can add later if needed.
- UI for setting effort. Can be added later — the API supports it via the chat
  request body's `effort` field once Task B lands.
- Migrating existing sessions — defaults take effect on next chat.

## Verification — real-world tests

1. **Schema accepts new fields:**
   - Manually JSON-validate the updated cc-* `agent.json` against the new
     schema.
   - Restart the server, query `GET /agents` — all 3 cc-* agents must list
     successfully (no validation error).

2. **Effort value reaches the SDK:**
   - Add a temporary `console.log('[DEBUG effort]', resolvedParams.effort)` in
     `claude-engine.js` near line 320.
   - Start a chat with cc-haiku → log shows `'max'`.
   - Start a chat with cc-opus → log shows `'xhigh'`.
   - Per-call override: `POST /agents/cc-haiku/chat` with body
     `{ message: '…', effort: 'low' }` → log shows `'low'`.
   - Remove debug log after verification.

3. **Per-call validator rejects invalid:**
   - `POST /agents/cc-haiku/chat` with `{ effort: 'extreme' }` → 400 with
     code `VALIDATION_ERROR`.

4. **No regression on openai sessions:**
   - Chat with the `assistant` agent (openai) — succeeds, ignores effort.

5. **End-to-end behavior smoke (qualitative):**
   - Send a non-trivial reasoning prompt to cc-opus configured with `xhigh`
     and confirm via the response or `/context` that thinking tokens were
     used. Same on cc-haiku with `max` (will likely use `high` — that's fine,
     just confirms no error).

## Criticizer
Run a criticizer specifically on:
- Whether `effort` field name in agent.json gets stripped by `loadAgent`'s spread
  (no — `{ ...config }` preserves all fields).
- Whether the validator's `VALIDATION_ERROR` code is surfaced to HTTP correctly.
  Trace `_err` and any callers.
- Whether setting `effort: 'max'` on sonnet/haiku where the SDK rejects it
  causes a hard failure (error surfaces to user) or silent skip.
- Whether existing sessions with old agents (no effort field) continue to work
  — they should, since `agent.effort` is `undefined` and the SDK option is
  conditional `effort: resolvedParams.effort || undefined`.
- Whether `KNOWN_OVERRIDE_FIELDS` is used elsewhere and adding to it has side
  effects.
- Whether the JSON schema's `additionalProperties: false` rejection is loud
  enough that users will notice if their agent.json is malformed.
