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.
| Threshold | Default | What it gates |
|---|---|---|
FALLBACK_THRESHOLD | 0.60 | Worst per-field score below this → Claude reruns the parse. |
FALLBACK_IMPROVEMENT_MIN | 0.05 | Claude's result is only kept if its avg confidence beats Gemini by this much. |
ACTIONABLE_THRESHOLD | 0.70 | Top-level actionable_confidence below this → "Is this actually an event/task?" |
TYPE_THRESHOLD | 0.70 | Per-item type_confidence below this → "Calendar or task list?" |
FIELD_THRESHOLD | 0.70 | Per mandatory field: null or below this → targeted field clarification. |
DEFAULT_CONFIDENCE_THRESHOLD | 0.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
- User sends screenshot. Bot acks: "Got it, analyzing…"
scrask_bot.pyruns in auto mode. Gemini parses. Worst per-field score is 0.92. No Claude fallback.actionable_confidence= 0.96. Above threshold, so no actionable gate.shape_intentwalks mandatory fields fortype: event— title, date, time all present, all above 0.85. No clarifications.needs_confirmation: false.- Bot reads
needs_confirmation: false, routes tocalctl. Skill creates the event. - Bot replies: "Added to Calendar: Team Standup — 2026-03-01 at 09:00"
Ambiguous path — vague WhatsApp "lets meet fri"
- User sends screenshot. Bot acks.
- 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.
- Claude's avg is 0.65, Gemini's was 0.55. Improvement 0.10 ≥ 0.05 → Claude result kept.
actionable_confidence= 0.85. No actionable gate.shape_intentwalks mandatory fields fortype: event:titleOK (above threshold)datebelow threshold → clarificationreason: low_confidencetimeis null → clarificationreason: missingneeds_confirmation: true
- Bot reads
clarifications[]and asks: "I need to confirm: What date is meet on Friday? What time is meet on Friday?" - User replies. Bot patches the fields, routes to
calctl, confirms "Added to Calendar: …"
Actionable-gate path — flyer that might just be content
- User sends screenshot. Bot acks.
- Parser runs.
actionable_confidence= 0.55. Below 0.70 →needs_actionable_confirmation: true. - Bot leads with: "Is this actually an event or task? (55% sure) Reply yes to continue, or no to skip."
- User: "no" → "Got it, skipped". Done.
- User: "yes" → continue into the per-item loop (steps 5 onward from the ambiguous narrative above).