Scrask decision flow

What happens between "user sends a screenshot" and "item saved in Calendar / Task list." Click any node in either diagram, or any row in the threshold table, for a detail popup.

There are two sides. The parser side (scripts/scrask_bot.py) is stateless: it ingests a screenshot, decides what to extract, and emits a JSON intent with clarification questions baked in. The bot side (the OpenClaw agent running on Telegram / iMessage / Slack) is the conversation loop: it renders the parser's output, asks the clarification questions, and dispatches each item to the user's installed destination skill.

Thresholds at a glance

Click any row to see what the threshold controls and where it lives in the code.

ThresholdDefaultWhat it gates
FALLBACK_THRESHOLD0.60 Worst per-field score below this → Claude reruns the parse.
FALLBACK_IMPROVEMENT_MIN0.05 Claude's result is only kept if its avg confidence beats Gemini by this much.
ACTIONABLE_THRESHOLD0.70 Top-level actionable_confidence below this → "Is this actually an event/task?"
TYPE_THRESHOLD0.70 Per-item type_confidence below this → "Calendar or task list?"
FIELD_THRESHOLD0.70 Per mandatory field: null or below this → targeted field clarification.
DEFAULT_CONFIDENCE_THRESHOLD0.75 Legacy per-item gate. Only kicks in for items with no confidences{} block.

1. Parser side — what scrask_bot.py does

Click any decision diamond or step to see the relevant constant, function, and threshold.

