<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Phinn</title>
    <description>The latest articles on DEV Community by Phinn (@phinn).</description>
    <link>https://dev.to/phinn</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4137618%2F0bd907c7-4cbe-425f-8c17-71f4daf4a117.png</url>
      <title>DEV Community: Phinn</title>
      <link>https://dev.to/phinn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/phinn"/>
    <language>en</language>
    <item>
      <title>Cost data surprised me</title>
      <dc:creator>Phinn</dc:creator>
      <pubDate>Thu, 01 Oct 2026 14:26:53 +0000</pubDate>
      <link>https://dev.to/phinn/cost-data-surprised-me-3f7m</link>
      <guid>https://dev.to/phinn/cost-data-surprised-me-3f7m</guid>
      <description>&lt;p&gt;稿子写好了:&lt;code&gt;drafts/devto_kinetaios_engines.md&lt;/code&gt;,直接可用(带 front-matter,&lt;code&gt;published: false&lt;/code&gt; 默认草稿)。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;英文全文如下:&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  I ran 4 AI coding agents on the same 400k-line project. The cost data surprised me.
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;This is a build-log, not a landing page. I built a multi-engine agent dashboard (&lt;a href="https://github.com/phinn/KinetAios" rel="noopener noreferrer"&gt;KinetAios&lt;/a&gt;, GPLv3, macOS + Windows) and benchmarked four agent engines on the same real task: a 400k-line medical-device CRM cross-analysis job. Same prompt, zero human intervention, full token accounting. My own engine lost the first round of architectural arguments. Numbers below — including the ones that embarrassed me.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The task: cross-analyze a real 400k-line CRM dataset (multi-sheet Excel, hospital tiers × time windows), produce an interactive ECharts report. This is the kind of messy, multi-hour job people actually hand to coding agents — not a LeetCode benchmark.&lt;/p&gt;

