Skill: vamsig7/goal-mode (version 1.0.0)

--- name: goal-mode description: Dynamic goal-aware browsing assistant that generates checklists, evaluates page relevance, produces session wrap-ups, and persists all session data to the workspace. allowed-tools: read write --- # Goal Mode Goal-aware browsing analysis engine. Receives structured JSON requests from a browser extension orchestrator and returns structured JSON responses. Supports any goal type — shopping, research, travel planning, learning, decision-making, career exploration, health, and more. ## CRITICAL RULE: Single raw JSON response Your **final text output** (after all tool calls complete) must be exactly one raw JSON object — no markdown fences, no prose before or after. Intermediate tool calls (`read`, `write`) are separate from this rule; they happen silently before the final output. ## Execution model For EVERY operation, follow these steps in order: 1. Validate `operation` and required `input` fields 2. `read` the operation's reference file (see routing table below) AND `read` `{baseDir}/references/schemas.md` for shared schemas 3. Compute the JSON result from input (keep it in memory, do NOT output it yet) 4. **Execute ALL workspace `write` (and `read` where needed) tool calls** listed in the Persist section of the reference file. Do not skip any. If a write fails, continue with the remaining writes. 5. **Only after all persistence writes are done**, output the JSON result as your final text response IMPORTANT: Steps 4 and 5 are mandatory for ALL operations. Every operation (`generate_criteria`, `evaluate_page`, `update_criteria`, `resume_goal`, `create_wrap_up`) MUST persist before returning. ## Operation routing After parsing the `operation` field, `read` the corresponding reference file: | Operation | Reference file | |-----------|---------------| | `generate_criteria` | `{baseDir}/references/generate-criteria.md` | | `evaluate_page` | `{baseDir}/references/evaluate-page.md` | | `update_criteria` | `{baseDir}/references/update-criteria.md` | | `create_wrap_up` | `{baseDir}/references/create-wrap-up.md` | | `resume_goal` | `{baseDir}/references/resume-goal.md` | Always also `read` `{baseDir}/references/schemas.md` for shared schemas (session.json, criteria.json, active-session.md, active-goal.json). ## Input format Every request is a JSON object with an `operation` field and an `input` field: ```json { "operation": "generate_criteria | evaluate_page | update_criteria | create_wrap_up | resume_goal", "input": { ... } } ``` ### Invalid input handling If `operation` is missing/unknown, or required fields are missing, return: ```json { "error": { "code": "invalid_input", "message": "Clear explanation of what field is missing or invalid." } } ``` ## Workspace persistence The workspace root is `/home/ubuntu/.openclaw/workspace`. Use **absolute paths** for all file operations. Do NOT use `exec` or `bash` for directory creation — the `write` tool automatically creates parent directories. ### File layout ``` /home/ubuntu/.openclaw/workspace/ goal-mode/ active-goal.json — Pointer to the current active goal {goal_slug}/ session.json — All session state: goal, criteria, pages, findings, candidates criteria.json — Criteria coverage snapshot (synced on every criteria change) wrap-up.json — Final session summary (written on finish) events/ — Immutable event log (one file per page evaluation) {timestamp}-evaluate-page.json (timestamp = ISO 8601 compact: YYYYMMDDTHHmmssZ) memory/goal-mode/ active-session.md — Live status of the current active goal (updated on every operation) latest-session.md — Human-readable summary of most recent finished session history.md — Append-only log of all past sessions ``` ### Goal slug Derived from the goal text: lowercase, replace spaces and special characters with hyphens, truncate to 60 characters, trim trailing hyphens. If a directory with that slug already exists and belongs to a **different** goal_text, append `-2`, `-3`, etc. ## Guardrails - ALWAYS return valid raw JSON as the text response. - Never wrap output in markdown code fences. - Never fabricate URLs or page content not present in the input. - Finding types must be dynamically chosen based on the goal context — do not default to shopping-oriented types for non-shopping goals. - **Criteria specificity:** criteria must be narrow enough that a single overview page cannot satisfy more than 2-3 of them. "Build quality" is too broad; "Build quality: hinge durability and chassis material" is the right level. - If the goal is ambiguous, generate criteria that help clarify it through browsing. - Recommendation in wrap-up must be `null` when evidence is insufficient — never force a recommendation. - Keep all text concise. Findings under 240 characters. Criteria under 120 characters. - `criteria_relevance` MUST contain an entry for every criterion in the input array. `criterion` values MUST be exact strings — do not rephrase them. A criterion is only `covered` when `best_relevance ≥ 0.7`. - **Per-page coverage cap:** a single page can flip at most 3 criteria from uncovered to covered. If more than 3 would cross the 0.7 threshold, only the top 3 by relevance are marked covered; the rest keep their updated `best_relevance` but stay uncovered until confirmed by another page. - **Depth-over-breadth:** score overview pages honestly. A listicle or roundup that mentions a topic in one paragraph scores 0.3–0.5, not 0.7+. Reserve 0.7+ for pages with dedicated, detailed coverage of that specific criterion. - All `confidence` values across all operations are floats between 0.0 and 1.0. Never use string enums like "high/medium/low" for confidence. - If workspace writes fail or `write` is unavailable, still return the JSON response. - If merge/append is not possible, prefer immutable event files over skipping persistence entirely. - Always use absolute paths starting with `/home/ubuntu/.openclaw/workspace/`. - Do NOT use `exec` for mkdir — `write` auto-creates parent directories.

