This benchmark did not begin as an attempt to build another model leaderboard.
We had a much narrower problem. A local Agent on a phone needs one default routing model. That model cannot merely hold a conversation. Within a short response window, it has to understand the request, select the right capability, extract execution parameters, and stop to ask a question when essential information is missing. It also has to fit on the device without turning an 8K context into an unacceptable memory or thermal cost.
The question we set out to answer was:
In our bilingual, structured, on-device Agent workflow, which model can make routing and execution decisions most reliably at a practical resource cost?
That qualification matters. We are not measuring general intelligence, writing quality, mathematics, or open-domain knowledge. We are answering a product engineering question.
Start with the Agent's actual job
Before an Agent can act on a request such as “Wake me at seven tomorrow,” it has at least two decisions to make.
First, it selects the right Skill from the capabilities currently available: alarms, calendars, mail, memory, and so on. It then selects an Action inside that Skill, extracts known parameters, identifies missing ones, and decides whether to act, plan, or ask the user for clarification.
This diagram could not be rendered. Its source is still available below.
View diagram source
flowchart LR
A[User request] --> B[Retrieve candidate Skills]
B --> C[Stage 1: select Skill]
C --> D[Stage 2: Action and parameters]
D --> E{Enough information?}
E -->|Yes| F[Act or plan]
E -->|No| G[Structured clarification]
F --> H[Swift Runtime validation]
G --> H
We do not ask the model for a plausible paragraph and grade it by feel. The evaluator requires an explicit structure: Skill, Action, Mode, known Slots, missing Slots, and objectives. The Swift Runtime then checks that the answer belongs to the retrieved candidate set and conforms to the current schema.
That distinction matters for an Agent with access to system capabilities. “Roughly understood the request” is not a useful execution guarantee. Choosing the wrong Action and omitting one required parameter are different failures, and both need to be visible.
What is in the 96-case suite
The model comparison uses the v3-96-staged-skill-action dataset:
| Dimension | Composition |
|---|---|
| Language | 48 Chinese, 48 English |
| Skills | 12 |
| Difficulty | 48 core, 48 challenge |
| Protocol | Two-stage Skill → Action/Slots/Mode |
| Context | Full run at both 4K and 8K |
Core cases cover common phrasing around alarms, calendars, mail drafts, and other routine requests. Challenge cases add nearby Skill distractors, missing required information, cross-capability planning, and more ambiguous wording.
We deliberately keep short requests in the set. Real phone commands are often only a few words long. A model that works only when every request is written like a test question is not particularly useful in a product.
Each case stores the expected Skill, Action, Mode, and relevant structural constraints. Published accuracy uses all 96 cases as the denominator. Parse failures are not quietly removed.
The 96 cases and their expected routes
The expected result below is the routing contract, not the final prose answer. A case passes each scored field only when the model selects the listed Skill, Action, and Mode. Extra assertion shows whether that case also checks missing-slot emptiness or requires a plan.
Three concrete scoring examples
- Complete command:
Set an alarm for 7 AM tomorrow.
builtin.alarm.manage → create_alarm, Mode act, missing_slots must be empty.
- Incomplete command:
Create an email draft for me.
builtin.mail.draft → create_draft, Mode clarify, missing_slots must be non-empty. The mail schema requires recipient, subject, and body.
- Multi-step command:
First inspect tomorrow's calendar, then create reminders for unresolved items.
builtin.task.plan_recover → orchestrate, Mode act, and requires_plan = true.
One current limitation is visible here: the v3 suite scores missing slots as empty versus non-empty. It does not yet award separate points for naming every missing field correctly. Exact slot names and values belong in the next contract-test version.
Core: 48 routine cases
| # / Case ID | Lang | Test input | Expected Skill → Action | Mode | Extra assertion |
|---|---|---|---|---|---|
1 · zh-alarm-create | ZH | 明天早上七点设置一个起床闹钟 | builtin.alarm.manage → create_alarm | act | missing: empty |
2 · zh-alarm-list | ZH | 查看我在 Vesta 里创建的所有闹钟 | builtin.alarm.manage → list_alarms | retrieve | — |
3 · en-alarm-create | EN | Set an alarm for 7 AM tomorrow. | builtin.alarm.manage → create_alarm | act | missing: empty |
4 · en-alarm-list | EN | List all alarms currently managed by Vesta. | builtin.alarm.manage → list_alarms | retrieve | — |
5 · zh-schedule-event | ZH | 明天下午三点在 A 会议室安排项目评审,持续一小时 | builtin.schedule.manage → create_calendar_event | act | missing: empty |
6 · zh-schedule-reminder | ZH | 提醒我周五下午六点提交周报 | builtin.schedule.manage → create_reminder | act | missing: empty |
7 · en-schedule-event | EN | Create a calendar event for project review tomorrow at 3 PM for one hour. | builtin.schedule.manage → create_calendar_event | act | missing: empty |
8 · en-schedule-reminder | EN | Remind me Friday at 6 PM to submit the weekly report. | builtin.schedule.manage → create_reminder | act | missing: empty |
9 · zh-brief-today | ZH | 汇总我今天的日程和未完成提醒 | builtin.daily.briefing → summarize_schedule | retrieve | — |
10 · zh-brief-conflict | ZH | 看看本周安排里有没有冲突和逾期事项 | builtin.daily.briefing → summarize_schedule | retrieve | — |
11 · en-brief-today | EN | Summarize today's calendar and unfinished reminders. | builtin.daily.briefing → summarize_schedule | retrieve | — |
12 · en-brief-overdue | EN | Show my schedule conflicts and overdue reminders this week. | builtin.daily.briefing → summarize_schedule | retrieve | — |
13 · zh-general-explain | ZH | 解释一下什么是最终一致性 | builtin.general.answer → answer | answer | — |
14 · zh-general-howto | ZH | 如何安全地删除一个文件?只需要解释步骤 | builtin.general.answer → answer | answer | — |
15 · en-general-explain | EN | Explain eventual consistency in simple terms. | builtin.general.answer → answer | answer | — |
16 · en-general-howto | EN | How do I delete a file safely? Explain only. | builtin.general.answer → answer | answer | — |
17 · zh-knowledge-cite | ZH | 根据我的本地项目文档,发布日期是什么?请引用来源 | builtin.knowledge.answer → answer_from_knowledge | retrieve | — |
18 · zh-knowledge-summary | ZH | 总结我导入的合同里关于违约责任的内容 | builtin.knowledge.answer → answer_from_knowledge | retrieve | — |
19 · en-knowledge-cite | EN | Search my local documents for the launch date and cite the source. | builtin.knowledge.answer → answer_from_knowledge | retrieve | — |
20 · en-knowledge-compare | EN | Compare the pricing terms in my two imported contracts. | builtin.knowledge.answer → answer_from_knowledge | retrieve | — |
21 · zh-connected-contact | ZH | 查一下通讯录里 Alex 的电话号码 | builtin.connected.context → answer_from_source | retrieve | — |
22 · zh-connected-web | ZH | 总结这个网页的内容:https://example.com/report | builtin.connected.context → answer_from_source | retrieve | — |
23 · en-connected-mail | EN | Look in my Gmail for the latest message from Alex. | builtin.connected.context → answer_from_source | retrieve | — |
24 · en-connected-location | EN | What city am I currently in? | builtin.connected.context → answer_from_source | retrieve | — |
25 · zh-document-import | ZH | 选择并导入这份 PDF 到本地知识库 | builtin.document.ingest → import_document | act | — |
26 · zh-document-ocr | ZH | 识别我选择的图片文字并录入知识库 | builtin.document.ingest → scan_document | act | — |
27 · en-document-import | EN | Import the selected PDF into my local knowledge base. | builtin.document.ingest → import_document | act | — |
28 · en-document-ocr | EN | Run OCR on the selected image and add it to the knowledge base. | builtin.document.ingest → scan_document | act | — |
29 · zh-memory-remember | ZH | 记住我不喜欢订阅制软件 | builtin.memory.governance → remember | act | missing: empty |
30 · zh-memory-forget | ZH | 忘记我之前保存的语言偏好 | builtin.memory.governance → forget | act | — |
31 · en-memory-list | EN | Show the long-term preferences you remember about me. | builtin.memory.governance → list_memories | retrieve | — |
32 · en-memory-remember | EN | Remember that I prefer one-time purchases. | builtin.memory.governance → remember | act | missing: empty |
33 · zh-mail-complete | ZH | 给 Alex 起草邮件,主题是周会,正文写明改到周五下午三点 | builtin.mail.draft → create_draft | act | missing: empty |
34 · zh-mail-missing | ZH | 帮我起草一封邮件 | builtin.mail.draft → create_draft | clarify | missing: non-empty |
35 · en-mail-complete | EN | Draft an email to Alex with subject Weekly Sync and say it moved to Friday at 3 PM. | builtin.mail.draft → create_draft | act | missing: empty |
36 · en-mail-missing | EN | Create an email draft for me. | builtin.mail.draft → create_draft | clarify | missing: non-empty |
37 · zh-runtime-contract | ZH | 检查 Vesta 当前的 Runtime 和安全执行契约 | builtin.runtime.inspect → inspect_runtime | retrieve | — |
38 · zh-runtime-mcp | ZH | 列出本机 MCP Runtime 当前暴露的能力 | builtin.runtime.inspect → inspect_runtime | retrieve | — |
39 · en-runtime-contract | EN | Inspect Vesta's current runtime and safety contract. | builtin.runtime.inspect → inspect_runtime | retrieve | — |
40 · en-runtime-mcp | EN | Show the capabilities exposed by the local MCP runtime. | builtin.runtime.inspect → inspect_runtime | retrieve | — |
41 · zh-plan-sequence | ZH | 先查看明天日程,然后为没有空闲时间的事项创建提醒 | builtin.task.plan_recover → orchestrate | act | requires plan |
42 · zh-plan-breakdown | ZH | 把准备发布会这件事拆成可验证的步骤并跟踪进度 | builtin.task.plan_recover → orchestrate | act | requires plan |
43 · en-plan-sequence | EN | First inspect tomorrow's calendar, then create reminders for unresolved items. | builtin.task.plan_recover → orchestrate | act | requires plan |
44 · en-plan-breakdown | EN | Break down the product launch preparation into verifiable steps and track progress. | builtin.task.plan_recover → orchestrate | act | requires plan |
45 · zh-data-export | ZH | 导出并加密备份我的全部 Vesta 本地数据 | builtin.data.portability → export_package | act | — |
46 · zh-data-restore | ZH | 从这个 vestaexport 数据包选择性恢复记忆和任务 | builtin.data.portability → restore_package | act | — |
47 · en-data-export | EN | Export an encrypted backup of all my local Vesta data. | builtin.data.portability → export_package | act | — |
48 · en-data-inspect | EN | Inspect this Vesta backup package without restoring it. | builtin.data.portability → inspect_package | retrieve | — |
Challenge: 48 boundary cases
| # / Case ID | Lang | Test input | Expected Skill → Action | Mode | Extra assertion |
|---|---|---|---|---|---|
49 · challenge-zh-alarm-recurring | ZH | 每个工作日早上八点半叫我起床 | builtin.alarm.manage → create_alarm | act | missing: empty |
50 · challenge-zh-alarm-query | ZH | 我明早有设叫醒铃吗? | builtin.alarm.manage → list_alarms | retrieve | — |
51 · challenge-en-alarm-recurring | EN | Wake me at 6:45 every weekday. | builtin.alarm.manage → create_alarm | act | missing: empty |
52 · challenge-en-alarm-query | EN | Do I have a wake-up alarm tomorrow morning? | builtin.alarm.manage → list_alarms | retrieve | — |
53 · challenge-zh-schedule-create | ZH | 下周一上午十点留半小时和产品经理同步 | builtin.schedule.manage → create_calendar_event | act | missing: empty |
54 · challenge-zh-schedule-delete | ZH | 删除明天下午由 Vesta 创建的牙医提醒 | builtin.schedule.manage → delete_reminder | act | — |
55 · challenge-en-schedule-create | EN | Put lunch with Sam on my calendar at noon Tuesday. | builtin.schedule.manage → create_calendar_event | act | missing: empty |
56 · challenge-en-schedule-delete | EN | Cancel the Vesta reminder to pay rent. | builtin.schedule.manage → delete_reminder | act | — |
57 · challenge-zh-brief-tomorrow | ZH | 我明天有哪些会和待办,按时间排一下 | builtin.daily.briefing → summarize_schedule | retrieve | — |
58 · challenge-zh-brief-load | ZH | 这周哪几天的安排最满? | builtin.daily.briefing → summarize_schedule | retrieve | — |
59 · challenge-en-brief-morning | EN | Give me a morning briefing from my calendar and reminders. | builtin.daily.briefing → summarize_schedule | retrieve | — |
60 · challenge-en-brief-load | EN | Which days this week are overloaded? | builtin.daily.briefing → summarize_schedule | retrieve | — |
61 · challenge-zh-general-rewrite | ZH | 把这句话改得更礼貌:你写错了 | builtin.general.answer → answer | answer | — |
62 · challenge-zh-general-calculate | ZH | 计算 240 的 18% 是多少 | builtin.general.answer → answer | answer | — |
63 · challenge-en-general-rewrite | EN | Rewrite this more politely: You are wrong. | builtin.general.answer → answer | answer | — |
64 · challenge-en-general-howto | EN | How can I set an alarm on iPhone? Explain the steps only. | builtin.general.answer → answer | answer | — |
65 · challenge-zh-knowledge-policy | ZH | 从已经收录的报销制度里找出打车标准 | builtin.knowledge.answer → answer_from_knowledge | retrieve | — |
66 · challenge-zh-knowledge-compare | ZH | 对比知识库中两份方案的风险章节 | builtin.knowledge.answer → answer_from_knowledge | retrieve | — |
67 · challenge-en-knowledge-policy | EN | According to documents already in my library, what is the reimbursement limit? | builtin.knowledge.answer → answer_from_knowledge | retrieve | — |
68 · challenge-en-knowledge-summary | EN | Summarize the termination clause from the contract I previously imported. | builtin.knowledge.answer → answer_from_knowledge | retrieve | — |
69 · challenge-zh-connected-web | ZH | 打开并概括这个链接:https://example.com/brief | builtin.connected.context → answer_from_source | retrieve | — |
70 · challenge-zh-connected-outlook | ZH | 在 Outlook 里找 Alex 最新发来的邮件 | builtin.connected.context → answer_from_source | retrieve | — |
71 · challenge-en-connected-contact | EN | Find Jordan's phone number in my contacts. | builtin.connected.context → answer_from_source | retrieve | — |
72 · challenge-en-connected-web | EN | Read and summarize https://example.com/brief. | builtin.connected.context → answer_from_source | retrieve | — |
73 · challenge-zh-document-scan | ZH | 把我刚选的扫描件 OCR 后加入资料库 | builtin.document.ingest → scan_document | act | — |
74 · challenge-zh-document-word | ZH | 导入这个 Word 文件并建立本地索引 | builtin.document.ingest → import_document | act | — |
75 · challenge-en-document-photo | EN | Extract text from this selected photo and save it to my local library. | builtin.document.ingest → scan_document | act | — |
76 · challenge-en-document-sheet | EN | Add the selected spreadsheet to my local documents. | builtin.document.ingest → import_document | act | — |
77 · challenge-zh-memory-remember | ZH | 以后回答尽量简短,请记住这个偏好 | builtin.memory.governance → remember | act | missing: empty |
78 · challenge-zh-memory-list | ZH | 列出你长期保存的关于我的偏好 | builtin.memory.governance → list_memories | retrieve | — |
79 · challenge-en-memory-forget | EN | Forget that I prefer dark mode. | builtin.memory.governance → forget | act | — |
80 · challenge-en-memory-update | EN | Change my saved language preference to Chinese. | builtin.memory.governance → remember | act | — |
81 · challenge-zh-mail-complete | ZH | 给财务起草邮件,说明发票将在周五补交 | builtin.mail.draft → create_draft | act | missing: empty |
82 · challenge-zh-mail-missing | ZH | 写封邮件通知项目延期 | builtin.mail.draft → create_draft | clarify | missing: non-empty |
83 · challenge-en-mail-complete | EN | Draft an email to finance saying the invoice will arrive Friday. | builtin.mail.draft → create_draft | act | missing: empty |
84 · challenge-en-mail-missing | EN | Write an email announcing the delay. | builtin.mail.draft → create_draft | clarify | missing: non-empty |
85 · challenge-zh-runtime-sources | ZH | 检查 Vesta 当前授权了哪些数据源 | builtin.runtime.inspect → inspect_runtime | retrieve | — |
86 · challenge-zh-runtime-tools | ZH | 查看本地执行器现在注册了哪些工具 | builtin.runtime.inspect → inspect_runtime | retrieve | — |
87 · challenge-en-runtime-tools | EN | Which tools are currently registered in the local runtime? | builtin.runtime.inspect → inspect_runtime | retrieve | — |
88 · challenge-en-runtime-write | EN | Check whether this runtime is allowed to execute write operations. | builtin.runtime.inspect → inspect_runtime | retrieve | — |
89 · challenge-zh-task-sequence | ZH | 先找出今天逾期的事项,再逐项建立新的提醒 | builtin.task.plan_recover → orchestrate | act | requires plan |
90 · challenge-zh-task-resume | ZH | 继续上次中断的发布准备任务并从失败步骤恢复 | builtin.task.plan_recover → recover | act | requires plan |
91 · challenge-en-task-sequence | EN | Review today's overdue items first, then create a reminder for each one. | builtin.task.plan_recover → orchestrate | act | requires plan |
92 · challenge-en-task-resume | EN | Resume the interrupted launch task from its failed step. | builtin.task.plan_recover → recover | act | requires plan |
93 · challenge-zh-data-inspect | ZH | 只检查这个备份包里有什么,不要恢复 | builtin.data.portability → inspect_package | retrieve | — |
94 · challenge-zh-data-export | ZH | 把知识库和长期偏好导出成加密数据包 | builtin.data.portability → export_package | act | — |
95 · challenge-en-data-restore | EN | Restore only memories from this Vesta export package. | builtin.data.portability → restore_package | act | — |
96 · challenge-en-data-inspect | EN | Verify this backup archive without importing anything. | builtin.data.portability → inspect_package | retrieve | — |
What we score
We do not collapse the result into one weighted score that lets strengths hide failures.
| Metric | Question it answers |
|---|---|
| Parse | Did both stages produce a complete contract? |
| Skill | Was the correct capability selected? |
| Action | Was the correct operation selected? |
| Mode | Should the Agent act, plan, or clarify? |
| Missing Slots | Did it stop when required information was absent? |
| Scope Validity | Did the answer remain inside the candidate set and schema? |
| TTFT / E2E | How quickly did generation begin and routing finish? |
A high Parse rate does not imply semantic correctness. A model can emit perfectly valid JSON while consistently selecting the wrong Skill. The opposite failure is also possible: the model appears to understand the request, but its output drifts far enough that the Runtime cannot use it.
For a model that may drive execution, Skill, Action, Mode, and missing-slot behavior matter most. A model with strong Skill selection but weak Action accuracy may still be interesting as a first-stage shortlist model, but it should not receive direct authority to produce execution parameters.
Fair does not mean one literal prompt wrapper
All 10 models use GGUF Q4_K_M, the same evaluator, dataset, 4K/8K settings, and fixed decoding:
temperature = 0
top_p = 1
top_k = 1
seed = 42
candidate_limit = 3
We do not force the same ChatML string onto every model. Llama 3 uses its header tokens, SmolLM3 explicitly disables extended thinking, and LFM2.5 receives its required start-of-text token. The task, information, and output contract stay the same; the outer template follows the model's native format.
That is our definition of a fair comparison: keep the work and rules constant without knowingly giving a model the wrong input format.
For every weight, we also record the publisher, repository, commit, filename, byte count, SHA-256, license, and Prompt Profile. A model name alone is not enough to reproduce a result. Two conversions or quantizations carrying the same name may behave differently.
Why capability and phone measurements are separate
This is the easiest part of the study to misread.
The 96 capability cases run with the same model files in a desktop Metal evaluator. That environment makes it practical to execute the suite in bulk, retain every raw answer, and repeat comparisons under stable conditions.
Resource measurements come from an iPhone 15 Pro Max with A17 Pro, 8 GB of physical memory, SwiftLlama, and llama.cpp b8668. Every model is tested at 4K and 8K, with three independent launches for each combination, for 60 launches in total.
The phone run records:
- model load time;
- TTFT for a short prompt;
- process memory;
- Metal memory;
- thermal state;
- whether basic generation completed.
This diagram could not be rendered. Its source is still available below.
View diagram source
flowchart TB
A[Pin model file and SHA-256] --> B1[Desktop Metal capability run]
A --> B2[iPhone resource run]
B1 --> C1[96 bilingual requests]
C1 --> D1[Parse Skill Action Mode]
B2 --> C2[Three independent 4K and 8K launches]
C2 --> D2[Load TTFT Memory Thermal]
D1 --> E[Selection decision]
D2 --> E
We keep these evidence tracks separate. Desktop capability accuracy is not presented as iPhone end-to-end task success. Successful loading on the phone does not prove that the model understood the 96 requests.
What 4K and 8K mean in this round
The phone prompt and generation are intentionally short. The 4K/8K measurements answer three questions:
- Can the model load with that context configuration on the target device?
- How much extra memory does the larger context reserve?
- Are startup, first-token latency, and thermal state acceptable for a short request?
They do not prove that speed remains unchanged after filling 8K tokens, and they do not measure long-prompt prefill throughput. That requires real workloads around 3.5K and 7.5K tokens, plus prefill speed, generation throughput, energy use, and sustained thermal curves.
In this round, 8K is a capacity and resource test, not an “8K long-context certification.”
How one result becomes evidence
Each capability run retains the raw model output and its SHA-256, not just the final percentage. The summary JSON records:
- dataset version and digest;
- model file SHA-256;
- Prompt configuration SHA-256;
- Prompt Profile;
- context size and candidate limit;
- expected and actual values for each case;
- TTFT, end-to-end latency, and token counts;
- whether format repair was needed.
On the phone side, we retain the raw text from every independent launch, CSV summaries, and per-model checksum files. The full evidence directory has a separate SHA256SUMS.txt.
This does not eliminate every possible human error. It does let us answer three basic questions later: which exact file was tested, which configuration was used, and which raw samples produced the percentage in the table.
What this benchmark does not prove
The current evidence supports choosing a router for this particular on-device Agent. It does not directly support claims that:
- one model has greater general intelligence;
- one model is better across every language and Tool domain;
- long 8K prefill performance has been validated;
- real alarm, calendar, or mail tasks achieve a stated end-to-end success rate;
- this is an academic or industry-wide ranking.
Of the 96 current cases, the 48 core cases have participated in development feedback and are not a fully independent final holdout. Important slices such as clarification and planning are still small. Each phone configuration has only three launches, without randomized model order, controlled cool-down, battery measurement, or a sustained 20-turn session.
Those limitations are not a polite paragraph added at the end. They are part of the result.
What we are adding next
The next version has a stricter target:
- Build an independent holdout with at least 300 cases, isolated from tuning.
- Have two non-authors review every case against a versioned rubric.
- Run at least three trials per case and report stable passes, unstable cases, and the 95% Wilson lower bound.
- Add real 3.5K/7.5K prefill, sustained sessions, energy use, thermal curves, and Jetsam recovery.
- Validate parameters, Tool order, approvals, clarification, and final environment state in isolated Tool fixtures.
- Finish with a small set of high-risk end-to-end checks against real system capabilities on the phone.
A 300-case tuning/holdout draft and the evaluation harness already exist, but the cases have not completed independent two-person review. We therefore do not present them as formal results.
Back to the original question
This method does not try to identify “the world's smartest small model.” It does something narrower and more useful: under a defined device, Runtime, and Agent contract, it identifies the best default for the current workflow and makes the reasons, failures, and evidence boundaries inspectable.
For the full 10-model results, including 4K/8K iPhone resources and model-by-model findings, read 10 Small Language Models on an iPhone.