Skip to main content
Glama

Mise

The cooking assistant that tells you to wait.

Built for the Alexa+ track of Build, Ship, Shape: Amazon Developer Hackathon (2026).

Every recipe app pushes you forward through steps. Mise holds you back, because it can see the pan. Ask "can I add the garlic yet?" and it answers "Not yet — they're still firm at the edges. About ninety seconds."


Judges start here — 60 seconds, no credentials

No AWS account, no camera, no model, no API key. Four verdicts, four buttons.

git clone https://github.com/jwlai-cloud/mise.git && cd mise
python3 -m venv .venv && .venv/bin/pip install -e .
PYTHONPATH=src .venv/bin/python -m uvicorn mise.app:app --port 8000

Then open http://localhost:8000/dev/control and press 1 2 3 4, watching http://localhost:8000/dev/panel beside it.

Key

What the panel does

Why it matters

1

NOT YET climbing to GO

the ordinary case

2

CAN'T TELL — steam on the lens

the meter is past the gate marker and it still refuses. A guessing system says GO here

3

CAN'T TELL — frames stopped

the panel dims and counts the seconds since it last saw the pan

4

OFF THE HEAT

overrides the cooldown and a confidence too low to judge on

Button 2 is the whole submission. The perception is prior art and we say so (ADR-0002); what does not exist anywhere else is readiness as a refusable tool contract.

If you want to drive it as a real MCP client

The server is streamable HTTP at http://localhost:8000/mcp, spec 2025-11-25. A browser will return 406 — that is the protocol requiring an Accept header, not a fault.

npx @modelcontextprotocol/inspector      # then connect to http://localhost:8000/mcp

Call check_doneness after pressing a button. advance_step will refuse unless a passing observation was made in the last twenty seconds.

What you cannot try, and why

Talking to a real Echo. The Alexa+ MCP Toolkit publish path is a US-only private preview; the rules permit a simulated experience and this is it. Separately, Alexa's only camera-facing developer API is the Object Detection Sensor API — an Echo Show will not hand frames to a third-party server, so the camera is your own phone (ADR-0002).

A live pan. perception.py is built, validated and tested against a call shape verified in botocore's service model, but has never been invoked — blocked on an account-level Bedrock entitlement form and an IAM grant. docs/PRODUCT-FEEDBACK.md marks what runs versus what is only designed, tool by tool.

# python runs only the FIRST path given, so loop - one invocation per suite.
for t in tests/test_*.py; do PYTHONPATH=src python3 "$t" > /dev/null \
  && echo "PASS $t" || echo "FAIL $t"; done

Related MCP server: checkyourself

How it works

  phone camera ──frames──▶ vision loop ──▶ CookState cache ──▶ MCP tools ──▶ Alexa+
   (own clock, ~0.3Hz)      (slow, async)     (state.py)        (<500ms)      (voice)
                                                   │
                                                   └──▶ ui:// panel (polls 2s, stays live)

The single load-bearing decision: no tool call ever waits on a vision model. Alexa+ allows roughly 500ms per tool round-trip. A vision model does not fit in that budget and never will — so the model runs continuously on its own clock and writes to a cache, and tools only ever read the cache.

The second: voice is turn-based, the panel is not. Alexa+ cannot interrupt you to say "that's catching." But an MCP App panel marked visibility: ["app"] can be polled on an interval and keeps rendering after the voice turn ends — so the screen can shout while your hands are covered in flour.

Four verdicts, and the third one is the product

Verdict

Meaning

proceed

the gate is met

wait

not met — with the evidence, and roughly how long

refuse

frames are stale or confidence is low, so it will not guess

abort

something is catching; overrides everything, never gated on confidence

AgentCore — where the differentiation lives

The vision loop is not the innovation; camera-gated cooking is published work (Fullerton, IEEE IRI 2025) and shipping marketing copy. What does not exist anywhere is readiness as a refusable tool contract. That contract is designed to be enforced in three places, on purpose — but be clear about which of the three run today:

Layer

File

What it guarantees

Status

The gate itself

src/mise/gate.py

Deterministic, unit-tested, and willing to return refuse

runs

Session policy

src/mise/policy.py

The four rules, evaluated locally against the agent's own action history

runs

Strands steering

src/mise/steering.py

advance_step cancelled at the tool boundary, so the model cannot talk its way past the gate

needs pip install -e '.[aws]'

AgentCore Policy (Dogwood temporal)

policies/mise.dogwood

The same four rules as deployable policy — formerly within, count … since within

expressed, not yet deployed

policy.py mirrors the Dogwood semantics so the contract holds, and is testable, with or without AWS reachable. When AgentCore Policy is configured the remote decision wins; the local evaluator is the fallback, not a second opinion.

AgentCore Memory (src/mise/memory.py) does the other half: it learns what you mean by translucent on your hob, from you saying "that was too early." Samsung and GE calibrate to the appliance they sold you; nobody calibrates to your pan. Falls back to a local JSON file when AWS isn't configured, so the project runs with no credentials.

The abstention harness (evals/abstention.py) measures the thing that actually matters — not accuracy, but whether it declines exactly when its perception is unreliable:

frames               10
answered             7  (coverage 70%)
  WRONG              0   <- selective risk 0.0%
abstained            3
  justified          3   (estimate was off by >0.15)
  unnecessary        0
abstention precision 100%

a system that never refused would be wrong 20% of the time
this one is wrong 0.0% of the time, on 70% of frames

The harness refuses to call that a result, and so should you — ten hand-written seed rows is a smoke test for the metric, not evidence about the system. report() prints NOT A RESULT under 100 frames on purpose.