path: references/create-wrap-up.md
content: # Operation: create_wrap_up

## Input

```json
{
  "operation": "create_wrap_up",
  "input": {
    "goal_text": "string",
    "goal_slug": "string",
    "constraints": ["array"],
    "pages": [
      { "url": "...", "title": "...", "relevance": "high", "confidence": 0.9, "reasoning": "..." }
    ],
    "findings": [
      { "type": "...", "text": "...", "source_url": "..." }
    ],
    "candidates": [{ "name": "...", "attributes": {}, "pros": [], "cons": [] }],
    "criteria": [
      { "text": "...", "covered": true, "best_relevance": 0.9, "sources": 3 },
      { "text": "...", "covered": false, "best_relevance": 0.4, "sources": 1 }
    ]
  }
}
```

## Behavior

Using ONLY the data in the input:

- Compute `progress` from the criteria array: `covered count / total count`
- Summarize what was learned in structured sections (see output shape)
- Make a recommendation if enough evidence exists (null if insufficient data)
- List trade-offs between identified options/findings
- Identify unresolved conflicts in the evidence
- Derive remaining uncertainties from uncovered criteria
- Suggest concrete final actions the user should take

## Output JSON shape

```json
{
  "progress": 0.75,
  "pages_evaluated": 5,
  "key_learnings": [
    "One-sentence learning referencing a specific finding or page"
  ],
  "gaps": ["Criterion X was not covered — no dedicated source found"],
  "verdict": "One-paragraph overall conclusion about the goal.",
  "recommendation": {
    "candidate": "recommended option/item name",
    "reasoning": "Why this is recommended.",
    "confidence": 0.85
  },
  "candidates_compared": [{ "...same structure as input candidates..." }],
  "trade_offs": ["trade-off 1", "trade-off 2"],
  "unresolved_conflicts": ["conflict 1"],
  "suggested_final_actions": ["action 1", "action 2"]
}
```

- `progress` is computed from input criteria: `covered count / total count`. Do NOT expect it in the input.
- `key_learnings` — each entry is a concrete takeaway referencing specific findings/pages.
- `gaps` — uncovered criteria and why they remain uncovered.
- `verdict` — short overall conclusion paragraph.
- `recommendation` is `null` when evidence is insufficient. `recommendation.confidence` is a float 0.0–1.0.

## Persist

Always write ALL five files:

1. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/{goal_slug}/wrap-up.json` — the full wrap-up JSON you generated
2. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/active-goal.json` — update status to `"finished"`, set `finished_at` timestamp
3. `write` to `/home/ubuntu/.openclaw/workspace/memory/goal-mode/latest-session.md` — human-readable markdown summary: goal, constraints, pages evaluated count, key findings, recommendation, remaining uncertainties
4. `read` then `write` to `/home/ubuntu/.openclaw/workspace/memory/goal-mode/history.md` — append one line: `YYYY-MM-DD | {goal_text} | {constraints summary} | {status} | {N} pages evaluated | {recommendation or "no recommendation"}`. If file doesn't exist, create it.
5. `write` to `/home/ubuntu/.openclaw/workspace/memory/goal-mode/active-session.md` — write `No active goal.` to indicate the session is done


path: references/generate-criteria.md
content: # Operation: generate_criteria

## Input

```json
{
  "operation": "generate_criteria",
  "input": {
    "goal_text": "string — the user's goal in their own words",
    "constraints": ["array of constraint strings"]
  }
}
```

## Behavior

Analyze the goal to determine its category and generate **6-10** checklist criteria. Each criterion is a **specific noun phrase** describing a narrow topic or dimension to evaluate — NOT a question, NOT a sentence, NOT a "Does this page..." filter.

### The Specificity Rule (most important)

Criteria must be **narrow enough that a single overview page cannot satisfy more than 2-3 of them**. This is the single most important quality metric.

Before finalizing, mentally test: "Could one broad overview page on this topic score ≥ 0.7 on more than 3 of these criteria?" If yes, your criteria are too broad — decompose them.

**Decomposition strategy for broad goals:**
- Split by **sub-topic** (not just facet): e.g., "best headphones" → specific use cases, driver types, or price tiers narrow enough that a "top 10 headphones" listicle only skims them
- Split by **depth requirement**: prefer criteria that require specialized sources (detailed reviews, comparison tests, expert guides) rather than generic overviews
- Split by **actionability**: at least 2 criteria should require information that helps the user **do something** (make a decision, take an action, compare options)

### Rules for criteria

- Each criterion is a specific noun phrase (3-8 words)
- Narrow enough to require a **dedicated page or section** to satisfy — not just a passing mention in an overview
- Describes WHAT to evaluate, not HOW to evaluate it
- Specific to the goal (not generic)
- Measurable or verifiable from web pages
- Ordered by importance
- **Every constraint MUST produce at least one criterion.** If the user says "budget under $1000", there must be a criterion about pricing within that budget. If the user says "vegetarian options only", there must be a criterion targeting vegetarian-specific information. Do not silently ignore constraints.
- At least 2 criteria should target information requiring **specialized sources** (not found in a general overview)

### CORRECT format examples (diverse goal types)

- Goal "buy a laptop under $1000":
  `["CPU and GPU benchmark comparisons under $1000", "Battery life test results (hours)", "Build quality: hinge durability and chassis material", "Display color accuracy and brightness (nits)", "SSD speed and upgrade options", "Thermal throttling under sustained load", "Warranty terms and repair track record"]`
- Goal "find best noise-cancelling headphones":
  `["ANC effectiveness in dB reduction tests", "Sound signature: bass vs neutral profile", "Comfort for 4+ hour wear sessions", "Multipoint Bluetooth pairing support", "Battery life and fast-charge specs", "Microphone quality for calls", "Durability and hinge/folding mechanism"]`
- Goal "plan a trip to Japan in April":
  `["Cherry blossom forecast and peak viewing spots", "JR Pass vs IC card cost comparison", "Ryokan vs hotel pricing in Kyoto", "Visa-free entry requirements and duration", "Tokyo to Kyoto transit options and times", "Daily food budget breakdown", "Must-book-ahead attractions and reservation tips", "Pocket Wi-Fi vs eSIM providers and pricing"]`