&lt;p&gt;Four engines, one prompt each, no babysitting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct&lt;/strong&gt; — my in-house ReAct loop with native tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; — Anthropic's CLI, spawned as an engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex&lt;/strong&gt; — OpenAI's CLI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PEVJ V2&lt;/strong&gt; — my older Plan-Execute-Verify-Judge architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The scoreboard
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Score /10&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;What killed it (or didn't)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direct (mine)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9.2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.31M&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native SAX streaming — big files never enter context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;7.0&lt;/td&gt;
&lt;td&gt;~2M&lt;/td&gt;
&lt;td&gt;Best context management, but no plugin tools → detours through Bash+python every step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;5.5&lt;/td&gt;
&lt;td&gt;~2.4M&lt;/td&gt;
&lt;td&gt;Sandbox-first design fights interactive data tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PEVJ V2 (also mine)&lt;/td&gt;
&lt;td&gt;3.5&lt;/td&gt;
&lt;td&gt;3.8M+&lt;/td&gt;
&lt;td&gt;Verify stage with no guardrails = a token amplifier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Yes, my newer engine beat my older engine and Claude Code. Yes, the older one is in the table anyway. That's the point of publishing trace data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The finding that actually matters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;7.5% of API calls burned 63% of the total budget.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I dug into the traces expecting a model problem. It wasn't. The worst offender read the same big file &lt;em&gt;sequentially, 16 times&lt;/em&gt; — and every read resent the full conversation history. The model wasn't dumb; the tool design was.&lt;/p&gt;

&lt;p&gt;The fix wasn't a better model. It was a streaming reader (SAX-style, so the file never enters context at all). That line item went to zero.&lt;/p&gt;

&lt;p&gt;If you're building anything that lets an LLM touch large files, instrument per-tool-call cost &lt;strong&gt;before&lt;/strong&gt; you tune prompts. Your "expensive model" problem is probably a tool granularity problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real multi-engine workday costs
&lt;/h2&gt;

&lt;p&gt;The benchmark is one data point. Here's an actual day: four parallel sessions, mixed engines, isolated cost accounting per session.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Session&lt;/th&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Cost that day&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Sonnet&lt;/td&gt;
&lt;td&gt;Refactor an 800-line module&lt;/td&gt;
&lt;td&gt;$2.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;o4-mini&lt;/td&gt;
&lt;td&gt;Fix 17 failing tests&lt;/td&gt;
&lt;td&gt;$0.87&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Direct&lt;/td&gt;
&lt;td&gt;GLM&lt;/td&gt;
&lt;td&gt;Code retrieval + doc Q&amp;amp;A&lt;/td&gt;
&lt;td&gt;$0.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;Direct&lt;/td&gt;
&lt;td&gt;GLM&lt;/td&gt;
&lt;td&gt;Long-term memory housekeeping&lt;/td&gt;
&lt;td&gt;$0.03&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;$3.43 total.&lt;/strong&gt; All in one window, all running concurrently. The boring economics: expensive models on hard tasks, cheap models on mechanical ones — and that only works if sessions can run side by side with per-session model choice. One terminal per CLI doesn't compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three engineering lessons from making multi-engine actually work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Total state isolation, or sessions will eat each other
&lt;/h3&gt;

&lt;p&gt;We shipped a bug where session A was waiting on a shell-command confirmation — and session B consumed it. One modal queue shared across sessions, in a multi-agent app, is a race condition wearing a UI. Every session now owns its confirm queue and its entire tool context. Isolation isn't a feature here; it's the architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. SQLite WAL is the right substrate for desktop agents
&lt;/h3&gt;

&lt;p&gt;Readers never block the writer. Browsing session history while the current session streams writes: zero &lt;code&gt;SQLITE_BUSY&lt;/code&gt; in months of daily use. Plus the file &lt;em&gt;is&lt;/em&gt; the export format — copy it, it opens anywhere. And because it's a plain local file, other tools (scripts, other agents) can read your session history directly. Cloud apps lock that behind APIs; local SQLite hands it to you.&lt;/p&gt;

&lt;p&gt;One adjacent gotcha: FTS5's default &lt;code&gt;unicode61&lt;/code&gt; tokenizer is a disaster for Chinese text. We went bigram + a custom index to pull recall up. If your agent serves non-English users, check this before launch, not after.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The local-first part is a threat model, not a vibe
&lt;/h3&gt;

&lt;p&gt;The agent executes on your machine — shell, files, SQLite history, local vector index. The only cloud is your own API key for inference, and the free tier runs entirely on local Ollama models. No relay server, no account, no telemetry. Your code never transits anyone's middleman.&lt;/p&gt;

&lt;p&gt;I keep this framing because "local-first" gets used as marketing glitter. The test is simple: can you point at the exact bytes that leave your machine? If the answer is "just the inference requests to your own key," that's a defensible design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's still hard
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Interactive approvals across concurrent sessions (see lesson 1) — the modal-confirm pattern gets weird at 3+ parallel agents. Headless timeout-with-auto-deny is the next step.&lt;/li&gt;
&lt;li&gt;Session transcript size grows unbounded; recall works, but "summarize my last 40 hours of agent work" is still an open problem for us.&lt;/li&gt;
&lt;li&gt;Windows build parity (Electron) always trails macOS by a week or two. Solo-dev tax.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;KinetAios&lt;/strong&gt; — the dashboard from this post: &lt;a href="https://github.com/phinn/KinetAios" rel="noopener noreferrer"&gt;github.com/phinn/KinetAios&lt;/a&gt; (GPLv3, macOS + Windows, free tier with Ollama, BYO key). ~1,000+ users reached so far; if this post saved you time, a star genuinely helps an indie project get seen.&lt;/li&gt;
&lt;li&gt;Full benchmark report with per-step traces (including my own engine's 3.5/10 failure details): &lt;a href="https://github.com/phinn/KinetAios/blob/main/documents/excel-cross-analysis-engines.html" rel="noopener noreferrer"&gt;excel-cross-analysis-engines.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask me anything about the benchmark methodology, the token accounting, or multi-engine session isolation in the comments — happy to share traces.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>agents</category>
    </item>
    <item>
      <title>I built a task app that stores everything in one plain SQLite file — and I don't regret skipping Core Data</title>
      <dc:creator>Phinn</dc:creator>
      <pubDate>Tue, 22 Sep 2026 12:30:34 +0000</pubDate>
      <link>https://dev.to/phinn/i-built-a-task-app-that-stores-everything-in-one-plain-sqlite-file-and-i-dont-regret-skipping-ode</link>
      <guid>https://dev.to/phinn/i-built-a-task-app-that-stores-everything-in-one-plain-sqlite-file-and-i-dont-regret-skipping-ode</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the engineering story behind &lt;a href="https://apps.apple.com/app/id6780944289" rel="noopener noreferrer"&gt;KinetTask&lt;/a&gt;, a Mac/iOS task app I shipped last week. It's a Show HN-adjacent story but dev.to gets the parts HN would skewer me for. Numbers and regrets included.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The decision everyone told me was wrong
&lt;/h2&gt;

&lt;p&gt;When I started building a local-first task app, every senior Swift dev I asked said the same thing: "Use GRDB. Or SwiftData. Writing SQLite by hand is masochism."&lt;/p&gt;

&lt;p&gt;I did it anyway: &lt;strong&gt;the SQLite3 C API, directly, in Swift 6.1&lt;/strong&gt;. Not because I'm tough — because I wanted exactly three things and none of them needed an ORM:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The database file &lt;strong&gt;is&lt;/strong&gt; the export format. Copy &lt;code&gt;tasks.sqlite3&lt;/code&gt; to another machine, it just opens. No "export" feature needed.&lt;/li&gt;
&lt;li&gt;Every agent action lands as an &lt;strong&gt;append-only journal row&lt;/strong&gt; — so when the AI planner retries a task, I can diff what actually changed instead of replaying blindly.&lt;/li&gt;
&lt;li&gt;Checkpoints store a &lt;strong&gt;hash of the normalized artifact&lt;/strong&gt;. Hash the normalized artifact, not the raw output — timestamps will gaslight you into thinking two identical results differ.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The actual surface area (it's small)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The whole "ORM":&lt;/span&gt;
&lt;span class="c1"&gt;// - open with WAL + busy_timeout&lt;/span&gt;
&lt;span class="c1"&gt;// - one prepared statement per query&lt;/span&gt;
&lt;span class="c1"&gt;// - one user_version pragma per schema migration step&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. A few tables, WAL mode, prepared statements, &lt;code&gt;PRAGMA user_version&lt;/code&gt; for migrations. The entire persistence layer is ~600 lines including the journal and checkpoint logic. GRDB would have been 150 lines of &lt;em&gt;my&lt;/em&gt; code plus a dependency I'd have to reason about during every weird concurrency bug.&lt;/p&gt;

&lt;p&gt;The honest cost: I wrote two use-after-free bugs against &lt;code&gt;sqlite3_stmt&lt;/code&gt; in the first week. C API gives you exactly enough rope.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that justified the whole approach (v1.2.1)
&lt;/h2&gt;

&lt;p&gt;My iOS widget was silently losing every checkbox tap. Root cause, the fun kind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Main app's DB lived in its sandbox &lt;code&gt;Application Support&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The widget wrote to the &lt;strong&gt;App Group container&lt;/strong&gt; — a &lt;em&gt;different physical file&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Both files happily existed forever. No crash. No error. Just… widget taps vanishing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fix: unify the data root (App Group on iOS), migrate legacy DBs, turn on &lt;strong&gt;WAL + busy_timeout(2s)&lt;/strong&gt; because now two processes share one file, and add a snapshot-reconciliation pass when the main app becomes active — because the widget writing to SQLite doesn't magically invalidate the main app's in-memory cache, and its next &lt;code&gt;UPDATE&lt;/code&gt; would happily &lt;strong&gt;overwrite the tap with stale state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;None of this was exotic. All of it was invisible until a real user compared widget state with list state. With Core Data this specific bug would've been &lt;em&gt;harder&lt;/em&gt; to create (shared store coordinator handles cross-process hints) — that's the fair counterargument, and I'd still make the same trade, because I can &lt;code&gt;sqlite3 tasks.sqlite3&lt;/code&gt; on a user's machine during support and &lt;em&gt;see everything&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What on-device LLM actually feels like
&lt;/h2&gt;

&lt;p&gt;The AI features (natural-language capture, "what should I focus on") run on-device: Apple's foundation model where available, a GGUF model via llama.cpp in-process otherwise.&lt;/p&gt;

&lt;p&gt;The real numbers nobody puts in marketing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold inference on a 3B model: noticeably dumber than any cloud model. For "parse this sentence into a task with due date" it's fine. For "reorganize my projects" it's… enthusiastic.&lt;/li&gt;
&lt;li&gt;The failure mode isn't hallucination, it's &lt;strong&gt;over-triggering&lt;/strong&gt;: the model wants to help when it shouldn't. Fix wasn't a better model — it was an anti-filler gate: if the parse adds no fields beyond what you typed, don't create filler structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ship the widget &lt;strong&gt;after&lt;/strong&gt; verifying both processes write the same file. Obvious in hindsight; it always is.&lt;/li&gt;
&lt;li&gt;The journal + hash checkpoint design came from my agent-engine side project, and it's the only reason I could debug the retry-drift cases at all. If your app has &lt;em&gt;any&lt;/em&gt; automated writes, an append-only log is cheap insurance.&lt;/li&gt;
&lt;li&gt;I still don't have sync. The file is yours — iCloud Drive it if you want. v1.2 added a &lt;code&gt;.kinettask&lt;/code&gt; backup format for machine moves, which covers 90% of what people actually ask for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free for 50 tasks, one-time $4.99, no subscription, no account, no telemetry: &lt;a href="https://apps.apple.com/app/id6780944289" rel="noopener noreferrer"&gt;App Store&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ask me anything about the SQLite internals in the comments — especially if you think skipping GRDB was stupid, I genuinely want that thread.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>sqlite</category>
      <category>ios</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
