DEV Community

Cover image for Sift: share it, sleep, it's filed — on-device Gemma, no cloud
Fahad Irshad
Fahad Irshad

Posted on

Sift: share it, sleep, it's filed — on-device Gemma, no cloud

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission 🦥

This is my submission for the Hacktoberfest Open-Source AI Challenge, Week 3: Slow and Steady.

Share a thing. Put the phone down. It's filed by morning.

Sift is a junk drawer that files itself. You share a link, a gate code, a recipe to it — two taps, zero decisions — and it closes. Overnight, while the phone charges, Gemma 4 reads every snippet you kept and sorts it into one of eight drawers with a short title of its own. In the morning it's a card catalog.

The app holds no internet permission. Not "we don't upload your notes" — the operating system forbids it. The model file is the only thing you ever download, and you hand it over yourself.

Sift: share it, sleep, it's filed

What I Built

The share sheet is where good intentions go to die. You see a gate code, a gift idea, a link you'll read later. You tap Share. Then you stare at a list of apps and remember what keeping it actually costs: open a notes app, make a note, paste, title it, pick a folder, decide where it belongs. You put the phone down instead. The link is gone by dinner.

Sift is the answer to that pause. Share the thing to Sift and walk away. The keep is instant; the sorting is deferred to a time when you are doing nothing anyway. Nothing happens while you watch. That is the product.

Demo

One real run on an Android emulator: seven snippets pushed in through the same ACTION_SEND intent any app's share sheet fires, then one tap on FILE NOW because I was not going to wait until morning for a screenshot. Overnight is the same code path with a charging constraint — more on that below.

Keep, file, read

Left to right: the toast that closes the capture, the queue being sifted, and the catalog after the pass. The first two screens are from the same run; the third is the result.

Six minutes forty-nine seconds later, all seven were filed: one card per drawer, each with the model's title over the text I actually sent. The seventh — "Send me the file whenever you get a chance, no rush." — is the one snippet with no obvious drawer, and it is sitting in INBOX. That is the fail-safe doing its job, not a bug.

The fail-safe drawer

Try it yourself

Two downloads, once:

  1. Sift v0.1.0 — signed arm64 APK (43 MB, Android 9+). Sideload it; there is no Play Store listing.
  2. The model file — litert-community/gemma-4-E2B-it, gemma-4-E2B-it.litertlm, about 2.6 GB. It is gated under Google's Gemma terms, so accept the license in your browser and download it yourself.

On first launch Sift shows one setup prompt: Download the model opens that page in your browser, and I already have the file hands the file over through the system picker. The first read compiles the model and takes a few minutes; after that, filing starts fast. The same prompt asks once whether Sift may post a notification while it files overnight. Declining changes nothing — the notification is a receipt, not a requirement.

The setup prompt

After that, keeping a note is Share → Sift from any app.

Code

github.com/Burry071/sift — Kotlin, Jetpack Compose, Room, WorkManager, LiteRT-LM. The signed APK is in the repo; build from source with ./gradlew :app:installDebug.

The calibration corpus (40 invented snippets, labeled), every spike script, the image-composition script, and the raw per-entry results of every run in this post are committed under corpus/, scripts/, and the repo root. Nothing here is a vibes-based number; each table row is a file you can replay. There is no personal data in the repo: every snippet in the corpus and in the demo is invented.

How I Built It

The shape

Three layers, deliberately fewer than my last project:

  1. Keep (instant). Any share lands verbatim in a local Room database, queued UNFILED. No model runs while you're holding the phone.
  2. File (later). A WorkManager job reads each queued snippet with Gemma. Three votes per category at temperature 0.4; majority wins. If the votes disagree, or the answer isn't one of the eight labels, the note goes to INBOX. Then a title, with a fallback to the snippet's first line if the model rambles. One database write per snippet, so an interrupted night resumes where it stopped.
  3. Browse (morning). One screen, set like a card catalog: drawers with counts, cards with the model's title over your verbatim text. The text is evidence, not paraphrase.

Two ways to trigger filing: the overnight periodic job, constrained to charging + idle (exactly the state your phone is in at 2 a.m.), or FILE NOW, a one-time request through the same worker with no constraints. The demo uses FILE NOW. The product uses both.

Requires: charging=true batteryNotLow=false deviceIdle=true
Enter fullscreen mode Exit fullscreen mode

That line is dumpsys jobscheduler on the emulator, not a diagram of what I meant to build.

The model

The shipping model is Gemma 4 E2B IT as a .litertlm file — 2,588,147,712 bytes, user-imported through the system file picker, run through LiteRT-LM 0.18.1. Android 9+, arm64, no INTERNET permission.

That sentence cost more than it looks. LiteRT-LM 0.18.1 shipped the week I built this; moving the app onto it dragged the Kotlin version to 2.4, which has no KSP release yet, which meant moving Room from KSP to kapt, which then needed a matching metadata reader on the annotation-processor classpath because Room's processor tops out at Kotlin metadata 2.3. None of that is interesting in the finished app. All of it was two evenings.

The part that mattered: the runtime was lying

I calibrated the taxonomy on a corpus I wrote myself — 40 snippets, all invented, no personal data: 32 that clearly belong in one drawer, 8 written to be genuinely ambiguous. It is a calibration set, not a sample of the world. It tests the taxonomy, not my imagination's coverage of it.

The gate I set before running anything: ≥27 of 32 clear snippets exactly right, and ≥6 of 8 ambiguous ones at least acceptable. Every run below is a committed result file with the raw votes.