- Goal "switch to data science career":
  `["Python vs R for entry-level roles", "Portfolio projects that hiring managers value", "Bootcamp vs self-study completion rates", "Entry-level salary by city and industry", "SQL and cloud skills gap analysis", "Interview process at FAANG vs startups", "Transition timeline from non-technical background"]`
- Goal "improve home espresso setup":
  `["Grinder burr types: flat vs conical performance", "Pressure profiling on sub-$500 machines", "Water mineral content and filtration", "Milk steaming wand power and technique", "Single-dose workflow and retention", "Puck prep: WDT and tamping consistency"]`

### WRONG — never generate criteria like these

- "Does this page discuss coffee varieties?" (question format)
- "The page should contain information about..." (sentence format)
- "Check if the page mentions..." (instruction format)
- "Comprehensive overview of the entire topic" (too vague, too long)
- "Performance benchmarks" (too vague — benchmarks for what? under what conditions?)
- "Price and value" (too broad — every product page touches this)
- "Build quality" (too broad — need to specify what aspect: materials, durability tests, etc.)
- "History and background" (too broad — any overview page covers this trivially)

## Output JSON shape

`criteria` must be a flat JSON array of plain strings. No nested objects, no keywords/categories/subcriteria.

```json
{
  "criteria": ["Specific noun phrase 1", "Specific noun phrase 2", "..."]
}
```

## Persist

Always write ALL four files:

1. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/active-goal.json` — the active-goal pointer JSON (goal_slug, goal_text, constraints, status: "active", started_at, updated_at)
2. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/{goal_slug}/session.json` — full session object with all arrays seeded empty:
   ```json
   { "schema_version": 2, "goal_slug": "...", "goal_text": "...", "constraints": [...], "status": "active", "started_at": "...", "updated_at": "...", "criteria": [{ "text": "...", "covered": false, "best_relevance": 0.0, "sources": 0 }], "pages": [], "findings": [], "candidates": [] }
   ```
3. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/{goal_slug}/criteria.json` — see criteria.json schema in references/schemas.md
4. `write` to `/home/ubuntu/.openclaw/workspace/memory/goal-mode/active-session.md` — see active-session.md format in references/schemas.md


path: references/resume-goal.md
content: # Operation: resume_goal

## Input

```json
{
  "operation": "resume_goal",
  "input": {
    "goal_slug": "string — slug of the goal to resume (optional, defaults to active goal)"
  }
}
```

## Behavior

1. If `goal_slug` is provided, read `/home/ubuntu/.openclaw/workspace/goal-mode/{goal_slug}/session.json`. If not provided, read `active-goal.json` to get the current slug, then read its session.
2. If the session has `status: "finished"`, return an error: `{ "error": { "code": "goal_finished", "message": "This goal is already finished. Start a new goal with generate_criteria." } }`
3. Return the full session state so the caller can continue from where it left off.

## Output JSON shape

```json
{
  "goal_slug": "...",
  "goal_text": "...",
  "constraints": [],
  "status": "active",
  "criteria": [
    { "text": "...", "covered": true, "best_relevance": 0.9, "sources": 2 },
    { "text": "...", "covered": false, "best_relevance": 0.4, "sources": 1 }
  ],
  "pages_evaluated": 5,
  "pages": [
    { "url": "...", "title": "...", "relevance": "high", "confidence": 0.9, "reasoning": "...", "evaluated_at": "..." }
  ],
  "findings_count": 12,
  "candidates": [
    { "name": "...", "attributes": {}, "pros": [], "cons": [] }
  ],
  "progress": 0.6
}
```

`progress` is calculated as: number of covered criteria / total criteria.

## Persist

1. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/active-goal.json` — update to point to this goal slug with `status: "active"` and fresh `updated_at`
2. `write` to `/home/ubuntu/.openclaw/workspace/memory/goal-mode/active-session.md` — regenerate from the session state (see references/schemas.md)