An abstention is judged against whether the estimate was actually unreliable (|doneness − label_doneness| > 0.15), not against whether it would have landed on the right side of the gate. That distinction is the whole metric. Steam crosses the lens, confidence collapses to 0.31, and the model still emits doneness=0.88; if that noise happens to be correct, side-of-gate scoring records the refusal as a mistake — rewarding luck and penalising calibration, on exactly the behaviour this project exists to demonstrate. Rows therefore carry a continuous label_doneness; rows with only the older label_ready bit still run and are reported as degraded.

The eval also prints a risk-coverage sweep over the confidence floor, because "the threshold is too conservative" is a claim you can only check against a table:

  floor   coverage   risk   abstained  justified
  0.30       80%   12.5%          2          2
  0.35       70%    0.0%          3          3
  0.60       70%    0.0%          3          3  <- current
  0.90       50%    0.0%          5          3

On the seed corpus there is a safe plateau from 0.35 to 0.85 where risk is 0% and coverage is identical; below 0.35 risk jumps to 11–12%. So the current floor is not costing coverage, and the earlier read that it was "too conservative" was an artefact of the old scoring. Re-run this against real labelled frames before trusting any of it.

The four policy rules

  1. Advancing requires a passing gate observed in the last 20 seconds — not the model's belief, an actual recent observation.

  2. No repeating the same refusal inside two minutes — every incumbent nags; a muted coach can't warn you when it matters. Cooldowns are keyed per verdict, so a repeated "Still not yet." never spends the budget a genuine "I still can't tell." needs. The panel always keeps the detail.

  3. Danger is never rate-limited and never gated on confidence.

  4. Three corrections on one step means the threshold is wrong, not the cook — stop blocking and hand control back. It stops blocking; it never starts claiming. The voice still says "not yet" or "I can't tell", with "but it's your call on this step" appended. Danger still overrides it.

Run it

pip install -e .
PYTHONPATH=src uvicorn mise.app:app --port 8000
  • MCP endpoint: http://localhost:8000/mcp (mcp 2.1 MCPServer, streamable HTTP, spec 2025-11-25)

  • Panel: http://localhost:8000/dev/panel

  • Demo control: http://localhost:8000/dev/control — four buttons, one per verdict

  • Phone camera: http://localhost:8000/dev/camera (needs HTTPS — see below)

Driving the demo

A scripted pan drives the state until the vision model is wired, so all four verdicts are reachable today with no model, no camera and no AWS:

curl "localhost:8000/dev/scenario?name=clean_run"   # NOT YET -> GO
curl "localhost:8000/dev/scenario?name=steam"       # CAN'T TELL: past the gate, lens steamed
curl "localhost:8000/dev/scenario?name=blocked"     # CAN'T TELL: frames stopped
curl "localhost:8000/dev/scenario?name=catches"     # OFF THE HEAT
curl "localhost:8000/dev/scenario?name=steam&at=40" # seek straight to the interesting beat
curl "localhost:8000/dev/reset"                     # clear learned calibration between takes

The camera page is a phone browser posting JPEGs to /ingest at ~0.3 Hz. It needs a secure context — a LAN address is refused outright, so tunnel it: cloudflared tunnel --url http://localhost:8000.

python3 tests/test_gate.py              # the decision logic
python3 tests/test_policy.py            # the four policy rules
python3 tests/test_steering.py          # the tool-boundary gate
python3 tests/test_scenarios.py         # all four verdicts are reachable
python3 tests/test_refusal_contract.py  # a refusal is never spoken as a wait
python3 tests/test_abstention.py        # the metric rewards calibration, not luck
python3 evals/abstention.py             # abstention quality over labelled frames

Layout

Path

What it is

src/mise/state.py

the cache — the boundary between the slow and fast worlds

src/mise/recipes.py

a recipe is a list of gates, not steps

src/mise/gate.py

the judgement, including the refusal path

src/mise/server.py

MCP server: tools + the ui://mise/panel resource

src/mise/vision.py

the slow loop — scripted scenarios today, model in week 1

src/mise/app.py

ASGI: /mcp plus dev routes

src/mise/memory.py

AgentCore Memory — per-cook, per-hob calibration

src/mise/policy.py

the refusal contract, locally evaluable

src/mise/steering.py

Strands steering at the tool boundary

policies/mise.dogwood

the Dogwood temporal policy source

evals/abstention.py

the pan eval — abstention quality, not accuracy. Stdlib, no AWS: AgentCore Evaluations scores traces, and StartBatchEvaluation cannot take a corpus.

evals/trajectory.py

the agent eval — did the graph take a sane path

evals/opik_pack.py

optional Opik wrapper for comparing model profiles

src/mise/agents.py

the hot graph — perception, a conditional critic, risk, arbiter

ui/panel.html

the live panel; works in an MCP host and a plain browser

ui/control.html

demo remote — one button per verdict, never in the shot

ui/camera.html

the phone: grab a frame, POST the JPEG, forget it

Docs

File

What it is

CLAUDE.md

Standing context and invariants for coding sessions

docs/PRODUCT.md

The problem, who it's for, the edge over prior art, and the non-goals

docs/PROGRESS.md

Read first. State, next actions, open questions

docs/ARCHITECTURE.md

Current-state system snapshot

docs/adr/

Decisions, immutable once accepted

docs/SPIKE.md

The three questions that retire the risk

docs/FRICTION.md

Friction log — the optional 10% judging bonus

docs/PRODUCT-FEEDBACK.md

Required submission element: the five questions, per tool

docs/SUBMISSION.md

The Devpost writeup, with a pre-submission checklist

infra/SETUP.md

AWS setup in four ordered steps; infra/preflight.py reports what is blocked

docs/LEARNING.md

Tech breakdown with primary sources

Copyright 2026 Junwei Lai. Licensed under the Apache License, Version 2.0 — see LICENSE.

Related MCP Connectors

Related MCP Servers