diff --git a/AGENT.md b/AGENT.md
new file mode 100644
index 0000000..f04c08e
--- /dev/null
+++ b/AGENT.md
@@ -0,0 +1,168 @@
+# Agent Instructions — Salesforce Appraiser Review Letter
+
+> Read this file at the start of every iteration. It is the canonical operating guide.
+
+---
+
+## Role
+
+You are a senior Salesforce developer working autonomously on the appraiser review letter project.
+You have full access to the codebase and can read, write, and run shell commands.
+
+---
+
+## Core Loop
+
+### 1. Orient
+
+- Read `PROJECT-SPEC.md` — requirements and acceptance criteria for Phase 2
+- Read `IMPLEMENTATION_PLAN.md` — task list and current status
+- Read `DECISIONS.md` — architecture decisions already in force
+- Run `git log --oneline -10` — see what has been done
+- Check for any prior escalations in `IMPLEMENTATION_PLAN.md`
+
+### 2. Pick ONE Task
+
+- Find the first unchecked task `- [ ]` in `IMPLEMENTATION_PLAN.md`
+- If all tasks are checked, output `DONE` and exit
+- Work only on this one task — do not pick up the next one
+
+### 3. Implement
+
+- Follow the existing code patterns in the project
+- Read the relevant existing classes/LWCs before writing new code
+- Study the CLM tracking pattern as the model for eSignature tracking
+
+### 4. Verify — BLOCKING before commit
+
+```bash
+# Set up the CLI path (system Node v12 is too old — always use nvm Node)
+NODE=/home/paulh/.nvm/versions/node/v22.22.0/bin/node
+SF=/home/paulh/.nvm/versions/node/v22.22.0/lib/node_modules/@salesforce/cli/bin/run.js
+
+# Deploy source to org
+$NODE $SF project deploy start \
+ --source-dir force-app \
+ --target-org appraiser-dev
+
+# Run Apex tests (synchronous, human-readable output)
+$NODE $SF apex run test \
+ --target-org appraiser-dev \
+ --class-names DocusignESignatureServiceTest,CLMAdminServiceTest,AppraiserCasePayloadBuilderTest,CLMDocGenCalloutTest \
+ --result-format human \
+ --synchronous
+```
+
+**Do not commit if deploy fails or tests fail.**
+
+For metadata-only tasks (adding custom fields, no Apex changes): deploy and confirm the object deploys cleanly, but you do not need to run all test classes — run a smoke test against the affected test class only.
+
+### 5. Commit and Mark Done
+
+Commit with this exact trailer format:
+
+```
+():
+
+
+
+Agent: claude-sonnet-4-6
+Tests: N/N passing | N/A (metadata-only)
+Tests-Added: +N | 0
+TypeScript: N/A (Apex project)
+```
+
+Then mark the task `- [x]` in `IMPLEMENTATION_PLAN.md` and commit that update in the same commit.
+
+### 6. Exit
+
+Output a brief summary (what you did, test results, what is next) and exit.
+The loop will restart you with fresh context for the next task.
+
+---
+
+## Rules
+
+1. **One task per iteration.** Never work on multiple tasks.
+2. **Read before writing.** Always read the existing class/LWC before modifying it.
+3. **Follow existing patterns.** The CLM tracking pattern is the model for eSignature tracking.
+4. **Tests are mandatory for every Apex change.** Add to the existing test classes — do not create new ones unless the spec says to.
+5. **Never add fields or methods not required by the spec.** Implement what is asked, nothing more.
+6. **Never modify production data.** All DML in tests uses `@isTest` and `Test.startTest()/stopTest()`.
+7. **Mock HTTP callouts in tests.** Use `HttpCalloutMock` — same pattern as existing test classes.
+8. **Deploy before committing.** Never commit code that does not deploy cleanly.
+9. **Follow DECISIONS.md.** Do not change approaches listed as Accepted without escalating.
+10. **If stuck, escalate.** Do not invent missing requirements.
+
+---
+
+## Escalation Protocol
+
+Stop and add this block at the top of `IMPLEMENTATION_PLAN.md`, then output `STUCK`:
+
+```markdown
+## ESCALATION REQUIRED
+- **Task:** [current task]
+- **Issue:** [what is ambiguous/missing/conflicting]
+- **What I need:** [specific question or decision]
+- **What I'd do if I had to guess:** [best guess]
+```
+
+Escalate when:
+- The spec has no FR covering a required decision
+- A constraint in DECISIONS.md conflicts with the spec
+- A Salesforce platform limit or API behavior is unclear
+- A named credential or metadata record is missing from the org
+- Any task requires deleting or overwriting existing custom fields already in use
+
+---
+
+## Output Signals
+
+- `PLANNED` — plan created, ready for build
+- `DONE` — all tasks complete
+- `STUCK` — needs human intervention
+- `ERROR` — unrecoverable error
+
+---
+
+## Project Structure
+
+```
+force-app/main/default/
+├── classes/
+│ ├── CLMAdminService.cls ← orchestration + persistDocGenResult pattern
+│ ├── CLMAdminServiceTest.cls ← test pattern to follow
+│ ├── CLMDocGenCallout.cls
+│ ├── CLMDocGenCalloutTest.cls
+│ ├── DocusignESignatureService.cls ← add createEnvelope() here
+│ ├── DocusignESignatureServiceTest.cls
+│ ├── AppraiserCasePayloadBuilder.cls
+│ └── AppraiserCasePayloadBuilderTest.cls
+├── lwc/
+│ ├── clmDocGenWorkbench/ ← add Send for Signature button here
+│ └── docusignEsignWorkbench/ ← read-only browsing (do not modify Phase 2)
+└── objects/
+ └── Appraiser_Case__c/
+ └── fields/ ← add 5 eSignature tracking fields here
+```
+
+---
+
+## SF CLI Invocation (Always Use This Pattern)
+
+```bash
+NODE=/home/paulh/.nvm/versions/node/v22.22.0/bin/node
+SF=/home/paulh/.nvm/versions/node/v22.22.0/lib/node_modules/@salesforce/cli/bin/run.js
+$NODE $SF
+```
+
+Target org alias: `appraiser-dev`
+
+---
+
+## Context Anchor
+
+- **Phase 1 is complete** — CLM doc generation works end-to-end. Do not modify it.
+- **Phase 2 goal** — after a CLM doc is generated and attached, enable sending it for eSignature using a DocuSign template (Option A).
+- **The authoritative plan** is in `NEXT_STEPS_DOCGEN.md` under Phase 2. `IMPLEMENTATION_PLAN.md` is the execution tracker derived from it.
diff --git a/DECISIONS.md b/DECISIONS.md
new file mode 100644
index 0000000..e349851
--- /dev/null
+++ b/DECISIONS.md
@@ -0,0 +1,122 @@
+# Architecture Decision Records
+
+> Non-obvious decisions that agents must not undo.
+> Read during Orient phase every iteration.
+
+---
+
+### ADR-001: Option A (template-based envelope) for createEnvelope()
+**Date:** 2026-04-09
+**Status:** Accepted
+
+**Context:**
+Two approaches were considered for creating a DocuSign eSignature envelope:
+- Option A: POST with a `templateId` — DocuSign template defines recipients/tabs, Salesforce only passes merge data at send time
+- Option B: POST with a raw document (download CLM blob, upload as base64) — Salesforce manages all recipient config
+
+**Decision:**
+Use Option A. Simpler implementation, no blob download, recipient config lives in the DocuSign template where it belongs.
+
+**Consequences:**
+✅ Fewer lines of Apex (no ContentVersion download, no base64 encoding)
+✅ Recipient/tab configuration lives in DocuSign where it is maintained
+❌ Requires a matching eSignature template to exist in the DocuSign account
+❌ If the template does not exist, the API returns a 400 — the operator must set up the template first
+
+**Alternatives Considered:**
+Option B was rejected as unnecessary complexity for this proof-of-concept phase.
+
+---
+
+### ADR-002: Named credentials for all DocuSign API calls
+**Date:** 2026-04-09
+**Status:** Accepted
+
+**Context:**
+All CLM and eSignature API calls must go through Salesforce named credentials. No hardcoded tokens, passwords, or base URLs in Apex.
+
+**Decision:**
+Named credential API name is always sourced from `CLM_Account_Setting__mdt`:
+- CLM calls use `CLM_API_Named_Credential__c`
+- eSignature calls use `ESignature_Rest_Named_Credential__c`
+
+The `eSignatureAccountId` is also from `CLM_Account_Setting__mdt.ESignature_Account_Id__c`.
+
+**Consequences:**
+✅ No secrets in source code
+✅ Works with Salesforce's credential management (OAuth token refresh, etc.)
+❌ Named credentials must be manually configured in each org before the code works
+
+---
+
+### ADR-003: persistEnvelopeResult follows persistDocGenResult pattern
+**Date:** 2026-04-09
+**Status:** Accepted
+
+**Context:**
+The project already has `CLMAdminService.persistDocGenResult()` which writes CLM task results back to the case. The eSignature result persistence should follow the same pattern — not a new approach.
+
+**Decision:**
+`CLMAdminService.persistEnvelopeResult()` is an `@AuraEnabled` static method that takes structured parameters (not a blob or generic map) and writes specific fields to the case via a SOQL update. Same structure as `persistDocGenResult`.
+
+**Consequences:**
+✅ Consistent code pattern, agent can learn from existing method
+✅ Testable with the same approach as `CLMAdminServiceTest`
+
+---
+
+### ADR-004: eSignature tracking fields belong on Appraiser_Case__c
+**Date:** 2026-04-09
+**Status:** Accepted
+
+**Context:**
+The case is the central record. CLM tracking fields already live on `Appraiser_Case__c`. The eSignature tracking fields follow the same pattern.
+
+**Decision:**
+All five eSignature lifecycle fields go on `Appraiser_Case__c`, not on a child record or a separate object.
+
+**Consequences:**
+✅ Single record for operator to view
+✅ Consistent with existing CLM field placement
+❌ Only one envelope tracked per case (sufficient for Phase 2)
+
+---
+
+### ADR-005: Operator manually enters template ID in Phase 2
+**Date:** 2026-04-09
+**Status:** Accepted
+
+**Context:**
+Option: auto-populate the template ID from `CLM_Letter_Definition__mdt` or present a template browser.
+Both require additional metadata fields or another API call and are out of scope for Phase 2.
+
+**Decision:**
+The operator inputs the eSignature template ID directly into a text field in `clmDocGenWorkbench`. No auto-selection, no browser, no additional metadata field.
+
+**Consequences:**
+✅ Simpler Phase 2 scope
+✅ No new metadata record changes needed
+❌ Operator must know the template ID — acceptable for a proof-of-concept
+
+**Review after:**
+Phase 3 could add a `ESignature_Template_Id__c` field to `CLM_Letter_Definition__mdt` and pre-populate the input.
+
+---
+
+### ADR-006: HTTP mocks required for all Apex callout tests
+**Date:** 2026-04-09
+**Status:** Accepted
+
+**Context:**
+Salesforce requires all HTTP callouts in test classes to use `Test.setMock(HttpCalloutMock.class, ...)`. The existing test classes all follow this pattern.
+
+**Decision:**
+All tests for `createEnvelope()` must mock the HTTP response using an inner `MockHttpResponse` class implementing `HttpCalloutMock`. Do not attempt live callouts in test methods.
+
+**Consequences:**
+✅ Tests run in any Salesforce org without external connectivity
+✅ Consistent with existing test class patterns (see `CLMDocGenCalloutTest`)
+
+---
+
+_Decisions don't drift if they're written down._
diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md
new file mode 100644
index 0000000..8238301
--- /dev/null
+++ b/IMPLEMENTATION_PLAN.md
@@ -0,0 +1,33 @@
+# Implementation Plan — Phase 2: eSignature Envelope Creation
+
+> This is the live execution tracker. The agent reads and updates this every iteration.
+> Check off tasks as they are completed (`- [x]`).
+> Add `ESCALATION REQUIRED` block at the top if stuck.
+
+---
+
+## Tasks
+
+- [ ] **Task 1 — Custom fields:** Add 5 eSignature tracking fields to `Appraiser_Case__c` object metadata and update `package.xml` if needed. Deploy to verify. (FR-001)
+
+- [ ] **Task 2 — EnvelopeCreateResult class + createEnvelope() method:** Add `EnvelopeCreateResult` inner class and `createEnvelope(Id caseId, String accountCode, String templateId)` to `DocusignESignatureService`. (FR-002)
+
+- [ ] **Task 3 — Tests for createEnvelope():** Add test coverage in `DocusignESignatureServiceTest` — success path (mock 201), failure path (mock 400), blank email guard. (NFR-001, TC-001, TC-002)
+
+- [ ] **Task 4 — persistEnvelopeResult() + CaseContext extension:** Add `persistEnvelopeResult()` to `CLMAdminService`. Extend `CaseContext` inner class and `getCaseContext()` SOQL to include the 5 eSignature fields. (FR-003, FR-004)
+
+- [ ] **Task 5 — Tests for persistEnvelopeResult():** Add test coverage in `CLMAdminServiceTest` — verify fields written correctly, `ESignature_Sent_At__c` is set, `ESignature_Completed_At__c` is NOT written on a "sent" result. (NFR-001, TC-003)
+
+- [ ] **Task 6 — clmDocGenWorkbench UI:** Add "eSignature Template ID" text input and "Send for Signature" button to `clmDocGenWorkbench`. Button calls `createEnvelope()` then `persistEnvelopeResult()`, shows result. (FR-005)
+
+- [ ] **Task 7 — Full deploy and test run:** Deploy entire change set, run all four test classes, verify all pass. Confirm no regressions. Update `docs/CURRENT_STATUS.md` to reflect Phase 2 completion.
+
+---
+
+## Completion
+
+When all tasks are checked, output `DONE`.
+
+---
+
+_Phase 2 plan created: 2026-04-09_
diff --git a/PROJECT-SPEC.md b/PROJECT-SPEC.md
new file mode 100644
index 0000000..cd7bed7
--- /dev/null
+++ b/PROJECT-SPEC.md
@@ -0,0 +1,327 @@
+# Project Specification — Phase 2: eSignature Envelope Creation
+
+---
+
+## 1. Project Overview
+
+### What are we building?
+
+Connecting the CLM document generation workflow to DocuSign eSignature. After a review letter is generated and attached to an `Appraiser_Case__c` record, the operator can click "Send for Signature" to create a DocuSign envelope (using a pre-built eSignature template), send it, and have the envelope ID, status, and sent timestamp written back to the case.
+
+### Why does it matter?
+
+Phase 1 produces the review letter. Phase 2 delivers it for signature. Without this, the operator must manually log into DocuSign to send the envelope — a workflow gap.
+
+### Success criteria
+
+- [ ] Five eSignature tracking fields exist on `Appraiser_Case__c` and deploy cleanly
+- [ ] `DocusignESignatureService.createEnvelope()` calls the eSignature REST API and returns a structured result
+- [ ] Envelope ID, status, and sent timestamp are persisted to the case after a successful send
+- [ ] `clmDocGenWorkbench` exposes a "Send for Signature" button that activates after attach succeeds
+- [ ] All Apex changes are covered by tests in the existing test classes; all tests pass in appraiser-dev
+
+---
+
+## 2. Technical Foundation
+
+### Tech stack
+
+- **Language:** Apex (Salesforce), LWC (JavaScript/HTML)
+- **Framework:** Salesforce DX / SFDX source format
+- **Test framework:** Apex Test (deployed and run via sf CLI)
+- **Package manager:** N/A — Salesforce metadata deployment
+- **Org alias:** `appraiser-dev`
+
+### Project structure
+
+```
+force-app/main/default/
+├── classes/
+│ ├── CLMAdminService.cls
+│ ├── CLMAdminServiceTest.cls
+│ ├── DocusignESignatureService.cls ← primary change target
+│ └── DocusignESignatureServiceTest.cls ← test target
+├── lwc/
+│ └── clmDocGenWorkbench/ ← UI change target
+└── objects/
+ └── Appraiser_Case__c/
+ └── fields/ ← 5 new fields
+```
+
+### Build and test commands
+
+```bash
+NODE=/home/paulh/.nvm/versions/node/v22.22.0/bin/node
+SF=/home/paulh/.nvm/versions/node/v22.22.0/lib/node_modules/@salesforce/cli/bin/run.js
+
+# Deploy
+$NODE $SF project deploy start --source-dir force-app --target-org appraiser-dev
+
+# Run tests
+$NODE $SF apex run test \
+ --target-org appraiser-dev \
+ --class-names DocusignESignatureServiceTest,CLMAdminServiceTest \
+ --result-format human \
+ --synchronous
+```
+
+### Coding standards
+
+- Follow existing Apex class structure — inner DTO classes, `@AuraEnabled` methods, `with sharing`
+- Follow CLM tracking pattern: service layer method → result object → persist to case in `CLMAdminService`
+- All HTTP callouts mocked in tests using `HttpCalloutMock` (see `CLMDocGenCalloutTest` for the pattern)
+- LWC: follow existing `clmDocGenWorkbench` patterns (wire adapters, async/await handlers, tracked properties)
+- Field API names use `__c` suffix; field-meta.xml follows Salesforce DX format
+
+---
+
+## 3. Requirements
+
+### FR-001: eSignature Tracking Fields on Appraiser_Case__c
+
+**Description:** Five custom fields to track the envelope lifecycle on the case record.
+
+**Fields:**
+- `ESignature_Envelope_Id__c` — Text(255), label "eSignature Envelope ID"
+- `ESignature_Envelope_Status__c` — Text(50), label "eSignature Envelope Status" (values: created / sent / delivered / completed / voided)
+- `ESignature_Sent_At__c` — DateTime, label "eSignature Sent At"
+- `ESignature_Completed_At__c` — DateTime, label "eSignature Completed At"
+- `ESignature_Envelope_Url__c` — URL(255), label "eSignature Envelope URL"
+
+**Acceptance criteria:**
+- [ ] All 5 field metadata XML files exist under `objects/Appraiser_Case__c/fields/`
+- [ ] Fields deploy cleanly to appraiser-dev with no errors
+- [ ] `package.xml` is updated to include the new fields (or already covers `CustomField` members)
+
+---
+
+### FR-002: createEnvelope() in DocusignESignatureService
+
+**Description:** An Apex method that creates a DocuSign eSignature envelope using Option A (template-based). The caller provides a case ID, account code, and template ID. The method resolves the named credential from the account setting, calls `POST /v2.1/accounts/{accountId}/envelopes` with the template ID and a signer role populated from the case's appraiser fields, and returns a structured result.
+
+**API call shape:**
+```json
+POST /v2.1/accounts/{accountId}/envelopes
+{
+ "templateId": "",
+ "templateRoles": [
+ {
+ "email": "",
+ "name": "",
+ "roleName": "Signer"
+ }
+ ],
+ "status": "sent"
+}
+```
+
+**Result class:** `EnvelopeCreateResult` — inner class on `DocusignESignatureService`:
+- `Boolean success`
+- `String envelopeId`
+- `String status`
+- `String envelopeUri`
+- `String errorMessage`
+
+**Method signature:**
+```apex
+@AuraEnabled(cacheable=false)
+public static EnvelopeCreateResult createEnvelope(
+ Id caseId,
+ String accountCode,
+ String templateId
+)
+```
+
+**Acceptance criteria:**
+- [ ] Method is `@AuraEnabled` and callable from LWC
+- [ ] Resolves `eSignatureAccountId` and `eSignatureRestNamedCredential` from `CLM_Account_Setting__mdt` via `accountCode`
+- [ ] Queries `Appraiser_Email__c`, `Appraiser_Name__c`, `Appraiser_Last_Name__c`, `Appraiser_Salutation__c` from the case
+- [ ] POSTs correct JSON body to the eSignature API
+- [ ] Returns `EnvelopeCreateResult` with `envelopeId` and `status` on success
+- [ ] Returns `EnvelopeCreateResult` with `success=false` and `errorMessage` on failure (non-2xx or exception)
+- [ ] Does NOT throw uncaught exceptions — catches and wraps in result
+
+---
+
+### FR-003: persistEnvelopeResult() in CLMAdminService
+
+**Description:** After a successful `createEnvelope()` call, write the result back to `Appraiser_Case__c`. Follows the same pattern as `persistDocGenResult`.
+
+**Method signature:**
+```apex
+@AuraEnabled(cacheable=false)
+public static void persistEnvelopeResult(
+ Id caseId,
+ String envelopeId,
+ String envelopeStatus,
+ String envelopeUri
+)
+```
+
+**Fields written:**
+- `ESignature_Envelope_Id__c` = envelopeId
+- `ESignature_Envelope_Status__c` = envelopeStatus
+- `ESignature_Sent_At__c` = Datetime.now()
+- `ESignature_Envelope_Url__c` = envelopeUri (if not blank)
+
+**Acceptance criteria:**
+- [ ] Method updates the correct case record (by `caseId`)
+- [ ] Sets `ESignature_Sent_At__c` to current datetime
+- [ ] Does not overwrite `ESignature_Completed_At__c` (only set when status = completed)
+
+---
+
+### FR-004: Extend CaseContext with Envelope Fields
+
+**Description:** The `CaseContext` inner class in `CLMAdminService` (and its `getCaseContext()` query) must include the 5 eSignature tracking fields so the LWC can display current envelope status.
+
+**Fields to add to CaseContext:**
+- `String eSignatureEnvelopeId`
+- `String eSignatureEnvelopeStatus`
+- `String eSignatureEnvelopeUrl`
+- `Datetime eSignatureSentAt`
+- `Datetime eSignatureCompletedAt`
+
+**Acceptance criteria:**
+- [ ] `getCaseContext()` SOQL query includes all 5 fields
+- [ ] `CaseContext` DTO maps all 5 fields with `@AuraEnabled`
+
+---
+
+### FR-005: "Send for Signature" Button in clmDocGenWorkbench
+
+**Description:** After the "Attach Generated Document" step succeeds, a "Send for Signature" button becomes enabled. Clicking it calls `createEnvelope()` and then `persistEnvelopeResult()`, and displays the result (envelope ID and status, or error message) in the workbench UI.
+
+**Behavior:**
+- Button is disabled until `attachedFileContentDocumentId` is populated (i.e., attach has succeeded)
+- Button shows a spinner while in progress (same pattern as generate/attach buttons)
+- On success: display envelope ID and status; update envelope fields displayed from `caseContext`
+- On error: display error message
+- Button does NOT automatically select a template — the operator must provide `templateId` via a text input field in the UI
+
+**New UI elements:**
+- Text input: "eSignature Template ID" (maps to `templateId` parameter)
+- Button: "Send for Signature"
+- Status display: envelope ID, status, sent timestamp (read from refreshed `caseContext`)
+
+**Acceptance criteria:**
+- [ ] Button is disabled when `attachedFileContentDocumentId` is blank
+- [ ] Button calls `DocusignESignatureService.createEnvelope()` with the case ID, account code, and template ID from the input
+- [ ] On success, calls `CLMAdminService.persistEnvelopeResult()` and refreshes the case context display
+- [ ] On failure, displays the error message from `EnvelopeCreateResult.errorMessage`
+- [ ] Template ID input is cleared after a successful send
+- [ ] Spinner shown during the callout; button disabled during in-flight request
+
+---
+
+### NFR-001: Test Coverage
+
+- [ ] `DocusignESignatureServiceTest` covers `createEnvelope()` — at minimum: success path (mock HTTP 201), failure path (mock HTTP 400), missing template ID guard
+- [ ] `CLMAdminServiceTest` covers `persistEnvelopeResult()` — at minimum: fields written correctly, `ESignature_Sent_At__c` is set
+- [ ] All existing tests continue to pass after each change
+
+### NFR-002: No Hardcoded Credentials
+
+- [ ] All DocuSign API calls go through named credentials (never hardcoded tokens or passwords)
+- [ ] Named credential API name is sourced from `CLM_Account_Setting__mdt`
+
+---
+
+## 4. Data Model
+
+### New fields on Appraiser_Case__c
+
+| API Name | Type | Length |
+|----------|------|--------|
+| ESignature_Envelope_Id__c | Text | 255 |
+| ESignature_Envelope_Status__c | Text | 50 |
+| ESignature_Sent_At__c | DateTime | — |
+| ESignature_Completed_At__c | DateTime | — |
+| ESignature_Envelope_Url__c | URL | 255 |
+
+### Existing fields read by createEnvelope()
+
+| API Name | Used for |
+|----------|----------|
+| Appraiser_Email__c | templateRoles[0].email |
+| Appraiser_Name__c | templateRoles[0].name (prefer this) |
+| Appraiser_Salutation__c | fallback for name |
+| Appraiser_Last_Name__c | fallback for name |
+
+---
+
+## 5. Architecture Decisions
+
+### Constraints
+
+- **MUST:** Use `CLM_Account_Setting__mdt` to resolve named credentials — never hardcode
+- **MUST:** Follow the `persistDocGenResult` pattern in `CLMAdminService` for `persistEnvelopeResult`
+- **MUST:** Mock all HTTP callouts in tests using `HttpCalloutMock`
+- **MUST NOT:** Modify the CLM document generation flow (Phase 1) — leave it untouched
+- **MUST NOT:** Create new Apex test classes — add to existing ones
+- **MUST NOT:** Add eSignature template browsing in this phase — the operator inputs the template ID directly
+- **PREFER:** Option A (template-based envelope) for `createEnvelope()` — simpler, already decided
+- **ESCALATE:** If the eSignature named credential does not exist in the org, stop and flag it
+- **ESCALATE:** If `Appraiser_Email__c` is blank on the queried case, stop — do not silently send with a blank email
+- **ESCALATE:** If the spec does not cover a required decision — do not fill gaps silently
+
+### Dependencies
+
+- `CLM_Account_Setting__mdt` — provides `ESignature_Account_Id__c`, `ESignature_Rest_Named_Credential__c`
+- DocuSign eSignature REST API v2.1 — `/v2.1/accounts/{accountId}/envelopes`
+- Named credential (AcctDemo_NamedCreds or equivalent) — must exist in appraiser-dev
+
+### Known Challenges
+
+- The named credential for eSignature REST may differ from the CLM named credential — check `CLM_Account_Setting__mdt` for `ESignature_Rest_Named_Credential__c`
+- DocuSign template roles: the `roleName` must match the role name defined in the eSignature template — "Signer" is the default, but the actual template may use a different name. If tests fail with a 400 from DocuSign about role name, escalate.
+- LWC must use `async/await` for imperative Apex calls (not wire adapters) for the send action — matches the existing attach pattern
+
+### Rejected Approaches
+
+| Rejected option | Why rejected |
+|-----------------|-------------|
+| Option B (upload CLM document as envelope document) | More complex, requires downloading the blob and managing recipients in code. Option A with a matching template is simpler and sufficient. |
+| Creating a new Apex test class for eSignature | Not needed — `DocusignESignatureServiceTest` already exists and is the right home |
+| Automatic template selection via API | Out of scope for Phase 2 — operator inputs template ID directly |
+
+---
+
+## 6. Reference Materials
+
+### Existing code to learn from
+
+- `CLMAdminService.persistDocGenResult()` — the exact pattern to replicate for `persistEnvelopeResult()`
+- `CLMAdminService.getCaseContext()` — how case fields are queried and mapped to a DTO
+- `CLMDocGenCalloutTest` — the HTTP mock pattern to follow in `DocusignESignatureServiceTest`
+- `clmDocGenWorkbench` — the LWC pattern (async handlers, spinner, button enable/disable) to follow for the send button
+
+### Anti-patterns
+
+- Do not use `HttpRequest.setBodyAsBlob()` for the JSON envelope payload — use `setBody()` with `JSON.serialize()`
+- Do not trust the `status` field in the LWC to determine if all prior steps completed — check the specific field (e.g., `attachedFileContentDocumentId`) that the button should gate on
+
+---
+
+## 7. Evaluation Design
+
+### Test cases
+
+#### TC-001: createEnvelope() success path
+**Input:** Valid caseId, valid accountCode, valid templateId; mock HTTP returns 201 with `{"envelopeId":"abc-123","status":"sent","uri":"/envelopes/abc-123"}`
+**Expected output:** `EnvelopeCreateResult.success = true`, `envelopeId = "abc-123"`, `status = "sent"`
+**Verification:** `System.assertEquals(true, result.success)` in `DocusignESignatureServiceTest`
+
+#### TC-002: createEnvelope() failure path
+**Input:** Valid caseId/accountCode/templateId; mock HTTP returns 400 with error body
+**Expected output:** `EnvelopeCreateResult.success = false`, `errorMessage` is non-blank
+**Verification:** `System.assertEquals(false, result.success)` and `System.assertNotEquals(null, result.errorMessage)`
+
+#### TC-003: persistEnvelopeResult() writes correct fields
+**Input:** Existing case record, envelopeId = "test-env-id", envelopeStatus = "sent", envelopeUri = "/envelopes/test-env-id"
+**Expected output:** Re-queried case has `ESignature_Envelope_Id__c = "test-env-id"`, `ESignature_Sent_At__c` is non-null
+**Verification:** Re-query case after call and assert in `CLMAdminServiceTest`
+
+---
+
+_Last updated: 2026-04-09_
diff --git a/ralph-loop.sh b/ralph-loop.sh
new file mode 100755
index 0000000..3ac1de7
--- /dev/null
+++ b/ralph-loop.sh
@@ -0,0 +1,198 @@
+#!/usr/bin/env bash
+#
+# Ralph Wiggum Loop — Autonomous agent iteration
+#
+# Based on Geoffrey Huntley's approach:
+# - Each iteration spawns a FRESH agent with clean context
+# - Agent reads the plan, picks ONE task, implements, tests, commits, exits
+# - Loop restarts until all tasks are done
+#
+# No context compaction. No stale reasoning. Just fresh starts.
+#
+# Usage:
+# ./ralph-loop.sh # Build mode (default)
+# ./ralph-loop.sh plan # Planning mode (create IMPLEMENTATION_PLAN.md)
+# ./ralph-loop.sh --max 20 # Limit to 20 iterations
+# ./ralph-loop.sh --agent claude # Use claude (default)
+# ./ralph-loop.sh --agent codex # Use OpenAI Codex CLI
+# ./ralph-loop.sh --agent aider # Use Aider
+# ./ralph-loop.sh --agent gemini # Use Gemini CLI
+# ./ralph-loop.sh --agent custom # Use custom agent (see below)
+#
+# Extensibility:
+# To add support for other AI coding agents (aider, cursor, windsurf, etc.):
+# 1. Add a new case in the run_agent() function's agent selection block
+# 2. Format the prompt appropriately for that agent's CLI interface
+# 3. Ensure the agent outputs to the logfile for promise detection
+#
+# Example for Aider:
+# aider)
+# aider --message "$prompt" --yes 2>&1 | tee "$logfile"
+# ;;
+#
+# Example for custom script:
+# custom)
+# ./my-agent-wrapper.sh "$prompt" 2>&1 | tee "$logfile"
+# ;;
+#
+set -euo pipefail
+
+MODE="${1:-build}"
+MAX_ITERATIONS=50
+AGENT="claude"
+PLAN_FILE="IMPLEMENTATION_PLAN.md"
+SPEC_FILE="PROJECT-SPEC.md"
+AGENT_FILE="AGENT.md"
+LOG_DIR=".ralph-logs"
+
+# Parse arguments
+shift 2>/dev/null || true
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --max) MAX_ITERATIONS="$2"; shift 2 ;;
+ --agent) AGENT="$2"; shift 2 ;;
+ *) echo "Unknown option: $1"; exit 1 ;;
+ esac
+done
+
+mkdir -p "$LOG_DIR"
+
+# Colors
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+log() { echo -e "${BLUE}[ralph]${NC} $1"; }
+success() { echo -e "${GREEN}[ralph]${NC} $1"; }
+warn() { echo -e "${YELLOW}[ralph]${NC} $1"; }
+error() { echo -e "${RED}[ralph]${NC} $1"; }
+
+# Check prerequisites
+if [[ ! -f "$SPEC_FILE" ]]; then
+ error "Missing $SPEC_FILE — create your project spec first."
+ exit 1
+fi
+
+if [[ ! -f "$AGENT_FILE" ]]; then
+ warn "No $AGENT_FILE found. Using default agent instructions."
+fi
+
+run_agent() {
+ local iteration=$1
+ local mode=$2
+ local logfile="$LOG_DIR/iteration-${iteration}.log"
+ local prompt=""
+
+ if [[ "$mode" == "plan" ]]; then
+ prompt="Read PROJECT-SPEC.md. Decompose the project into discrete, testable tasks ordered by dependency. Write the plan to IMPLEMENTATION_PLAN.md with checkboxes. Output PLANNED when done."
+ else
+ prompt="Read AGENT.md (if it exists) for your instructions. Follow the core loop: orient, pick one task, implement, verify, commit, exit."
+ fi
+
+ log "Iteration $iteration ($mode mode) — starting fresh agent..."
+
+ # Agent selection block
+ # Extend this case statement to support additional agents
+ case "$AGENT" in
+ claude)
+ echo "$prompt" | claude -p --output-format text 2>&1 | tee "$logfile"
+ ;;
+ codex)
+ echo "$prompt" | codex 2>&1 | tee "$logfile"
+ ;;
+ aider)
+ # Aider: AI pair programming in your terminal
+ # https://aider.chat
+ aider --message "$prompt" --yes 2>&1 | tee "$logfile"
+ ;;
+ gemini)
+ # Google Gemini CLI (if available)
+ # Adjust command based on actual Gemini CLI interface
+ echo "$prompt" | gemini-cli 2>&1 | tee "$logfile"
+ ;;
+ custom)
+ # Custom agent integration
+ # Replace this with your own agent wrapper script
+ # The script should:
+ # 1. Accept prompt as first argument or via stdin
+ # 2. Perform the requested work (read files, write code, run tests, commit)
+ # 3. Output promise signals: PLANNED|DONE|STUCK|ERROR
+ # 4. Exit with appropriate code
+ if [[ -x "./custom-agent.sh" ]]; then
+ ./custom-agent.sh "$prompt" 2>&1 | tee "$logfile"
+ else
+ error "Custom agent selected but ./custom-agent.sh not found or not executable"
+ exit 1
+ fi
+ ;;
+ *)
+ error "Unknown agent: $AGENT"
+ error "Supported agents: claude, codex, aider, gemini, custom"
+ error "To add support for other agents, edit the run_agent() function in this script"
+ exit 1
+ ;;
+ esac
+
+ return 0
+}
+
+check_output() {
+ local logfile="$1"
+
+ if grep -q 'DONE' "$logfile" 2>/dev/null; then
+ return 0 # Done
+ elif grep -q 'STUCK' "$logfile" 2>/dev/null; then
+ return 2 # Stuck
+ elif grep -q 'ERROR' "$logfile" 2>/dev/null; then
+ return 3 # Error
+ else
+ return 1 # Continue
+ fi
+}
+
+# Main loop
+if [[ "$MODE" == "plan" ]]; then
+ log "Planning mode — creating implementation plan..."
+ run_agent 0 plan
+ success "Plan created. Review $PLAN_FILE, then run: ./ralph-loop.sh"
+ exit 0
+fi
+
+log "Starting Ralph Wiggum loop (max $MAX_ITERATIONS iterations)"
+log "Agent: $AGENT"
+log "Spec: $SPEC_FILE"
+log "Plan: $PLAN_FILE"
+echo ""
+
+for i in $(seq 1 "$MAX_ITERATIONS"); do
+ run_agent "$i" build
+ logfile="$LOG_DIR/iteration-${i}.log"
+
+ check_output "$logfile"
+ status=$?
+
+ case $status in
+ 0)
+ success "🎉 ALL TASKS COMPLETE after $i iterations!"
+ exit 0
+ ;;
+ 2)
+ warn "Agent is stuck. Review $logfile and intervene."
+ exit 1
+ ;;
+ 3)
+ error "Agent encountered an error. Review $logfile."
+ exit 1
+ ;;
+ 1)
+ log "Iteration $i complete. Restarting with fresh context..."
+ echo ""
+ sleep 2
+ ;;
+ esac
+done
+
+warn "Reached max iterations ($MAX_ITERATIONS). Review progress in $PLAN_FILE."
+exit 1