path: references/schemas.md
content: # Shared Schemas

## session.json

The single accumulator for all session state. Seeded by `generate_criteria`, updated by `evaluate_page` and `update_criteria`, finalized by `create_wrap_up`.

```json
{
  "schema_version": 2,
  "goal_slug": "...",
  "goal_text": "...",
  "constraints": [],
  "status": "active",
  "started_at": "...",
  "updated_at": "...",
  "criteria": [
    { "text": "CPU and GPU benchmark comparisons under $1000", "covered": true, "best_relevance": 0.9, "sources": 2 },
    { "text": "Battery life test results (hours)", "covered": false, "best_relevance": 0.4, "sources": 1 }
  ],
  "pages": [
    { "url": "...", "title": "...", "relevance": "high", "confidence": 0.9, "reasoning": "...", "evaluated_at": "..." }
  ],
  "findings": [
    { "type": "spec", "text": "...", "source_url": "..." }
  ],
  "candidates": [
    { "name": "...", "attributes": {}, "pros": [], "cons": [] }
  ]
}
```

No separate `pages.json`, `findings.json`, or `candidates.json` files.

## criteria.json

Snapshot of the criteria array, kept in sync by every operation that modifies criteria. Same shape as `session.json.criteria` wrapped in metadata:

```json
{
  "schema_version": 2,
  "criteria": [
    { "text": "...", "covered": false, "best_relevance": 0.0, "sources": 0 }
  ],
  "created_at": "2026-02-19T10:00:00Z",
  "updated_at": "2026-02-19T10:00:00Z"
}
```

### Criteria coverage rules

- On `generate_criteria`: set `covered: false`, `best_relevance: 0.0`, `sources: 0`.
- On `evaluate_page`: update `best_relevance` to `max(existing, new relevance)`. Increment `sources` if page scored ≥ 0.3 for this criterion. Set `covered: true` when `best_relevance >= 0.7`, subject to the per-page coverage cap (max 3 newly covered per page).
- On `update_criteria`: removed criteria lose data; replaced/added criteria start fresh.

## active-session.md

Overwritten on every operation to reflect live state. On `create_wrap_up`, write once with `status: finished`, then overwrite with `No active goal.`.

Path: `/home/ubuntu/.openclaw/workspace/memory/goal-mode/active-session.md`

```markdown
# Active Goal

**Goal:** {goal_text}
**Constraints:** {constraints joined with ", "}
**Status:** {active | finished}
**Progress:** {covered criteria count}/{total criteria count} criteria covered
**Pages evaluated:** {count}
**Started:** {YYYY-MM-DD}
**Updated:** {YYYY-MM-DD HH:MM UTC}

## Criteria

- [x] Covered criterion 1 (best: 0.9, 3 sources)
- [ ] Uncovered criterion 2 (best: 0.4, 1 source)
- [x] Covered criterion 3 (best: 0.7, 2 sources)

(show the highest relevance score and source count for each criterion)

## Recent Findings

- [{type}] {finding text} (from {source_url domain})
- [{type}] {finding text} (from {source_url domain})
(show last 5 findings max)

## Candidates

- **{name}**: {one-line summary of attributes}
(omit section if no candidates)
```

## active-goal.json

Cross-client discovery file:

```json
{
  "goal_slug": "buy-a-laptop-under-1000",
  "goal_text": "buy a laptop under $1000",
  "constraints": ["budget under $1000"],
  "status": "active",
  "started_at": "2026-02-19T10:00:00Z",
  "updated_at": "2026-02-19T12:30:00Z"
}
```


path: references/update-criteria.md
content: # Operation: update_criteria

## Input

```json
{
  "operation": "update_criteria",
  "input": {
    "goal_slug": "string",
    "add": ["new criterion string", "another new criterion"],
    "remove": ["exact text of criterion to remove"],
    "replace": [
      { "old": "exact text of existing criterion", "new": "replacement criterion text" }
    ]
  }
}
```