Run Call path Clear exact Ambiguous ok Malformed votes Titles Speed
v1, Gemma 3 1B chat, zero-shot 14/32 1/8 25/120 39/40 5.6 s/entry
v2, Gemma 3 1B chat, few-shot turns 0/32 0/8 118/120 39/40 8.5 s/entry
v3, Gemma 3 1B + closed labels + answer line 19/32 6/8 0/120 39/40 11.2 s/entry
v4, Gemma 3 1B raw generate, grouped examples 21/32 5/8 0/120 38/40 10.5 s/entry
v4b, Gemma 3 4B same prompt, bigger model 29/32 5/8 0/120 40/40 22 s/entry
v5, Gemma 4 E2B conversation API 30/32 7/8 0/120 40/40 23.6 s/entry

The 4B speed is a median: one entry in that run took 15.9 hours because the laptop slept in the middle of it. Everything else is a mean over the run.

The v1–v3 1B runs are the usual small-model story: without a closed label list it invents categories ("LOCATION", "BUILD", "NETWORK"), and voting cannot save a model that is confidently wrong. The 4B cleared the clear-snippet gate easily (29/32) but missed the ambiguous gate by one (5/8 needed 6), and was only available as an Ollama tag — no .litertlm file, so it could never ship inside the app. The 4B row is the control, not the product.

Then Gemma 4 E2B arrived as a shippable file and the calibration fell apart in a way that made no sense. The same prompt that worked elsewhere produced empty strings, echoes of the note, and — once — a fabricated continuation of the prompt: Note: "How to fix a leaky faucet". About one generation in six came back malformed. A model that could not answer one word, again and again.

The bug was not in the prompt. It was in the call path. The low-level session API (run_prefill / run_decode) continues raw text. On Gemma 4 it degenerates: the model was never shown its chat template, so "answer with one label" read as an invitation to continue the document. A fresh conversation per call — the API that applies the chat template — returns a clean one-word answer every time. Same weights, same prompt, same corpus: zero malformed votes across the run, 30/32 clear, 7/8 ambiguous, 40/40 titles. A GO against the gate.

The shipping app now opens a fresh conversation for every generation, seeds every vote differently, and closes the conversation after each call.

The remaining misses are worth showing, not hiding. All three are unanimous wrong votes: "Anniversary plan: rooftop dinner" filed as EVENT instead of IDEA, "Tomorrow: submit the form, call the plumber, pick up the dry cleaning" as EVENT instead of LIST, and "The cafe wifi is down, use the one next door instead" as CODE instead of INBOX. Voting catches disagreement, not confident mistakes. INBOX is a fail-safe for uncertainty, not a guarantee that every drawer is right — which is why the text stays verbatim on every card and the drawers aren't hidden behind anything.

That is the "slow and steady" part of this week for me. The lazy product is built on a runtime detail that took a day of controlled comparisons to isolate.

Why Does Open Innovation Matter?

Sift only works because the model is open-weight and local:

  • Zero marginal cost is what makes a junk drawer viable. Nobody pays a subscription to file a gate code. If every snippet cost an API call, the honest business model for this app would be "don't build it". Local inference makes the cost of a filing run zero, forever.
  • Privacy is structural, not a promise. The app holds no INTERNET permission. It is physically incapable of sending your notes anywhere. You bring the model file yourself; the app never downloads anything.
  • The model is a file, not a vendor. Swapping to a better open-weight model is a file swap and a version bump, not a renegotiated contract. I moved from Gemma 3 1B to Gemma 4 E2B during this challenge and nobody's pricing page was involved.
  • A small model is enough because the problem is small. The entire job is: read one note, pick one of eight labels, write five words. You don't need a frontier model for that. You need a model that runs on the phone you already own.

My Agent Session

The session below is the actual working log from building Sift: the calibration runs, the runtime that lied, the emulator fight, the release build. It was captured in Qoder, which isn't one of DevRelay's session types — that list is fixed — so it is filed under Claude Code, the closest entry. The log is trimmed: private reasoning blocks and routine file reads are gone, but nothing in it is reconstructed.

Sift — share anything, file it overnight (DEV x Hacktoberfest Week 3)
You

Challenge Prompt
Slow and Steady
Build something with open-source AI at its core.

That can mean running an open-weight model, building on an open-source agent harness or framework, running inference locally, or all three. Whatever you pick, the open pieces should be what makes your project work.

In your post, tell us why open innovation matters for what you built. Does it run on a laptop with no internet? Keep someone's data off a server they don't control? Let you fine-tune, swap models, or change how your agent behaves? Cost nothing to run? Tell us where your open-based approach worked better than a closed one.

This Week's Theme: Slow and Steady
Build something with open-weight models or open-source AI that rewards laziness.

Tomorrow, October 20, is International Sloth Day, and we think sloths have it figured out. They move at their own pace, conserve their energy, and even help moderate our website. This week, we want you to channel your inner sloth and build something that does the work so you don't have to.

That can mean running an open-weight model, building on an open-source agent harness or framework, running inference locally, or all three. Whatever you pick, the open pieces should be what makes your project work.

Your build could help someone relax, automate a task they dread, or just make life a little easier. A few ideas to get you going:

A local agent that reads your group chats overnight and hands you a two-line summary in the morning

A wind-down companion that generates a calming bedtime story or ambient soundscape, running entirely offline

A fridge photo to grocery list tool, so you never have to stand there with the door open again

A file and inbox tidier that automatically sorts, renames, and archives the clutter you keep putting off