flowchart TD
    A["User sends screenshot"] --> B["OpenClaw agent activates scrask-bot"]
    B --> C["scrask_bot.py runs"]

    C --> D{"Provider mode"}
    D -->|openclaw| OC["OpenClaw configured
vision LLM"] D -->|claude| E["Claude Opus parses"] D -->|gemini| F["Gemini 2.0 Flash parses"] D -->|auto| AR{"Which keys are set?"} AR -->|GEMINI_API_KEY| G["Gemini 2.0 Flash parses"] AR -->|"ANTHROPIC_API_KEY only"| E AR -->|Neither| OC G --> H{"Worst per-field
score < 0.60?"} H -->|No| K["Use Gemini result"] H -->|"Yes + ANTHROPIC_API_KEY"| I["Claude Opus reruns"] H -->|"Yes, no Claude key"| K I --> J{"Claude avg >
Gemini avg + 0.05?"} J -->|Yes| L["Use Claude result"] J -->|No| K E --> M["Raw parse_data"] F --> M K --> M L --> M OC --> M M --> N{"no_actionable_content
OR items empty?"} N -->|Yes| O["Return: couldn't find anything"] N -->|No| P["For each raw item:
shape_intent"] P --> Q["Build clarifications"] Q --> R{"type_confidence
< 0.70?"} R -->|Yes| S["Prepend type clarification:
'Calendar or task list?'"] R -->|No| T["Skip type clarification"] S --> U["Walk mandatory fields"] T --> U U --> V["For each mandatory field:
event/reminder = title, date, time
task = title only"] V --> W{"Value is
null/empty?"} W -->|Yes| X["Append clarification
reason: missing"] W -->|No| Y{"Per-field conf
< 0.70?"} Y -->|Yes| Z["Append clarification
reason: low_confidence"] Y -->|No| AA["Field OK"] X --> AB{"More fields?"} Z --> AB AA --> AB AB -->|Yes| V AB -->|No| AC["needs_confirmation =
len(clarifications) > 0"] AC --> AD{"actionable_confidence
< 0.70?"} AD -->|Yes| AE["needs_actionable_confirmation = true"] AD -->|No| AF["Render summary_text"] AE --> AF AF --> AG["Return JSON to bot"] click H call showInfo("FALLBACK_THRESHOLD") click J call showInfo("FALLBACK_IMPROVEMENT_MIN") click N call showInfo("no_actionable_content") click R call showInfo("TYPE_THRESHOLD") click W call showInfo("missing_field") click Y call showInfo("FIELD_THRESHOLD") click AC call showInfo("needs_confirmation") click AD call showInfo("ACTIONABLE_THRESHOLD") click P call showInfo("shape_intent") click I call showInfo("claude_fallback") click G call showInfo("gemini_parse") click E call showInfo("claude_parse") click F call showInfo("gemini_parse") click O call showInfo("terminal_no_actionable") click AG call showInfo("terminal_return")

2. Bot side — what the OpenClaw agent does with the parser output

Click any node to see how the bot is expected to behave at that step.

flowchart TD
    A["Bot receives parser JSON"] --> B{"no_actionable_content?"}
    B -->|Yes| C["Reply: couldn't find any event or task
End"] B -->|No| D{"needs_actionable_confirmation?"} D -->|Yes| E["Send: Is this actually
an event or task?"] E --> F{"User reply"} F -->|"no / skip"| G["Reply: Got it, skipped
End"] F -->|yes| H["Process each item"] D -->|No| H H --> I{"Item
needs_confirmation?"} I -->|No| J["Route silently to
destination skill"] J --> K{"destination"} K -->|calendar| L["calctl / accli /
apple-calendar /
brainz-calendar / gcal-pro"] K -->|task| M["apple-reminders /
things-mac / notion"] L --> N["Reply: Added to Calendar..."] M --> O["Reply: Added to Tasks..."] I -->|Yes| P["Walk clarifications
in order"] P --> Q{"clarification.field"} Q -->|type| R["Ask: Calendar or
task list?"] R --> S["Update item.destination"] Q -->|"date / time /
location / title / etc."| T["Ask: What time is X?
(question from clarifications)"] T --> U["Patch item field with reply"] S --> V{"More clarifications?"} U --> V V -->|Yes| P V -->|No| W{"Any reply
was 'skip'?"} W -->|Yes| X["Reply: Got it, skipped"] W -->|No| J H --> Y{"More items?"} Y -->|Yes| I Y -->|No| Z["Done"] N --> Y O --> Y X --> Y click B call showInfo("no_actionable_content") click D call showInfo("needs_actionable_confirmation_bot") click E call showInfo("actionable_gate_prompt") click I call showInfo("needs_confirmation") click J call showInfo("silent_dispatch") click K call showInfo("destination") click L call showInfo("calendar_skills") click M call showInfo("task_skills") click P call showInfo("clarifications_walk") click Q call showInfo("clarification_field") click R call showInfo("type_clarification") click S call showInfo("destination_swap") click T call showInfo("field_clarification")

3. End-to-end paths, narrated

Happy path — clean meeting invite, zero clarifications

  1. User sends screenshot. Bot acks: "Got it, analyzing…"
  2. scrask_bot.py runs in auto mode. Gemini parses. Worst per-field score is 0.92. No Claude fallback.
  3. actionable_confidence = 0.96. Above threshold, so no actionable gate.
  4. shape_intent walks mandatory fields for type: event — title, date, time all present, all above 0.85. No clarifications. needs_confirmation: false.
  5. Bot reads needs_confirmation: false, routes to calctl. Skill creates the event.
  6. Bot replies: "Added to Calendar: Team Standup — 2026-03-01 at 09:00"

Ambiguous path — vague WhatsApp "lets meet fri"

  1. User sends screenshot. Bot acks.
  2. Gemini parses. Time confidence is 0.0 (no time visible), date confidence is 0.60. Worst per-field score is 0.0, below 0.60 threshold → Claude reruns.
  3. Claude's avg is 0.65, Gemini's was 0.55. Improvement 0.10 ≥ 0.05 → Claude result kept.
  4. actionable_confidence = 0.85. No actionable gate.
  5. shape_intent walks mandatory fields for type: event:
    • title OK (above threshold)
    • date below threshold → clarification reason: low_confidence
    • time is null → clarification reason: missing
    • needs_confirmation: true
  6. Bot reads clarifications[] and asks: "I need to confirm: What date is meet on Friday? What time is meet on Friday?"
  7. User replies. Bot patches the fields, routes to calctl, confirms "Added to Calendar: …"

Actionable-gate path — flyer that might just be content

  1. User sends screenshot. Bot acks.
  2. Parser runs. actionable_confidence = 0.55. Below 0.70 → needs_actionable_confirmation: true.
  3. Bot leads with: "Is this actually an event or task? (55% sure) Reply yes to continue, or no to skip."
  4. User: "no" → "Got it, skipped". Done.
  5. User: "yes" → continue into the per-item loop (steps 5 onward from the ambiguous narrative above).