All three fields (`add`, `remove`, `replace`) are optional — include only what's needed. At least one must be present.

## Behavior

1. `read` the current `session.json` for the given `goal_slug`.
2. If the session has `status: "finished"`, return an error.
3. Apply changes to the criteria array in order: `remove` first, then `replace`, then `add`.
   - Removed criteria lose their coverage data permanently.
   - Replaced criteria keep `covered: false`, `best_relevance: 0.0`, `sources: 0` (fresh start for the new text).
   - Added criteria start with `covered: false`, `best_relevance: 0.0`, `sources: 0`.
4. Validate the result still has 4-10 criteria. If not, return an error.
5. Return the updated criteria array and progress.

## Output JSON shape

```json
{
  "criteria": [
    { "text": "...", "covered": true, "best_relevance": 0.9, "sources": 2 },
    { "text": "...", "covered": false, "best_relevance": 0.0, "sources": 0 }
  ],
  "progress": 0.5,
  "changes": {
    "added": 1,
    "removed": 1,
    "replaced": 0
  }
}
```

## Persist

1. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/{goal_slug}/session.json` — updated session with new criteria array and fresh `updated_at`
2. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/{goal_slug}/criteria.json` — updated criteria snapshot
3. `write` to `/home/ubuntu/.openclaw/workspace/memory/goal-mode/active-session.md` — regenerate from updated session state (see references/schemas.md)


path: references/evaluate-page.md
content: # Operation: evaluate_page

## Input

```json
{
  "operation": "evaluate_page",
  "input": {
    "goal_text": "string",
    "goal_slug": "string",
    "constraints": ["array"],
    "criteria": ["array of checklist criterion strings to check coverage against"],
    "page": {
      "url": "string",
      "title": "string",
      "extracted_text": "string — readable content from the page"
    }
  }
}
```

## Behavior

Using ONLY the data in the input.

**Large page handling:** If `extracted_text` is very long (>10,000 characters), focus your analysis on the most relevant sections rather than trying to extract every detail. Prioritize findings that directly address uncovered criteria. Cap findings at **10 per page** and keep each under 240 characters.

0. **Duplicate check:** Before evaluating, `read` `session.json` and check whether `page.url` already exists in `session.pages[]`. If it does, skip evaluation and return:
   ```json
   { "skipped": true, "reason": "duplicate", "url": "...", "previous_relevance": "high", "previous_evaluated_at": "..." }
   ```
   No persistence writes needed for duplicates.

1. Determine the page's relevance to the goal:
   - **high**: The page directly addresses the goal. Contains specific, actionable information (prices, specs, reviews, how-to steps, detailed analysis, comparisons, etc.). Most pages a user deliberately visits while researching should be `high`.
   - **medium**: Related but tangential — mentions the topic but lacks specific or actionable detail (general news article, category index page).
   - **low**: Unrelated to the goal, or content is mostly navigation/boilerplate.

   When in doubt between two levels, choose the HIGHER one.

2. Assign a confidence score between 0.0 and 1.0

3. Provide a one-sentence reasoning for the relevance classification

4. Extract structured findings with dynamically chosen types appropriate to the goal. Finding types are NOT fixed — choose types that make sense:
   - For purchasing goals: `price`, `spec`, `pro`, `con`, `review`, `comparison`
   - For research goals: `definition`, `evidence`, `methodology`, `source`, `counterpoint`
   - For travel goals: `cost`, `tip`, `warning`, `requirement`, `recommendation`
   - For any goal: `insight`, `fact`, `caveat`, `example`, `reference`, `other`

5. Identify key items (candidates/options/resources) if the page presents specific options, resources, or entities. Use **consistent attribute keys** within a session so candidates can be compared:
   - For products: `price`, `rating`, `key_spec` (the most differentiating spec)
   - For places/destinations: `cost_per_day`, `best_season`, `highlight`
   - For services/tools: `pricing_tier`, `free_plan`, `key_feature`
   - For careers/roles: `salary_range`, `required_experience`, `growth_outlook`
   - Pick 3-5 attribute keys that make sense for the goal and reuse them across all candidates.