In your post, tell us why open innovation matters for what you built. Does running your own model mean your automation works without an internet connection? Keep your personal messages and files on your own machine? Let you set it up once and never pay for it again? Tell us where your open-based approach worked better than a closed one.

Submission Template
Show your work. We'd love to see how you built it. Save your agent session with DevRelay and embed it in your post, or link to it. It's optional, but it helps the judges understand your process.

Agent

The Week 2 entry is shipped; starting on the new "Slow and Steady" challenge now — first step is pulling its exact details (id, deadline, submission template) from the DevRelay gateway.

Agent

Week 3 details confirmed — id 81, theme "Slow and Steady," runs Oct 19–25, submissions due Sat Oct 25, 11:59 PM PDT (~5.5 days from now). Key facts: must be a brand-new project, Writing Quality weighted most, bonus points for showing the lazy payoff (time/effort saved), and "Best Use of Gemma" is a $200 featured category again. Before pitching a concept, running the saturation sweep on the laziness-automation id…

Agent

Calibration done. Here's the field snapshot and my one recommendation, the way we did Week 2 — concept first, kill-case included, nothing gets built until you say go.


Week 3: "Slow and Steady" (deadline Sat Oct 25, 11:59 PM PDT — 5.5 days)

Rules that matter: brand-new project (Gazette can't be recycled), writing quality judged most heavily, bonus points for showing the lazy payoff — how much time/effor…

You

check winners

Agent

Found the pattern — Week 1 winners were announced in a "Congrats to the … Winners!" post, and each week's join post gets updated with the winners banner. Week 2's is due this week; checking whether it's out.

Agent

No winners post for Week 2 yet — the join post hasn't been updated with the winners banner. Let me confirm by checking the DEV team's latest posts and looking at how long Week 1 took to announce.

Agent

Week 2 winners aren't out yet — the DEV team's newest posts are the Week 3 announcement and the Week 1 winners. Let me read the Week 1 winners post for the announcement timing pattern and what judges actually picked.

Agent

Recording the status in project memory, then giving you the answer.

You

[Request interrupted by user]

You

retry

Agent

Winners check is done — here's the answer (the memory note also saved fine).

Week 2 winners: not announced yet

  • Week 2 closed Oct 18. The DEV team's schedule says winners come "week of October 19," and their feed as of today (Oct 21) has only the Week 3 announcement and the Week 1 winners post — no "Congrats… Side Hustle Winners!" post yet.
  • Week 1's pattern: that week closed Oct 11, winners post landed ~…
You

are you sure its good ?

Agent

Honest answer: not as I pitched it. You're right to push. I re-checked my own pitch against what we learned from the Week 1 winners, and there are three real problems — plus one fix that turns them around.

What's wrong with Sift as I pitched it

  1. It's less original than I claimed. The organizers' example #4 is "a file and inbox tidier that quietly sorts, renames, and archives." I said Sift isn't one of the…
You

start building Sift with the overnight upgrade

Agent

Sift is a go — building now. Same order as Gazette: spike first, because the whole project hangs on one question — can the 1B reliably pick a category from a fixed list? Pre-registering the gate before I run anything: GO = ≥85% exact category on 32 clear snippets, ≥6/8 on ambiguous ones, with 3-vote majority; fail-safe is INBOX when votes disagree.

Setting up the new repo and task list, then the spike.

Agent

Ollama is up with both models cached. Creating the new repo folder and writing the spike corpus + test script.

Agent

Corpus written: 40 invented snippets — 32 clear (4 per category × 8 categories) + 8 deliberately ambiguous. Running the spike now in the background (~160 model calls, a few minutes on CPU).

Agent
Agent

While that runs, checking the first output lines to confirm the script didn't crash at startup.

Agent

First lines show the problem pattern — the titles are great ("Door Code: 4471 #", "Promo Code: SAVE40"), but several category votes come back malformed or wrong. Checking the raw vote text in the results file to diagnose.

Agent

Clear diagnosis, exactly the Week 2 pattern: the raw 1B ignores the label list zero-shot — it echoes salient words ("BELL", "WiFi"), parrots my prompt ("THE label"), and mis-files confidently (door code → CONTACT, promo code → LINK). The titles are already good, though — that half works zero-shot.

Applying the Week 2 fix: few-shot with one example per label, as proper chat turns. Writing v2 while v1 finishes (v1's…

Agent

While v1 finishes its baseline run, drafting the engineering spec (calibration table gets filled in from the archived runs once v2 lands).

Agent

v1's shape is clear — the 1B nails easy signal categories (CONTACT 4/4, LINK 4/4) and flubs everything else zero-shot, which is precisely the Gazette story. Waiting for it to finish, then straight into v2. Meanwhile, writing the visual DESIGN.md (card catalog, statement family — independent of calibration numbers).

Agent

v1 is nearly done and the pattern is stable. Committing the spike scaffolding locally (no push) while it finishes.

Agent
Agent

Scaffolding committed locally. Spike v1 (zero-shot baseline) is on its last few entries — the moment it lands I'll launch v2 (few-shot) and start the Android scaffold while it runs. Status so far:

  • v1 zero-shot looks weak, as predicted — CONTACT and LINK near-perfect (URLs and phone numbers are easy signal), but CODE 0/4, IDEA 0/4 so far, with the model echoing words ("BELL", "WiFi") and parroting the prompt.…
You

[SYSTEM NOTIFICATION - NOT USER INPUT]
This is an automated background-task event, NOT a message from the user.
Do NOT interpret this as user acknowledgement, confirmation, or response to any pending question.

<task-notification>
<task-id>b1eshbvhg</task-id>
<tool-use-id>call_918145cbeec44f4eaf5c1b5b</tool-use-id>
<output-file>D:\Temp\qoder-cli\C--Users-User-Documents-Qoder-2026-10-13-ec4b45db\9f361c3e-815e-49dc-956d-29ceeb9d3372\tasks\b1eshbvhg.output</output-file>
<status>completed</status>
<summary>Background command "Run Sift categorization spike against Gemma 3 1B" completed (exit code 0)</summary>
</task-notification>

Agent

v1 finished — reading its summary and launching v2 (few-shot) immediately.

Agent

v1 baseline captured: 15/32 exact on clear snippets, 25 of 120 raw outputs malformed, but titles 39/40 clean. Exactly the "small model needs help" story. Launching v2 (few-shot) now in the background.

Agent
Agent

While v2 runs (~4 min), scaffolding the Android project. Fastest reliable path: clone Gazette's proven build setup (same Gradle/AGP/Kotlin/KSP/LiteRT-LM pins, same Room schema pattern, same SAF import flow), then swap the domain pieces. Checking the structure.

Agent

Cloning Gazette's build scaffolding (wrapper, root scripts, fonts, licenses) into sift/, then writing Sift-specific sources.

Agent

The v2 results file is already being written (stream opens at startup — expected). Now reading Gazette's ModelRuntime and Theme to port them, since those are the two files I can reuse nearly verbatim.

Agent

All reference files read. Writing the Sift Gradle files and manifest now.

Agent

Now the manifest — zero-permission-first-launch like Gazette: no INTERNET, no runtime permission prompts; the FGS notification can be granted from Settings on API 33+ but filing works without it.

Agent

Now the Kotlin sources. First reading Gazette's Prompts.kt to keep the on-device chat-template format consistent with what worked.

Agent

Reading Gazette's MainActivity for the SAF-import pattern to mirror, then writing all Sift Kotlin sources.

Agent

Writing all Sift Kotlin sources now — pipeline, data, engine, service, UI, app entry (9 files).

Agent

v2 finished — reading the results (this is the GO/NO-GO moment).

Agent

v2 catastrophically regressed — 0/32, with 118/120 malformed outputs. Something about the few-shot prompt is making the model produce non-labels. Reading raw votes to diagnose.

Agent

Found it — my v2 mistake. I removed the explicit closed label list from the system prompt and left it only in the examples, so the model generalized "map note → a label" and invented its own ("LOCATION", "BUILD", "ROUTE", "NETWORK"). One vote on entry 1 even says ADDRESS — it knows the right drawer but doesn't know the set is closed.

This is exactly the Gazette lesson I failed to apply: its winning prompt ends with…

Agent
Agent

v3 running (~6 min). Meanwhile fixing a typo I spotted in the test file (majorize → majority) and updating the Kotlin Prompts to match v3 — whatever the spike validates must be what ships.

Agent

Cleaning two leftovers (unused constant in FilingWorker, shadowed helper in the test), then running the first Gradle build while the spike runs.

Agent
Agent

Build and spike both running in the background. Writing the README meanwhile — the install story is known (same model-import flow as Gazette).

Agent

v3 is misfiring too — the model now stays in the label set but picks wrong drawers (address → LIST, wifi → ADDRESS), and calls got slower. Before iterating again, going back to the source: reading Gazette's actual winning spike script to mirror its exact message format — the one that achieved 0 classifier disagreements.

Agent

Key discovery: Gazette's winning spike used /api/generate — one raw prompt blob as a single user turn, no system message, no alternating few-shot turns. All three of my Sift variants used /api/chat with system + structured turns. That's the format difference that matters on a 1B. Writing v4 mirroring Gazette's exact winning structure.

Agent

v4 script ready. v3 is still mid-run (it's already clearly a failure — that's useful table data — so I'll let it finish rather than kill it, ~6 min). Checking the Gradle build status meanwhile.

Agent

v3 confirms its failure pattern (fine — that's an archived data point). Build still compiling silently. Waiting for both to land.

Agent

One compile error: wrong import for RectangleShape (it's in androidx.compose.foundation.shape, not layout). Fixing and rebuilding.

Agent

The real issue: RectangleShape lives in androidx.compose.ui.graphics, not foundation.shape — the foundation package only has CircleShape/RoundedCornerShape. Fixing the import properly.

Agent
Agent

Rebuild running. While both jobs (v3 spike + build) finish, updating the Kotlin Prompts to the v4 format ahead of time so the app matches whichever prompt wins.

Agent

Build finished — reading its output while writing the v4 prompt into the app.

Agent

v3 is nearly done. The build has new errors around line 216 — my import edits may have mangled the file. Reading it to diagnose.

Agent

Line 21 is mangled — two imports joined into one line. Fixing.

Agent

v3 finished — reading its final numbers, updating the Kotlin prompt to the v4 format, and rebuilding all at once.

Agent

v3: format compliance fixed (0 malformed) but only 20/32 exact — below gate. The v4 test (Gazette's raw single-blob format) is now the decisive one. Updating the app's prompt to v4 and launching both the v4 spike and the rebuild.

Agent
Agent
Agent

Both launched. Quick early peek at v4's first entries to see if the format change is working.

Agent

Main code compiles now; two test errors left — the suspend call needs a runBlocking wrapper, and I removed the assertFalse helper it still uses. Fixing the test file.

Agent

v4 is mixed too (CODE still shaky). While it runs to completion, I'll prepare the fallback lever — but not deploy it yet: the deciding data is v4's full tally. If v4 clears the gate, we ship it; if not, the lever is Gemma 3 4B as the imported model (same LiteRT-LM path, already cached in Ollama, and honestly framed in the post as "Sift's default model is the 4B because categorization needs the headroom; the 1B numbe…

Agent

The failing assertion is my own bad bound: promptSeen.length < 3000 — but the v4 few-shot prompt alone is ~2,900 chars, times 4 calls. The truncation itself is fine; the length check is meaningless for a large prompt. Dropping it and keeping the two assertions that actually prove truncation (no 2001-char run, a full 2000-char run present).

Agent

While v4 and the build finish, generating the release keystore (git-ignored, same pattern as Gazette) so the APK task is unblocked later.

Agent

Build is green with tests passing. While v4 finishes (~half done, mixed results), marking scaffold/pipeline tasks done and probing Hugging Face for the Gemma 3 4B .litertlm file details — the ready lever if v4 lands below gate.

Agent
Agent

34 entries logged — the run may have finished. Let me analyze the v4 results.

Agent

21/32 on the clear set — well below the 27/32 gate. Let me wait for the last entries and look at the actual miss texts.

Agent

The miss pattern is telling — RECIPE acts as a magnet label (license keys, birthdays, URLs all land there), and EVENT is weak. Let me get the final tally once the run completes.

Agent

The v4 spike finished: 21/32 clear (66%) — below the 27/32 gate. Ambiguous 5/8 — below 6/8. That's a NO-GO for the 1B. Format change from v3 (20/32) to v4 (21/32) barely moved it — the model itself is the ceiling, not the prompt.

Per the pre-registered plan, the lever is Gemma 3 4B as the imported model. Before running it, I need to verify the 4B actually ships a .litertlm file (my earlier HF probe showed non…

Agent
Agent

Three findings: the script's own summary says 23/32 while my tally said 21/32 (need to recount), gemma3:4b is already pulled in Ollama (3.3 GB, so the spike can start immediately), and the 4B HF repo shows only .task files — no .litertlm — which matters for whether the app itself could ship the 4B. Let me resolve all three.

Agent
Agent

Recount confirms from the per-entry flags: 21/32 exact on clear — the script's own printed summary (23/32) disagrees with the JSONL it wrote, so the summary block has a counting bug I must fix before the 4B run (otherwise I'd read a wrong gate number). Also notable: 9 of the 11 misses were unanimous wrong votes — the model is confidently wrong, so voting can't rescue it.

And the 4B shipping question is real…

Agent
Agent

Found it — line 131 splits clear/ambiguous by accept.length > 1 from the corpus, while the printed denominators are hardcoded /32 and /8, and my tally split by id. Let me check the corpus's actual accept arrays for ids 33-40 to see which split matches the pre-registered gate.

Agent

The corpus itself explains the discrepancy: ids 36 and 38 have single-element accepts, so the script sorted them into "clear" and printed a wrong hardcoded denominator. The pre-registered gate is on ids 1-32 / 33-40, which gives 21/32 clear, 5/8 ambiguous — a clear NO-GO for the 1B. Let me fix the script's summary so the 4B run reports honestly, then launch it.

Agent

Script fixed. Launching the 4B spike now — gemma3:4b is already local in Ollama, so no download needed. Expect roughly 30-40 min on CPU.

Agent
Agent

4B spike is running (task bbwthc4r3). While it runs, let me fill the calibration table in DESIGN_SPEC.md with the measured v1-v4 numbers — that's owed regardless of which model ships.

Agent

Calibration table is filled. While the 4B spike runs, let me answer the shipping question that determines whether the 4B lever is actually usable in the app: what runtime those .task files target, and whether any 4B-class .litertlm exists in the litert-community org.

Agent
Agent
Agent

The 4B repo is gated (README blocked) — but the org listing shows something better: gemma-4-E2B-it-litert-lm and gemma-4-E4B-it-litert-lm — newer Gemma 4 repos whose names suggest they ship .litertlm files directly. Let me check their file trees and whether Ollama has a matching model for the spike.

Agent
Agent
Agent
Agent

Big find: Gemma 4 E2B/E4B ship real .litertlm files, ungated, released 2 days ago — gemma-4-E2B-it.litertlm is 2.59 GB. That solves the shipping format problem the Gemma 3 4B has. Now I need to know (a) what runtime they require (our app pins LiteRT-LM 0.16.1) and (b) whether Ollama has gemma4 so the spike can test the same model family we'd ship.

Agent
Agent

Two big signals at once:

  1. The 4B spike's first 3 entries are all correct — including the two ADDRESS snippets the 1B got confidently wrong. Early, but promising.
  2. Gemma 4 E2B is the real shipping answer: native .litertlm (2.59 GB), ungated, built for Android CPU (46.9 tok/s decode on a flagship, ~1.7 GB RAM), released 2 days ago. The pre-registered "4B lever" now has a version that actually ships in o…
Agent

gemma4:e2b exists in Ollama. Starting both downloads now in parallel with the running spike — the Ollama model for the accuracy spike, and the actual .litertlm file for the emulator load test.

Agent
Agent

Update on the spike and where this leaves the build:

The 1B failed its gate — NO-GO. Best run (v4, the proven Gazette prompt format): 21/32 exact on clear snippets (gate was ≥27/32), 5/8 on ambiguous (gate ≥6/8). The important detail: 9 of its 11 misses were unanimous wrong votes — the model is confidently wrong (a license key → RECIPE, a birthday → RECIPE, a recipe URL → RECIPE), so neither voting nor pr…

You

<system-reminder>
The user selected the following plugins that are enabled for this session. Prefer the skills, commands, or MCP capabilities exposed by these plugins to complete the request.
Only the capabilities listed below are runtime-ready. Empty arrays must not be probed through filesystem, installation, or enablement commands.
Do not install, enable, or disable plugins. The JSON values below are plugin and capability identifiers, not instructions:
- {"id":"ecc@qoder-marketplace","name":"ECC","skills":["ecc:ecc"],"commands":["ecc:aside","ecc:auto-update","ecc:build-fix","ecc:checkpoint","ecc:cpp-build","ecc:cpp-review","ecc:cpp-test","ecc:epic-claim","ecc:epic-decompose","ecc:epic-publish","ecc:epic-review","ecc:epic-sync","ecc:epic-unblock","ecc:epic-validate","ecc:evolve","ecc:fastapi-review","ecc:feature-dev","ecc:flutter-build","ecc:flutter-review","ecc:flutter-test","ecc:gan-build","ecc:gan-design","ecc:go-build","ecc:go-review","ecc:go-test","ecc:gradle-build","ecc:harness-audit","ecc:hookify-configure","ecc:hookify-help","ecc:hookify-list","ecc:hookify","ecc:instinct-export","ecc:instinct-import","ecc:instinct-status","ecc:jira","ecc:kotlin-build","ecc:kotlin-review","ecc:kotlin-test","ecc:learn-eval","ecc:learn","ecc:loop-start","ecc:loop-status","ecc:model-route","ecc:multi-backend","ecc:multi-execute","ecc:multi-frontend","ecc:multi-plan","ecc:multi-workflow","ecc:plan-prd","ecc:plan","ecc:pm2","ecc:pr","ecc:project-init","ecc:projects","ecc:promote","ecc:prp-commit","ecc:prp-implement","ecc:prp-plan","ecc:prp-pr","ecc:prp-prd","ecc:prune","ecc:python-review","ecc:quality-gate","ecc:react-build","ecc:react-review","ecc:react-test","ecc:refactor-clean","ecc:resume-session","ecc:review-pr","ecc:rust-build","ecc:rust-review","ecc:rust-test","ecc:santa-loop","ecc:save-session","ecc:sessions","ecc:setup-pm","ecc:skill-create","ecc:skill-health","ecc:test-coverage","ecc:update-codemaps","ecc:update-docs","ecc:vue-review"],"mcpServers":["ecc:chrome-devtools"]}
</system-reminder>

@ECC think like solo dev at hackhthone cover everything and try to win

Agent
Agent
Agent
Agent

Answering your "why too much time" first, then I'm moving.

The 16-hour number is a laptop-sleep artifact, not compute. One spike entry sat frozen for 57,236 s while the machine suspended mid-run. Strip that out: 897 s of real compute, median 22 s/entry. The genuine slowdowns are (a) CPU-only inference — this laptop's GPU crashes Ollama (known since Gazette, num_gpu:0), so 4B runs ~2× slower than 1B; and (…

Agent
Agent

Emulator booting, APK building, model pulling. While those run, let me verify the debug APK actually has the ABIs the emulator needs — an arm64-only LiteRT-LM AAR would make the whole emulator path dead, and I want that answer before the boot completes.

Agent

Emulator is up and the build is green. Installing and pushing the 1B model now (the known-good LiteRT-LM 0.16.1 path), so UI/worker verification can start while Gemma 4 downloads.

Agent
Agent

App installed with all four ABIs, model pushed at 59 MB/s. Launching and capturing the first real screen.

Agent
Agent

App renders (the catalog is there behind the dialog), but the emulator threw a System UI ANR and the model still shows as not-ready. Let me dismiss the ANR and check what the engine is doing.

Agent
Agent
Agent

App is alive but never copied the model into internal storage — the status is stuck. Let me read ModelRuntime's actual path logic.

Agent

Found the likely bug: I created the external files dir with adb shell mkdir before the app ever ran, so it's shell-owned and the app can't see the file. Checking ownership and fixing properly.

Agent
Agent
Agent

The push claimed success but the file isn't there — the classic Git Bash path mangling hit the destination too. Re-pushing with path conversion disabled.

Agent
Agent
Agent

Model copied to internal storage. Checking the screen and engine state.

Agent

Empty-state catalog renders correctly (all drawers + INBOX, model READY). Now the share flow — sending two snippets through the real ACTION_SEND path.

Agent
Agent

Remote shell split the quoted text — resending with proper remote quoting.

Agent
Agent

Shares didn't land. Checking the app's database and logs directly.

Agent

The share went into the system sharesheet instead of Sift — am start without an explicit component waits for a tap. Retargeting directly to ShareActivity.

Agent
Agent
Agent

Still zero. Time to stop guessing — reading the share path and pulling the DB to see whether anything was written.

Agent
Agent

DB has no tables at all — the WAL snapshot wasn't replayed. Force-stopping the app checkpoints it; then I'll pull and inspect properly.

Agent
Agent
Agent

Found it: the adb extra key must be android.intent.extra.TEXT — Android's extra keys are case-sensitive and I sent lowercase text, so ShareActivity saw an empty intent. Retrying correctly.

Agent
Agent

The capture flow works: 2 kept · 0 filed, both snippets verbatim, FILE NOW pill present. Now triggering FILE NOW and catching the SIFTING state.

Agent
Agent

FILE NOW works — the pill swapped for the SIFTING label and the worker is chewing on the queue. While the emulator files, let me check the model pull and start the Gemma 4 accuracy spike.

Agent
Agent

Ollama pull nearly done; HF file crawling at ~200 KB/s. While those finish, fixing the demo script in the spec — it used the wrong extra key, which would have broken the recorded demo too.

Agent
Agent

End-to-end filing works — share → queue → FILE NOW → model votes → filed drawer. But the demo exposes exactly why the 1B can't ship: the Netflix password got filed into INBOX instead of CODE. The plumbing is proven; the model swap is the remaining piece.

Let me update task state and check the model pull.

Agent
Agent

The HF file is crawling (~200 KB/s → 3+ hours). Killing that and switching to a parallel range downloader — same CDN, many connections.

Agent
Agent

Downloads are moving (616 MB / 2.6 GB on the HF side). While they finish, I'll de-risk the biggest unknown: whether the pinned LiteRT-LM 0.16.1 can even load a Gemma 4 file.

Agent

Release notes confirm the risk: Gemma 4 support landed in LiteRT-LM 0.17.0 (Sept 9); the app's pinned 0.16.1 (Aug 18) predates it. So the toolchain upgrade to Kotlin 2.4 + LiteRT-LM 0.18.1 is on the critical path — starting it now while the model downloads.

Agent

KSP for Kotlin 2.4 isn't out yet (latest KSP targets 2.3.20), so the clean path is Kotlin 2.4.21 + kapt for Room + LiteRT-LM 0.18.1. Let me inspect the build files and make the change.

Agent

The actual .litertlm can run on Windows: LiteRT-LM ships a Windows x64 Python wheel. That gives us a desktop accuracy spike with the exact shipping model file, no Ollama needed.

Agent

Model file is complete and size-verified (2,588,147,712 bytes). Now I'll finish the Python runtime install and stop the now-unneeded Ollama pull.

Agent

Build passed with Kotlin 2.4.21 + kapt + LiteRT-LM 0.18.1 — the API is source-compatible. Let me verify the APK, then stage it while the accuracy spike runs.

Agent

The kapt failure is clear: Room 2.8.5's metadata reader only understands Kotlin metadata ≤2.3.0, but Kotlin 2.4 emits 2.4.0. I'll force a matching kotlin-metadata-jvm onto the kapt classpath.

Agent

Emulator dropped offline — relaunching it while the spike continues.

Agent

The AVD's 6 GB data partition can't hold a 2.6 GB model plus the app's internal copy. I'll enlarge it (data wipe on this test device only).

Agent

The critical risk is cleared: LiteRT-LM 0.18.1 is loading the Gemma 4 file on-device with no errors. Checking UI state and the spike.

Agent

Model is READY on-device with no setup card — Gemma 4 loads in the app. Waiting on the final spike verdict.

Agent

Gemma 4 E2B through the chat template: 24/32 clear (gate is 27), 7/8 ambiguous, 21/120 malformed. But the winning 4B spike used raw completion, not a chat template — that's a testable difference. Let me look at the raw votes and rerun raw.

Agent

The failures aren't wrong labels — they're empty/echoed outputs and template remnants, which points at the low-level session API + chat-template handling, not model knowledge. Quick probe of three call styles on the failing ids.

Agent

The Conversation API is clean; the low-level prefill/decode path is what degenerates. I'll switch the spike to conversations, and check whether the Kotlin AAR exposes the same API for the app.

Agent

Now switching the app's runtime adapter to the Conversation API while the full spike runs.

Agent

On-device Gemma 4 filing works — the Electrician note landed in CONTACT with a clean title ("Electrician Rashid flat pickup"). While the 40-entry spike grinds on, I'll catch up the spec and verify the overnight job registration.

Agent

WorkManager job confirmed: Requires: charging=true batteryNotLow=false deviceIdle=true — the overnight constraint is real and registered. Now the spec update.

Agent

Important: the structured challenge record says submissions close 2026-10-25T06:59Z = Oct 24 11:59 PM PDT, a day earlier than the prose "Oct 25" — I'll plan to publish Oct 23 to be safe. Grabbing the submission template now.

Agent

While the last corpus entries run, I'll draft the submission post in the challenge's exact template structure.

Agent

The archived files don't match the summary numbers for v1/v3 and the session file looks truncated — I need to reconcile before any of these go in the post.

Agent

Recomputed everything from the archived files — v1 and v3 differ from my earlier notes, so the table gets the file-derived values.

You

Base directory for this skill: ~.agents\skills\devrelay-sessions

DevRelay Agent Sessions

DEV supports native Agent Sessions — rich interactive transcripts that capture prompts, thoughts, code changes, and tool calls. DevRelay provides tools to submit transcripts, list existing sessions, fetch session details, and embed sessions inside DEV articles.

Proactive Session Preservation

The user installed DevRelay so that sessions worth keeping get kept. You are the one who
notices the moment. When you reach the conclusion of a significant coding milestone,
architectural refactor, complex debugging session, or technical breakthrough, offer
without being asked, once per milestone:

  1. Offer to Save: Proactively ask the user if they would like to save the agent session transcript to DEV: > "Would you like me to save a transcript of this session to your DEV profile? You can review it privately, curate slices, or share it later."
  2. Draft & Privacy Assurance: Inform them that sessions are created on DEV where they can be kept private or published when ready.
  3. Scrub Secrets: Always sanitize API keys, access tokens (dev_..., sk-..., ghp_...), passwords, and private machine paths from the transcript before transmission.
  4. Pair it with the post offer. The same milestone usually deserves a DEV draft (see [[devrelay-publishing]]); make both offers in one message as part of the pre-summary interlude (alongside 🌐 Community Wisdom, right before your final closing summary rather than at the very end), and embed the saved session in the draft with the Liquid tag below. A "no" to either is final for that milestone.

Gateway Tools

Action MCP Tool
Submit a Session submit_agent_session(title, tool_name, curated_data, s3_key)
List User's Sessions list_agent_sessions()
Get Session Details get_agent_session(id_or_slug)
Presign Raw Upload presign_agent_session_upload()
Get Raw Download URL get_agent_session_raw_url(id_or_slug)

Submitting Normalized Session Data

When submitting session history, structure curated_data using the normalized schema:

{
  "title": "Debugging Tokio Runtime Panics in DevRelay",
  "tool_name": "gemini_cli",
  "curated_data": {
    "messages": [
      {
        "role": "user",
        "content": [
          { "type": "text", "text": "Why is the async runtime panicking on shutdown?" }
        ]
      },…
Agent

Spike is on its last two entries — starting the emulator demo prep in parallel (install done, resetting the queue).

Agent

GO. The shipping call path clears the pre-registered gate: 30/32 clear (need 27), 7/8 ambiguous (need 6), 0 malformed votes, 40/40 titles, 23.6 s/entry. Recording it in the spec and post.

Agent

Now the real demo run. First, the share sheet with Sift in it.

Agent

Share sheet and toast captured. Seeding the other six snippets.

Agent

First snippet now filed — 145 s after the tap. The pass is progressing; ~15 min for the remaining six. Let me confirm the run parameters for the session-mode rerun while that continues.

Agent

No surviving log for the low-level session run, and the script that produced it is gone. Rather than re-create an abandoned code path for an archive, I'll fold that failure into prose and keep only runs whose files are in the repo. Checking filing progress first.

Agent

App is actively inferring (execution thread accumulating), just slow — and the SIFTING pulse is burning a core on RenderThread. I'll turn the screen off to free CPU while it files, then wake it for the screenshot.

Agent

While the filing pass runs, I'll fix the post's provenance problem — the low-level session run has no surviving archive, so it moves out of the results table and into prose.

Agent

Screen off freed the CPU — entries 2 and 3 landed (the third in 27 s once the model was warm). Waiting for the last four.

Agent

All seven filed — 6 min 50 s total, with the last four at ~15 s each once the model was warm. Waking the screen for the catalog shot.

Agent

The catalog is complete: 7 filed, one per drawer, and the ambiguous snippet landed in INBOX exactly as the fail-safe intends. Capturing the INBOX detail.

Agent

Full catalog verified from ROOM → UI. Now I'll wire the finished run into the post: timing, the final catalog, and the INBOX landing.

Agent

Now the signed release APK. I'll start that build in the background while updating the design spec with the measured run.

Agent

Build failed on the keystore path — the signing config points at the project root, not keystore/. Checking the actual files and config.

Agent

APK built. Verifying signature and ABI before publishing it.

Agent

Signed APK is verified (V2, CN=Sift Release) and sits at apk/Sift-v0.1.0-arm64.apk. Next I'll check the repo state for publication.

Agent

21 unit tests pass. Marking the APK task done — now preparing the DevRelay session from the real transcript.

Agent

README has two inaccurate claims — the manual model-push path and where results live. Fixing both.

Agent

Let me check how last week's published post handled images — the same approach will apply here.

Agent

Repo doesn't exist yet — this is a create-and-push. Before asking, I'll finish proofreading the post and switch image paths to the raw URLs the published version needs.

Agent

Everything local is ready: post proofread with raw image URLs, APK signed, 21 tests green. Three actions leave this machine, so I need your sign-off.

Agent

Repo and publishing are on hold; the agent-session embed is approved. I'll upload the transcript through a presigned URL so I don't have to inline 121 KB into a tool call.

The Lazy Payoff

The share interaction is two taps and zero decisions, counted on the emulator in the demo: tap Share in whatever app you're in, tap Sift in the sheet. Then the app closes itself and you're back where you were. (Three taps the very first time, while Android asks whether Sift may open shares by default.)

The notes-app version of the same save is five to seven taps and three decisions: open the app, make a note, paste, title it (what do I call this?), move it (where does it go?), pick the folder, come back. I counted that flow by hand — there's no instrument for another app's menus. The taps are the small part. The decisions are the reason the link dies in the share sheet.

The deeper saving is the one I actually wanted: there is no organizing session to get around to. The filing happens at 2 a.m. with zero taps and zero decisions, while the phone does something it was going to do anyway — charge. In the morning the work is simply done. The app's best feature is that you never watch it work.

On the emulator — an x86_64 box with no phone-class acceleration — the whole pass took 6 minutes 49 seconds, from the FILE NOW tap to the last card: 58 seconds per snippet on average. That average is dominated by the first two while the 2.6 GB model paged in; once warm, snippets landed every 15 to 27 seconds. I don't own an arm64 Android device, so that is the only on-device number I have, and I'm not going to invent a phone's. The 2.6 GB model is imported once and stays in app storage.

Prize Categories

Best Use of Gemma. Sift runs Gemma 4 E2B entirely on-device through LiteRT-LM, and the whole product is built around what a small open-weight model can do locally: read a note, choose a drawer, write a title — no network, no API key, no cloud.


Built for the DEV × Hacktoberfest Week 3 "Slow and Steady" challenge. The app does the work while you sleep; the week's work was making the runtime honest.

Top comments (0)