6. Score each criterion individually. For EVERY criterion in the `criteria` array, assign a `relevance` score from 0.0 to 1.0:
   - **0.0**: Not mentioned at all
   - **0.1–0.2**: Named or listed without explanation (e.g., appears in a timeline or bullet list)
   - **0.3–0.4**: Briefly mentioned in passing (a sentence or two, no real detail)
   - **0.5–0.6**: Partially covered (a paragraph or section with some useful info, but not the page's focus)
   - **0.7–0.8**: Substantially covered (a dedicated section with multiple extractable findings)
   - **0.9–1.0**: The page is primarily about this criterion, with deep detail

   A criterion is only considered **covered** if its `best_relevance` (across all pages) reaches **≥ 0.7**.

   **Depth-over-breadth rule:** Overview pages ("top 10 X" listicles, "complete guide to Y", general encyclopedia entries) typically skim many topics at 0.2–0.5 depth. Score these honestly — do not inflate scores just because the topic is mentioned. Example: a "best laptops 2026" roundup that lists battery life as "10 hours" in a spec table scores 0.3 for "Battery life test results (hours)" — it lacks actual test methodology. A dedicated battery benchmark article scores 0.8+.

   **Per-page coverage cap:** A single page may mark at most **3 criteria** as newly covered (transitioning from `best_relevance < 0.7` to `best_relevance ≥ 0.7`). If more than 3 would cross the threshold, only the top 3 by relevance score are marked covered; the others retain their updated `best_relevance` but `covered` stays `false` until confirmed by another source.

## Output JSON shape

```json
{
  "relevance": "high | medium | low",
  "confidence": 0.85,
  "reasoning": "One sentence explaining the classification.",
  "findings": [
    { "type": "dynamically chosen type", "text": "extracted finding text" }
  ],
  "candidates": [
    {
      "name": "entity name",
      "attributes": { "key": "value pairs relevant to this goal type" },
      "pros": ["strength 1"],
      "cons": ["weakness 1"]
    }
  ],
  "criteria_relevance": [
    { "criterion": "exact criterion text", "relevance": 0.9 },
    { "criterion": "exact criterion text", "relevance": 0.2 },
    { "criterion": "exact criterion text", "relevance": 0.0 }
  ]
}
```

`criteria_relevance` MUST include an entry for EVERY criterion in the input array, even those with 0.0.

## Persist

Always persist — even for `low` relevance pages (complete browsing history).

Write exactly **4 files** per page evaluation:

1. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/{goal_slug}/events/{timestamp}-evaluate-page.json` — the full evaluation result plus `{ url, title }` (timestamp = ISO 8601 compact: `YYYYMMDDTHHmmssZ`)
2. `read` then `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/{goal_slug}/session.json` — merge this page's data:
   - Append to `session.pages[]`: `{ url, title, relevance, confidence, reasoning, evaluated_at }`
   - **For high/medium only:** append to `session.findings[]` (include `source_url` on each) — skip any finding whose `text` is semantically identical to an existing finding. Upsert into `session.candidates[]` by name. Update criteria coverage per the rules in references/schemas.md.
   - **For low relevance:** only append to `session.pages[]` — skip findings, candidates, and criteria updates
   - Update `session.updated_at`
   - If `read` fails (file doesn't exist), start with the initial session structure from `generate_criteria` and add the new data.
3. `write` to `/home/ubuntu/.openclaw/workspace/goal-mode/{goal_slug}/criteria.json` — overwrite with current criteria array from session.json
4. `write` to `/home/ubuntu/.openclaw/workspace/memory/goal-mode/active-session.md` — regenerate from updated session state (see references/schemas.md)


Security scan verdicts — ClawScan: clean | static analysis:  | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)