<?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: Hyunsik Park</title>
    <description>The latest articles on DEV Community by Hyunsik Park (@hyunsik_park_7c24c2e4f4fc).</description>
    <link>https://dev.to/hyunsik_park_7c24c2e4f4fc</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%2F4158944%2F393113eb-70cd-4218-8847-b9bb0bf14eb1.png</url>
      <title>DEV Community: Hyunsik Park</title>
      <link>https://dev.to/hyunsik_park_7c24c2e4f4fc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hyunsik_park_7c24c2e4f4fc"/>
    <language>en</language>
    <item>
      <title>Three fewer graph passes, 12% more time</title>
      <dc:creator>Hyunsik Park</dc:creator>
      <pubDate>Mon, 02 Nov 2026 04:14:12 +0000</pubDate>
      <link>https://dev.to/hyunsik_park_7c24c2e4f4fc/three-fewer-graph-passes-12-more-time-5fn3</link>
      <guid>https://dev.to/hyunsik_park_7c24c2e4f4fc/three-fewer-graph-passes-12-more-time-5fn3</guid>
      <description>&lt;p&gt;&lt;em&gt;Bounded Accord’s Speed Demon engineering notes, written on October 31, 2026 and updated after our last push on November 2. Live-board results are practice scores, separate from final judging.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The last graph-construction change in this account removed three complete reads of a five-million-edge array. It preserved the order of every adjacency list and left the shortest-path search unchanged. It passed the correctness checks. The measured program took about 12% longer.&lt;/p&gt;

&lt;p&gt;That result came from our entry in &lt;a href="https://speeddemon.dev/" rel="noopener noreferrer"&gt;Speed Demon&lt;/a&gt;, organised by Hackathon Raptors. Bounded Accord implements four tasks in Rust: log aggregation, Boolean and phrase search, a CSV join, and directed shortest paths. The &lt;a href="https://github.com/HyunsikParker/speed-demon-2026" rel="noopener noreferrer"&gt;entry repository&lt;/a&gt; contains the implementations and their performance records.&lt;/p&gt;

&lt;p&gt;The comparisons below cover rejected changes, an officially scored storage improvement, and an input-partitioning change that reached the live board on the last day.&lt;/p&gt;

&lt;h2&gt;
  
  
  One graph pass can carry more expensive work
&lt;/h2&gt;

&lt;p&gt;Track D stores the graph in two compressed sparse row structures: one for outgoing edges and one for incoming edges. Each row’s edges are contiguous. A forward search from the source and a reverse search from the target can then read their own adjacency arrays without searching the original edge list.&lt;/p&gt;

&lt;p&gt;The accepted constructor first parses triples of source, destination and weight. For each direction, it counts degrees, computes row offsets, allocates edge storage, and scans the triples again to fill that storage. There are four full triple scans after parsing.&lt;/p&gt;

&lt;p&gt;The candidate counts both degrees while parsing, then fills both adjacency arrays in one shared triple scan. Each edge still appears in its original relative order within its row. Parallel edges, exact duplicates, self-loops and zero weights remain intact. The input byte buffer is released before the edge arrays are allocated in both versions.&lt;/p&gt;

&lt;p&gt;The following comparison includes reading, construction, all queries and writing the output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;th&gt;Accepted constructor&lt;/th&gt;
&lt;th&gt;Fused constructor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pinned-image process time&lt;/td&gt;
&lt;td&gt;0.858853 s&lt;/td&gt;
&lt;td&gt;0.960344 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak native process RSS&lt;/td&gt;
&lt;td&gt;192,388 KiB&lt;/td&gt;
&lt;td&gt;185,220 KiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nonblank, non-comment source lines&lt;/td&gt;
&lt;td&gt;281&lt;/td&gt;
&lt;td&gt;286&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Times are means of the middle three of five runs per version, after fixed warm-ups. Baseline and candidate runs were interleaved on the same Intel Core i5-12500H machine running Ubuntu 24.04 under WSL2. The images were pinned, six workers were used, and the published child timer excluded container startup. RSS came from separate native runs; it is not the official container-memory measurement.&lt;/p&gt;

&lt;p&gt;The time ratio, baseline divided by candidate, was 0.894317. Memory fell by about 3.7%. Both timing spread checks passed, but the candidate missed our previously fixed efficiency requirements. We restored the accepted source and executable and checked the full output hash again.&lt;/p&gt;

&lt;p&gt;The comparison establishes that this implementation was slower in this environment. It does not identify a cache-miss rate, a memory-bandwidth limit, or the cost of each added write. Counting loop passes supplied a hypothesis; it did not measure the cost of the resulting access pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  An exact small-key representation did pay
&lt;/h2&gt;

&lt;p&gt;Track C joins CSV records to countries and counts distinct names per country. Splitting records at every newline would be wrong: a quoted field can contain a newline. In the officially scored pipeline, the producer carries quote parity across input blocks and dispatches only complete logical records. Six workers decode fields and accumulate numeric totals.&lt;/p&gt;

&lt;p&gt;Distinctness spans all workers. The accepted design groups requests into 64 mutex-protected shards. Under a shard’s lock, the first occurrence of an exact &lt;code&gt;(name, country)&lt;/code&gt; pair sets a membership bit and increments one worker’s count. Summing those counts is valid because the first-pair decision is shared.&lt;/p&gt;

&lt;p&gt;The useful storage change was deliberately narrow. When there are at most 256 country groups, including the unmatched group, names of at most 16 bytes fit in two words plus a length. Four words hold the country-membership bits. The key contains every name byte; it is not a fingerprint used as identity. Length distinguishes a short name from a longer zero-padded value. Longer names and larger country tables keep the full-byte fallback.&lt;/p&gt;

&lt;p&gt;Against the preceding accepted pipeline, this change reduced pinned-image time from 0.186579 to 0.152485 seconds and native peak RSS from 24,060 to 19,828 KiB. It passed its fixed adoption requirements. Its subsequent official run, &lt;code&gt;run-52f0819fc621&lt;/code&gt;, recorded 55.455380 machine points, up from the previous best of 51.737524.&lt;/p&gt;

&lt;p&gt;The tests targeted the representation’s boundaries: every inline-key byte position, lengths around 16 and 17 bytes, zero bytes, country-bit boundaries, tables through 513 countries, concurrent first-pair updates and reused scratch storage. Fast-path checks alone would have missed the fallback’s obligations.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small time saving can lose the combined comparison
&lt;/h2&gt;

&lt;p&gt;A later Track C candidate scanned each complete record for outside-quote commas and quote counts. That metadata let it derive decoded note lengths without visiting every note byte in the decoder. Unescaped names borrowed their input bytes; escaped names used scratch storage. Record framing and membership storage stayed unchanged.&lt;/p&gt;

&lt;p&gt;It was correct across the tested cases, including split vector blocks, escaped quotes, embedded newlines, Unicode, BOM bytes and a two-megabyte note. It also reduced measured process time slightly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;th&gt;Accepted parser&lt;/th&gt;
&lt;th&gt;Structural-metadata parser&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pinned-image process time&lt;/td&gt;
&lt;td&gt;0.150447 s&lt;/td&gt;
&lt;td&gt;0.147206 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak native process RSS&lt;/td&gt;
&lt;td&gt;20,060 KiB&lt;/td&gt;
&lt;td&gt;20,016 KiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nonblank, non-comment source lines&lt;/td&gt;
&lt;td&gt;393&lt;/td&gt;
&lt;td&gt;458&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The speed ratio was 1.022018, below our fixed 1.18 adoption requirement. Both timing spread checks passed. The source increase also mattered: Speed Demon’s coefficient charges for source size as well as time, memory and dependencies. Using our local RSS and line-count proxies gave a coefficient ratio of 0.988437, below our required 1.05. These counts differ from the host’s formatted-source and container-memory axes, so the ratio is an estimate, not a predicted official score.&lt;/p&gt;

&lt;p&gt;We rejected this implementation before submission. Its shorter decoding path did not produce enough whole-program benefit to cover the measured trade-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding five boundaries without listing every record
&lt;/h2&gt;

&lt;p&gt;A later input-partitioning change reached the local adoption threshold. The first version mapped the immutable CSV file read-only and divided it into six complete-record ranges. Workers borrowed those ranges directly, eliminating the producer’s owned buffers and channels. Its partitioner still scanned the entire input sequentially, collecting record-end offsets to choose just five internal cuts. That version saved memory but missed the time requirement.&lt;/p&gt;

&lt;p&gt;The next version computes quote parity in six parallel byte ranges. A prefix XOR supplies the quote state at each proposed cut; a forward scan then finds the next newline outside a quoted field. An escaped quote contributes two quote bytes, so even a cut between them receives the correct starting state. Long quoted records may send several cuts to the same boundary, producing valid empty ranges. After partitioning finishes, six parsing workers borrow the resulting slices. The parser and shared membership tables stay unchanged.&lt;/p&gt;

&lt;p&gt;This comparison uses a new fixed batch against the previously accepted producer pipeline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;th&gt;Accepted producer pipeline&lt;/th&gt;
&lt;th&gt;Parallel-parity partitioning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pinned-image process time&lt;/td&gt;
&lt;td&gt;0.149709 s&lt;/td&gt;
&lt;td&gt;0.124672 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container cgroup peak memory&lt;/td&gt;
&lt;td&gt;20,582,400 B&lt;/td&gt;
&lt;td&gt;18,612,224 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source-line proxy&lt;/td&gt;
&lt;td&gt;409&lt;/td&gt;
&lt;td&gt;491&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The time reduction was 16.7%, and the measured memory reduction was 9.6%. The local coefficient ratio was 1.087865, above the fixed 1.05 requirement. That calculation uses time, memory, source size and direct runtime dependencies; image size is a separate build limit. Both implementations had a dependency-axis value of one.&lt;/p&gt;

&lt;p&gt;Memory was read from the Docker cgroup with host data prewarmed and a common trusted holder keeping the cgroup available after the timer exited. This measures the container’s recorded peak, but it does not reproduce the host’s parent harness or isolation. The source count is also a local proxy. The baseline had one slower run; the predeclared middle-three mean excluded the fastest and slowest of five runs from each version, with all raw values retained.&lt;/p&gt;

&lt;p&gt;Eleven Rust tests, seventeen validation groups and all fourteen image outputs passed. The checks included quote pairs split at every byte position, long records, Unicode, BOM handling and the final record without a newline. The source was frozen before fresh seeds 137 and 138 and reserved seed 139 were generated.&lt;/p&gt;

&lt;p&gt;A separate diagnostic sampled the unchanged candidate twice. It captured 620 and 621 CPU events, with text decoding accounting for about 20% in each capture. The earlier producer pipeline also placed text decoding near 21%. These are exclusive CPU sample locations, not elapsed-time shares; they do not establish that a previously rejected parser change should be tried again.&lt;/p&gt;

&lt;p&gt;The change's first live-board run, &lt;code&gt;run-e08fc96bfae2&lt;/code&gt;, recorded 57.147738 machine points; the previous best was 55.455380. The board measured 0.160190 seconds instead of 0.187834, and 17,788,928 bytes of peak memory instead of 20,283,392. Formatted source grew from 401 to 483 lines. On the board the coefficient rose by a factor of 1.081, close to the local estimate of 1.088, although the two use different time, memory and source-size measurements.&lt;/p&gt;

&lt;h2&gt;
  
  
  The local winner that lost on the board
&lt;/h2&gt;

&lt;p&gt;An earlier Track D experiment reordered vertices with breadth-first numbering. Its final local version remapped queries early enough to release the permutation before constructing the final adjacency arrays. The fixed native comparison improved from 0.849322 to 0.705578 seconds: about 16.9% less elapsed time. The RSS ratio was 0.997095, and the local coefficient proxy passed its gate.&lt;/p&gt;

&lt;p&gt;The official trial was valid but scored 49.524627 machine points. The retained best was 50.424918. We restored the prior source.&lt;/p&gt;

&lt;p&gt;Those official values are separate historical runs, not an interleaved experiment isolating the numbering change. The board observation was enough to reject that trial as an improvement over the retained best; it was not enough to explain the reversal. A later local diagnostic compared native and container execution contexts and still did not establish the cause.&lt;/p&gt;

&lt;p&gt;This also changed how we described unchanged-source results. A repository push can run tracks whose source did not change. Our best recorded A and D values came from such runs. They belong in the measurement record, but their variation cannot be credited to a new algorithm. On the final day, the unchanged Track A source took 0.189345 seconds in one run and 0.146796 in the next, against its best of 0.130583.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correctness evidence has a separate job
&lt;/h2&gt;

&lt;p&gt;For the fused graph candidate, seven Rust property tests passed. One checked that parsing preserved the ordered rows in both directions. Another tested every directed three-node graph whose nine possible edges were absent, zero-weight or unit-weight: 19,683 graphs, with every source-target pair checked against Bellman–Ford.&lt;/p&gt;

&lt;p&gt;The wider checks covered 117 adversarial and random graphs, repeated queries, large path sums, zero cycles and directed reachability. Seven host examples ran with one, two, six and twelve workers. Fresh tiny seeds 127 and 128 passed before reserved seed 129, with the source frozen. Published sample/full hashes and all fourteen measured or warm-up image outputs matched.&lt;/p&gt;

&lt;p&gt;These checks made a performance comparison meaningful by ruling out the tested wrong-answer cases. They do not prove every possible input correct or turn a slower result into a useful optimisation. The graph invariants still have to hold: path sums remain 64-bit, reverse edges retain their own weights, and bidirectional search cannot stop merely because it found a meeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The entry at code freeze
&lt;/h2&gt;

&lt;p&gt;Our last push before the November 2, 15:00 UTC code freeze was commit &lt;code&gt;f00c64fca46bb4124462c053305a1395c596035f&lt;/code&gt;. It only updated the performance records; its source is the same as commit &lt;code&gt;29a57dc3752e417842e5bc2bf4796170c2ead170&lt;/code&gt;, which carried the parallel-parity change. The table lists the best live-board value for each track. The values come from different recorded runs, not a simultaneous four-track timing batch.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Track&lt;/th&gt;
&lt;th&gt;Process time&lt;/th&gt;
&lt;th&gt;Machine points&lt;/th&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A: Ingest&lt;/td&gt;
&lt;td&gt;0.130583 s&lt;/td&gt;
&lt;td&gt;61.431366&lt;/td&gt;
&lt;td&gt;&lt;code&gt;run-8ed375c52049&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B: Index&lt;/td&gt;
&lt;td&gt;0.918916 s&lt;/td&gt;
&lt;td&gt;55.479075&lt;/td&gt;
&lt;td&gt;&lt;code&gt;run-a9ce34cd1404&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C: Join&lt;/td&gt;
&lt;td&gt;0.159350 s&lt;/td&gt;
&lt;td&gt;57.219847&lt;/td&gt;
&lt;td&gt;&lt;code&gt;run-596f68f709a3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D: Traverse&lt;/td&gt;
&lt;td&gt;0.723518 s&lt;/td&gt;
&lt;td&gt;50.424918&lt;/td&gt;
&lt;td&gt;&lt;code&gt;run-396084b71fdb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The A and D bests came from pushes that changed other tracks, with their own source unchanged. C's best came from the final, documentation-only push, which re-ran the parallel-parity source; its first run had scored 57.147738. These are practice-board machine results, not final judging or an award: final scoring reruns the recorded commit on a held-out dataset, and judges contribute 35% of each track total. The per-track &lt;code&gt;PERF.md&lt;/code&gt; files retain the unsuccessful comparisons alongside the accepted changes; &lt;code&gt;MEASUREMENTS.json&lt;/code&gt; binds the best values to run IDs and source hashes.&lt;/p&gt;

&lt;p&gt;For the next graph-construction change, the missing evidence is where the modified access pattern spends time. For the CSV pipeline, the open question is whether the board's 8% coefficient gain holds on the held-out dataset.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The implementations, validation and this article were developed with Codex; Claude Code added the November 2 board results. Algorithm references, measurement limits and assistance disclosures are retained in the entry repository. Task definitions and the official scoring procedure are published by &lt;a href="https://speeddemon.dev/" rel="noopener noreferrer"&gt;Hackathon Raptors&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>performance</category>
      <category>algorithms</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>What the House Heard: a ghost story told by your smart home's sensor logs, offline with TabPFN and Gemma</title>
      <dc:creator>Hyunsik Park</dc:creator>
      <pubDate>Thu, 29 Oct 2026 12:05:50 +0000</pubDate>
      <link>https://dev.to/hyunsik_park_7c24c2e4f4fc/what-the-house-heard-a-ghost-story-told-by-your-smart-homes-sensor-logs-offline-with-tabpfn-and-2km3</link>
      <guid>https://dev.to/hyunsik_park_7c24c2e4f4fc/what-the-house-heard-a-ghost-story-told-by-your-smart-homes-sensor-logs-offline-with-tabpfn-and-2km3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A house with sensors keeps a diary every night. CO₂ climbs when someone breathes in a closed room, the hallway motion sensor fires when someone walks past, a contact sensor knows when the front door opens. &lt;strong&gt;What the House Heard&lt;/strong&gt; reads one night of that diary on your own computer and asks: who was in which room, minute by minute, and did anything turn up in a room that nobody walked to?&lt;/p&gt;

&lt;p&gt;TabPFN, Prior Labs' tabular foundation model, estimates for every room and every minute how likely it is that a person was there. Anything that looks like a person in a room nobody reached through the hallway, while the bedroom is still occupied, gets flagged. Gemma 4, running locally through llama.cpp, then tells the night back as a short ghost story, and a fact check makes sure the story quotes only times and readings that are really in the logs.&lt;/p&gt;

&lt;p&gt;That is how I read the theme: the ghost story is already in the machine, and the job is to let it out without the diary leaving the house.&lt;/p&gt;

&lt;p&gt;The data in the demo is synthetic. A simulator writes seven ordinary nights for a four-room flat and one Halloween night with three events nobody can explain, plus decoys that look strange but are not: a cat that sets off motion sensors, a lamp left on after bedtime, a fridge compressor that hums every 45 minutes, and a resident who cannot sleep and sits in the dark living room.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live report:&lt;/strong&gt; &lt;a href="https://hyunsikparker.github.io/what-the-house-heard/sample/report.html" rel="noopener noreferrer"&gt;https://hyunsikparker.github.io/what-the-house-heard/sample/report.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc62a660npx8vyd5t6wfp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc62a660npx8vyd5t6wfp.png" alt="Report for Halloween night: occupancy by room, the three flagged events, the story and its fact check" width="800" height="975"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TabPFN flagged exactly the three injected events and none of the decoys:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Room&lt;/th&gt;
&lt;th&gt;Why it is strange&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;02:47–02:55&lt;/td&gt;
&lt;td&gt;living room&lt;/td&gt;
&lt;td&gt;nobody crossed the hallway, the bedroom still looked occupied; CO₂ up 45.6 ppm in the dark (1.4 lux at most), sound up to 53.5 dBA, 7 motion triggers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03:12–03:19&lt;/td&gt;
&lt;td&gt;kitchen&lt;/td&gt;
&lt;td&gt;same pattern with the light on (82.3 lux) and CO₂ up 99.3 ppm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03:33&lt;/td&gt;
&lt;td&gt;hallway&lt;/td&gt;
&lt;td&gt;the front door contact opened and the hallway motion sensor saw nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The resident who sat in the dark living room at 00:51 and the walk to the kitchen at 01:58 came through the hallway, so they count as explained. The cat's walk at 04:18 was not flagged; in the simulation a cat trips motion sensors but adds no CO₂.&lt;/p&gt;

&lt;p&gt;Here is part of what Gemma wrote from that evidence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You lie in the dark, listening to the rhythmic breathing of your partner beside you. The house is silent until the sensors begin to betray your sanctuary. At 02:47, something moves in the living room despite no one leaving the bedroom. You hear a disturbance, a sound reaching 53.5 dBA, while the humidity climbs to 46.7%.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The boring explanation: A faulty sensor or a small pest is moving through the walls and vents, while a neighbor may have briefly propped the door open.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The report ends with: &lt;strong&gt;Fact check passed: 3 clock times and 6 readings in the story all appear in the evidence (attempt 1).&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HyunsikParker" rel="noopener noreferrer"&gt;
        HyunsikParker
      &lt;/a&gt; / &lt;a href="https://github.com/HyunsikParker/what-the-house-heard" rel="noopener noreferrer"&gt;
        what-the-house-heard
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Offline ghost stories from smart-home sensor logs: TabPFN finds who was where, Gemma tells the night, a fact check keeps it honest. Synthetic data.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;What the House Heard&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A smart home records a lot at night: CO₂, light, sound, motion, the front door. What the House Heard reads one night of
those logs on your own computer and asks a plain question: who was in which room, minute by minute? Anything that looks
like a person in a room nobody walked to gets flagged. A local Gemma model then tells the night back as a short ghost
story, and a fact check makes sure every time and reading in the story comes from the logs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Built with PriorLabs-TabPFN.&lt;/strong&gt; Made for the DEV Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine
&lt;strong&gt;All data in this repository is synthetic&lt;/strong&gt; (see &lt;code&gt;simulate.py&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HyunsikParker/what-the-house-heard/docs/report.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHyunsikParker%2Fwhat-the-house-heard%2FHEAD%2Fdocs%2Freport.png" alt="The report for Halloween night: occupancy by room, three flagged events, the story and its fact check"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;simulate.py&lt;/code&gt; writes seven ordinary nights for a four-room flat (labelled: which rooms had a person in them) and one
unlabelled Halloween night. The ordinary nights include the things that…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HyunsikParker/what-the-house-heard" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Occupancy with TabPFN.&lt;/strong&gt; Each room-minute becomes one row with twelve features: CO₂ and its five-minute rise, how far CO₂ sits above the room's recent floor, light, sound above the room's median, motion over three minutes, temperature and humidity changes, the door contact, the time of night and the room. TabPFN gets a 3,000-row sample of the labelled nights as its context. There is no training loop: &lt;code&gt;fit()&lt;/code&gt; stores the context and &lt;code&gt;predict_proba()&lt;/code&gt; scores all 2,160 room-minutes of the new night. As a check, I gave it six labelled nights and asked about the seventh: it labelled every room-minute correctly. Simulated sensors are much cleaner than real ones, so I read that as "the pipeline works", not as a claim about a real home. Two fits (the check and the real night) take about a minute on the CPU of an M4 Mac mini.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From probabilities to ghosts.&lt;/strong&gt; A room counts as occupied when TabPFN puts it above 0.5 for at least two minutes. The event is unexplained when nobody crossed the hallway in the three minutes before and the bedroom still looks occupied. "Crossed the hallway" means TabPFN's hallway probability or the raw hallway motion count, because during development the motion sensor alone missed a quick walk and turned a trip to the kitchen into a ghost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The story with Gemma.&lt;/strong&gt; Gemma 4 12B (QAT, Q4_0) runs in llama.cpp's server with thinking turned off. The prompt contains only the evidence: two residents in bed, the explained walks, and the flagged events with their readings. The rules ask for a second-person story of 150 to 220 words that quotes at least three readings exactly and invents nothing the sensors could not record. One story takes 25 to 45 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fact check.&lt;/strong&gt; Gemma is a good storyteller and a careless clerk. An early draft gave the kitchen "a sound up to 45.3 lux": the right number with the wrong unit, which my first, number-only check let through. The check now pairs every number with its unit (ppm, lux, dBA, %) and every clock time with an event window. When a story fails, the narrator asks again with the next seed, up to four times. Gemma also kept telling the reader they were alone in the house although the logs show two people in the bedroom; telling it that the reader lies awake next to the other resident fixed that. The check does not catch every flourish: the door still "clicks open" in the story, although a contact sensor records no sound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Sensor logs are a record of when you sleep, when you get up and when your home is empty. Here that record never leaves the computer: TabPFN runs on the CPU, Gemma runs in llama.cpp, nothing calls a hosted API, and the whole thing works with the Wi-Fi off.&lt;/p&gt;

&lt;p&gt;Local weights also made the fact check cheap. Retrying a story with a new seed costs half a minute on my own machine rather than another metered request, so the pipeline can afford to reject a story over one wrong unit.&lt;/p&gt;

&lt;p&gt;TabPFN's in-context learning suits homes, because every flat is laid out differently and nobody wants to train a model per household. In the simulation, six or seven labelled nights as context were enough.&lt;/p&gt;

&lt;p&gt;A note on versions: Prior Labs recommends TabPFN-3.5, which needs a licence acceptance on their platform. I used TabPFN v2 because its weights run offline without an account; 3.5 uses the same scikit-learn interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best Use of TabPFN&lt;/li&gt;
&lt;li&gt;Best Use of Gemma&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built with PriorLabs-TabPFN. All sensor data in the demo is synthetic. The code and this post were produced with Claude, Anthropic's AI agent.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>machinelearning</category>
      <category>opensource</category>
    </item>
    <item>
      <title>FileNap: local Gemma file plans, with a way back</title>
      <dc:creator>Hyunsik Park</dc:creator>
      <pubDate>Mon, 19 Oct 2026 23:22:14 +0000</pubDate>
      <link>https://dev.to/hyunsik_park_7c24c2e4f4fc/filenap-local-gemma-file-plans-with-a-way-back-i3n</link>
      <guid>https://dev.to/hyunsik_park_7c24c2e4f4fc/filenap-local-gemma-file-plans-with-a-way-back-i3n</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;FileNap is a local file-planning app for a small pile of receipts, notes and saved reading. Gemma reads short previews and proposes destinations. Each proposed move comes with a reason and an exact quote from the file. You review the plan, select the moves to approve, and keep a record that supports undo.&lt;/p&gt;

&lt;p&gt;The chore it aims to reduce is opening files one by one to decide where they belong. The intended user is someone with a small inbox to sort who still wants to check the decisions. This prototype handles up to 16 files in one plan; it does not reorganize an entire drive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://HyunsikParker.github.io/filenap-hf26/" rel="noopener noreferrer"&gt;Watch the recorded local demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The recording uses 12 fictional documents. Gemma proposed nine moves, kept a checklist in place and left two files for review: an ambiguous document and a deliberate prompt-injection example. After approval, the nine files moved into Receipts, Notes and Reading. Undo returned them to the inbox. A separate filesystem check confirmed that all 12 original SHA-256 hashes were restored.&lt;/p&gt;

&lt;p&gt;Planning took &lt;strong&gt;89.6 seconds&lt;/strong&gt; on an Apple M4 Mac with 32 GiB of memory. The &lt;strong&gt;42.6-second edit&lt;/strong&gt; removes most of the model wait and slows the review steps. The same page includes the &lt;strong&gt;111.3-second full recording&lt;/strong&gt; at its original timing, with English captions for both versions.&lt;/p&gt;

&lt;p&gt;These are fictional test documents, not evidence of accuracy on personal files or time saved in everyday use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/HyunsikParker/filenap-hf26" rel="noopener noreferrer"&gt;Source code, tests and run instructions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The application is a new Python project started during this week's build window. It uses the standard library, a local Ollama runtime and a browser interface. The application code is MIT licensed; model weights are not redistributed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Gemma supplies the content-based decisions. File names such as &lt;code&gt;paper_001.txt&lt;/code&gt; do not tell the app whether a document is a receipt, a note or something to read. The model receives the selected names, text excerpts, folder goal and permitted destinations, then returns a structured plan.&lt;/p&gt;

&lt;p&gt;The recorded run used a cached &lt;strong&gt;Gemma4 11.9B Q4_K_M&lt;/strong&gt; model with thinking disabled. Its manifest and weight hashes are documented in the README. The public Ollama tag had changed by the time of verification, so the shared tag name alone does not reproduce these exact cached bytes.&lt;/p&gt;

&lt;p&gt;The file-operation code checks model IDs, destinations, source quotes and the saved file state. Applying a plan requires approval. Moves preserve existing destination files, and undo leaves a conflict visible if a moved file has changed or its original name has been recreated.&lt;/p&gt;

&lt;p&gt;An undo-collision test caught a bug where the journal could report an incomplete undo as finished. That case now preserves both files, reports the conflict and supports retry. The final &lt;strong&gt;19 tests passed on macOS and Linux/WSL2&lt;/strong&gt;, including interrupted moves, edited files, collisions and local HTTP boundaries.&lt;/p&gt;

&lt;p&gt;A correct quote can still accompany a wrong classification. FileNap keeps review in the flow. It is a working prototype, and its recovery tests do not cover every possible filesystem race.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The open-weight model lets this project run its inference locally with a documented model artifact. FileNap's client accepts only a loopback model endpoint, does not follow redirects or use environment proxies, and has no hosted fallback. Development used Ollama with cloud features disabled and an already-cached model, without an inference API key.&lt;/p&gt;

&lt;p&gt;Open source also makes the decision boundary inspectable: the prompt, validation checks and code that moves files are in the repository. The model makes suggestions; the application records approved actions. Someone adapting the project can examine both parts before trusting it with a folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;Best Use of Gemma.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI assistance:&lt;/strong&gt; Codex assisted with implementation, tests, demo preparation and this submission draft. The results above come from local test and recording evidence. No personal documents or private agent-session transcript are included.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>opensource</category>
    </item>
    <item>
      <title>OutingFold: a pocket field guide that shows what Gemma left out</title>
      <dc:creator>Hyunsik Park</dc:creator>
      <pubDate>Mon, 05 Oct 2026 21:16:08 +0000</pubDate>
      <link>https://dev.to/hyunsik_park_7c24c2e4f4fc/outingfold-a-pocket-field-guide-that-shows-what-gemma-left-out-4n2c</link>
      <guid>https://dev.to/hyunsik_park_7c24c2e4f4fc/outingfold-a-pocket-field-guide-that-shows-what-gemma-left-out-4n2c</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;OutingFold turns park notes into a small field guide you can fold and take outside. Paste the source text, let local Gemma suggest useful excerpts, review the selection, then save an offline page or print it.&lt;/p&gt;

&lt;p&gt;The card has places for practical details, things to look and listen for, cautions, and a detail you notice yourself. It is for a short visit where a few source excerpts would be useful and another open browser tab would not.&lt;/p&gt;

&lt;p&gt;The difficult part was deciding what a model should be allowed to put on that page. A fluent summary can drop the second half of a warning. An exact quotation can do the same thing if it leaves out the next sentence. OutingFold therefore shows the lines the model left out alongside the ones it chose. Both need review before export.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hyunsikparker.github.io/outingfold/" rel="noopener noreferrer"&gt;Open OutingFold&lt;/a&gt;. No account or installation is needed for the recorded example or manual selection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ljvv3bos3t77sjyj1ay.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ljvv3bos3t77sjyj1ay.jpg" alt="OutingFold landing page with an illustrative woodland image and a folded guide" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr8nzf2lseccashp9zbxr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr8nzf2lseccashp9zbxr.jpg" alt="OutingFold showing a saved Gemma selection from National Park Service excerpts" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Try the park example&lt;/strong&gt;. This loads an actual saved response from &lt;code&gt;gemma4:12b-it-q4_K_M&lt;/code&gt;, using nine excerpts from the National Park Service's &lt;a href="https://www.nps.gov/this/planyourvisit/things2do.htm" rel="noopener noreferrer"&gt;Theodore Roosevelt Island page&lt;/a&gt;. It does not run inference in the browser.&lt;/p&gt;

&lt;p&gt;The response leaves the ranger-program paragraph out and puts two trail-surface descriptions under “Look and listen.” To try the review controls, add S2 and move S6 and S7 to “Plan the visit.” Read the remaining source lines, tick the review confirmation, and continue to the Save step to download or print. Those edits produce the example below; they are reader edits, not a better model response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdszf512cm1pu9a6woywu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdszf512cm1pu9a6woywu.jpg" alt="Source review after adding S2, with the omitted history paragraph still visible" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downloaded HTML keeps all the pasted notes, including omitted excerpts, in an expandable section. It has no scripts or external assets. The printout keeps only the selected excerpts and space for handwritten observations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frl7x7tmretzk75vgpnu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frl7x7tmretzk75vgpnu5.png" alt="One-page A4 PDF of the example after the selection was edited" width="800" height="1132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/HyunsikParker/outingfold/raw/b4ee57827bf3a066f9f2e520f4499d3d95759487/media/reviewed-example.pdf" rel="noopener noreferrer"&gt;Download that PDF&lt;/a&gt;. This is a browser-generated PDF, not a photograph of a field trial. I have not tested OutingFold outdoors or on a physical printer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/HyunsikParker/outingfold" rel="noopener noreferrer"&gt;Source, setup and test records&lt;/a&gt; · MIT license&lt;/p&gt;

&lt;p&gt;This is a new project begun on October 5 for Week 1. The app uses plain browser modules and a Node server, with no npm dependencies. To run fresh selections, install Node.js 22 or later and Ollama, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/HyunsikParker/outingfold.git
&lt;span class="nb"&gt;cd &lt;/span&gt;outingfold
ollama pull gemma4:12b-it-q4_K_M
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama must be running. Open &lt;code&gt;http://127.0.0.1:4318&lt;/code&gt;. The model download is roughly 8 GB; the recorded run used an Apple M4 with 32 GB RAM. Smaller machines have not been tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Each source line gets an ID. Gemma reads those lines and returns a short JSON selection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"picks"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"S6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"section"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"S9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"section"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"care"&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is an illustration of the format, not a recorded response. The actual prompt, schema and raw outputs are in the repository.&lt;/p&gt;

&lt;p&gt;The application validates the IDs and sections, then copies the original substrings. It rejects unknown IDs, duplicate IDs and extra fields. There is no field for generated card prose. Length limits keep the selection small enough to review, and exports stay disabled until the reader confirms that review. Changing a source line or selection clears the confirmation.&lt;/p&gt;

&lt;p&gt;The first completed evaluation exposed the gap between exact copying and useful selection:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Predeclared case&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;th&gt;Request time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NPS park excerpts&lt;/td&gt;
&lt;td&gt;Retained the selections required by the test&lt;/td&gt;
&lt;td&gt;8.533 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synthetic closure and exception&lt;/td&gt;
&lt;td&gt;Kept the restriction and its qualification&lt;/td&gt;
&lt;td&gt;10.835 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sparse observation notes&lt;/td&gt;
&lt;td&gt;Returned no selections and missed two relevant lines&lt;/td&gt;
&lt;td&gt;3.490 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The run stopped at that failure. A fourth, prompt-injection case was not run. One earlier request had been cancelled without a result. The &lt;a href="https://github.com/HyunsikParker/outingfold/blob/main/evidence/report.json" rel="noopener noreferrer"&gt;complete report&lt;/a&gt; includes the failed case; these three examples are too few to estimate general accuracy.&lt;/p&gt;

&lt;p&gt;All completed responses passed the source-copy check, but the empty response passed it by selecting nothing. That check alone would make a poor success metric. The interface now tells the reader when Gemma selects nothing and lets them choose from the unchanged source. It also leaves empty card sections explicitly marked as having no selection.&lt;/p&gt;

&lt;p&gt;Nine software tests pass without running a model. They cover source copying, invalid selections, limits, HTML escaping, retention of omitted notes, and local API boundaries. Browser checks covered source edits, manual recovery, downloads, keyboard focus, 320 px and 390 px viewports, the three-step editor, and the one-page A4 example shown above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The model runs on the reader's computer through Ollama. After downloading the weights, this selection step needs no hosted inference account or API key. The app sends notes only to its loopback server and local Ollama, which is useful when the notes contain private plans. The public demo does not send typed notes to a model.&lt;/p&gt;

&lt;p&gt;An API could perform the same selection task. Running open weights locally gives this version control over where the notes go and avoids a per-request inference charge. The tradeoff is real: a large initial download, memory requirements, and speed that depends on the machine. This prototype uses one fixed model; changing it requires changing the code and retesting.&lt;/p&gt;

&lt;p&gt;The prompt, validation rules, failed response and export code are available together. A reader can inspect why a line appeared on the card and repair a poor selection without waiting for another model call. The app's job ends when the reviewed page is saved. The blank fourth panel leaves something for the visit itself to supply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma performs the local excerpt selection in the running app. The hosted example preserves one real response from the recorded evaluation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI assistance and sources:&lt;/strong&gt; I used OpenAI Codex to help develop the code and write-up. The woodland image was generated with GPT Image and is an illustration, not a photograph of the example park. Test results above come from the saved run and software/browser checks. The park example quotes NPS text, retrieved October 5; it is a collection of excerpts, not the entire page or a current conditions report. No claim to original U.S. Government works. OutingFold does not verify the source or plan a route; check hours, closures, weather and access before leaving.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>gemma</category>
    </item>
    <item>
      <title>ShiftCal: a local Gemma roster reader for someone close to me</title>
      <dc:creator>Hyunsik Park</dc:creator>
      <pubDate>Mon, 05 Oct 2026 01:04:11 +0000</pubDate>
      <link>https://dev.to/hyunsik_park_7c24c2e4f4fc/shiftcal-a-local-gemma-roster-reader-for-someone-close-to-me-4dbf</link>
      <guid>https://dev.to/hyunsik_park_7c24c2e4f4fc/shiftcal-a-local-gemma-roster-reader-for-someone-close-to-me-4dbf</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Someone close to me finds it cumbersome to move a rotating shift roster into a calendar. ShiftCal takes their own shifts as text, uses local Gemma to read the dates and shift types, and prepares a calendar file after they check each row.&lt;/p&gt;

&lt;p&gt;A night shift makes the problem concrete. &lt;code&gt;31일 나이트&lt;/code&gt; means a night shift on the 31st. With the example hours of 23:00–07:00, an October shift ends on November 1. Typing the wrong end date would leave a misleading calendar entry. ShiftCal calculates that boundary in tested code and shows both dates for review.&lt;/p&gt;

&lt;p&gt;The real-person need is confirmed. The schedules shown here are fictional, and I have not yet collected feedback from the recipient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hyunsikparker.github.io/shiftcal/" rel="noopener noreferrer"&gt;Open the interactive recorded demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Try a recorded example&lt;/strong&gt;. The fourth row, &lt;code&gt;4일 이브닝?&lt;/code&gt;, stays unresolved. Correct it to Evening only if the original roster confirms that shift, or remove it. Check each remaining row to enable the calendar export. Day-off rows stay visible but create no event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxnf7obgulpk9on7lfy01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxnf7obgulpk9on7lfy01.png" alt="ShiftCal showing the source roster beside rows to review" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The public page replays a saved response from an actual Gemma run; it does not host inference. To process new text, follow the local setup in the README. The app needs Python and Ollama, with no Python or JavaScript packages. It currently accepts text only and uses Korea Standard Time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/HyunsikParker/shiftcal" rel="noopener noreferrer"&gt;Source, setup and tests on GitHub&lt;/a&gt; · MIT license.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Gemma (&lt;code&gt;gemma4:12b-it-q4_K_M&lt;/code&gt;, through Ollama) extracts a day and a D/E/N/O shift code from each Korean or English input line. Each result remains beside its source. Missing or duplicated model rows are flagged; explicit uncertainty stays unresolved.&lt;/p&gt;

&lt;p&gt;JavaScript handles dates, shift hours and iCalendar formatting. It rejects invalid dates, duplicate dates and overlapping shifts. Changing a row or its settings clears the relevant confirmation. Export stays blocked until the remaining rows are valid and checked. There is no automatic calendar sync.&lt;/p&gt;

&lt;p&gt;The Python server listens on loopback, calls a fixed local Ollama address and rejects foreign origins and Host headers. It limits input size and handles one extraction at a time. It does not log or save roster text. Users are asked to leave out names, workplace details and other people's shifts.&lt;/p&gt;

&lt;p&gt;Fifteen unit tests passed, including month and year boundaries, leap years, overnight overlaps, uncertainty and server access checks. Four synthetic fixtures also went through the actual model: three matched exactly, with 13 of 14 date/code pairs matching overall. In the remaining row, &lt;code&gt;주간&lt;/code&gt; had no date. Gemma left both fields unresolved instead of retaining the expected day-shift code. The row remained visible and could not be exported. These are small functional checks, not an accuracy benchmark.&lt;/p&gt;

&lt;p&gt;Desktop and mobile layouts were checked, along with a fresh three-line extraction in the browser. Calendar-app import and recipient field testing remain unverified. Reimporting a file may create duplicates; the app recommends a separate calendar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;An open-weight model makes local roster processing possible without sending each schedule to a hosted model service. Open source makes the boundary around that model visible: the prompt, schema, validation and export code are all in the repository. Someone adapting ShiftCal can change the shift hours or inspect a date calculation without depending on a private API.&lt;/p&gt;

&lt;p&gt;The first model download needs an internet connection and disk space. Local inference still needs suitable hardware and electricity; the application has no per-request service charge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma performs the Korean and English roster extraction in the local application. The public demo includes a recorded response from that same implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI disclosure:&lt;/strong&gt; Codex generated and revised the implementation, tests and prose under my direction. I supplied and confirmed the real-person problem. Executed tests and model responses were checked; no interview, testimonial or field trial is claimed. This entry uses the Fully Autonomous development disclosure.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Constraint Clerk: A Source-Cited Changeover Agent</title>
      <dc:creator>Hyunsik Park</dc:creator>
      <pubDate>Sat, 03 Oct 2026 15:13:18 +0000</pubDate>
      <link>https://dev.to/hyunsik_park_7c24c2e4f4fc/constraint-clerk-a-source-cited-changeover-agent-2jfg</link>
      <guid>https://dev.to/hyunsik_park_7c24c2e4f4fc/constraint-clerk-a-source-cited-changeover-agent-2jfg</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path One: Ship an Agent That Queries Real Content&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fully Autonomous AI build.&lt;/strong&gt; Codex generated and revised the implementation, original fictional records and this writeup. The verification below is an actual Sanity Content Agent response. No human coding or venue experience is claimed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Constraint Clerk checks a changeover plan for a fictional pocket theater. A cart needs to reach backstage before an 18:12 cue. The September survey records a 940 mm passage, but an October sound screen reduces it to 690 mm. The unfolded cart is 880 mm wide.&lt;/p&gt;

&lt;p&gt;Finding both numbers is not enough. The agent must identify which claim applies, follow the explicit supersession, then combine width with cart state, preparation time, route duration and crew availability. Folding makes the cart narrower, but requires two crew and three minutes of preparation. The wider east passage has a timed closure. The west passage has no measured width.&lt;/p&gt;

&lt;p&gt;The output separates &lt;strong&gt;blocked&lt;/strong&gt;, &lt;strong&gt;feasible&lt;/strong&gt; and &lt;strong&gt;unknown&lt;/strong&gt;, with source records for each check. It never converts a missing measurement into permission to proceed. All venue and equipment records are original fiction, not a real inspection or safety assessment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hyunsikparker.github.io/constraint-clerk/" rel="noopener noreferrer"&gt;Open Constraint Clerk&lt;/a&gt; — no login required.&lt;/p&gt;

&lt;p&gt;Start with &lt;strong&gt;View recorded AI verification&lt;/strong&gt;. It loads the real October 3 response, its calculated result and the retrieval trace. Expand &lt;strong&gt;Full actual answer&lt;/strong&gt; and &lt;strong&gt;Actual retrieval trace&lt;/strong&gt; to inspect them. Selecting a source opens the original record, including the superseded survey.&lt;/p&gt;

&lt;p&gt;The public demo is static: viewing the recording makes &lt;strong&gt;no new AI request&lt;/strong&gt;. The other controls run the local checker. Changing inputs clears the recorded response, so a local result cannot be mistaken for a fresh model answer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Verification scope&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original unfolded cart, north route&lt;/td&gt;
&lt;td&gt;Blocked: 880 mm cart, 690 mm passage&lt;/td&gt;
&lt;td&gt;Real Content Agent + Context conversation; also locally reproducible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Folded cart, same route and two crew&lt;/td&gt;
&lt;td&gt;Feasible against the documented constraints; finish 18:10&lt;/td&gt;
&lt;td&gt;Local deterministic check only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Folded cart, unmeasured west route&lt;/td&gt;
&lt;td&gt;Unknown; width evidence is missing&lt;/td&gt;
&lt;td&gt;Local deterministic check only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzgfe2oux2d3qcn5hlfgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzgfe2oux2d3qcn5hlfgf.png" alt="Constraint Clerk showing the recorded agent response, constraint results and current source record" width="800" height="845"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/HyunsikParker/constraint-clerk" rel="noopener noreferrer"&gt;Source repository&lt;/a&gt; · MIT licensed.&lt;/p&gt;

&lt;p&gt;The React interface, TypeScript checker, local agent server, Sanity Studio schema and pinned lockfiles are included. The README explains how to run the local demo and reproduce the cloud integration with a separate project and your own credentials. &lt;strong&gt;21 local tests pass&lt;/strong&gt;, including source supersession, closure boundaries, missing measurements, canonical reference recovery and transport limits. Type checking and the production build pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Sanity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One dataset, two scoped Context endpoints
&lt;/h3&gt;

&lt;p&gt;The Content Lake holds 19 fictional documents: 11 &lt;code&gt;ccSource&lt;/code&gt; records and eight &lt;code&gt;ccClaim&lt;/code&gt; constraints. A claim stores its target, property, value, effective date and optional &lt;code&gt;supersedes&lt;/code&gt; reference. Routes refer to passages; folding requirements and crew rosters remain separate records.&lt;/p&gt;

&lt;p&gt;A Knowledge Base was built from that dataset. One read-only Context MCP endpoint exposes only the KB. A second exposes only the project's &lt;code&gt;ccSource&lt;/code&gt; and &lt;code&gt;ccClaim&lt;/code&gt; records. The agent uses Sanity Content Agent through the AI SDK, with built-in read, write and web search disabled.&lt;/p&gt;

&lt;p&gt;The request follows this path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The harness reads the KB outline and exposes &lt;code&gt;knowledge_base_read&lt;/code&gt; to the model.&lt;/li&gt;
&lt;li&gt;The model reads a relevant KB entry, then calls the custom &lt;code&gt;check_plan&lt;/code&gt; tool.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_plan&lt;/code&gt; queries the live dataset using Context's &lt;code&gt;groq_query&lt;/code&gt;, restores canonical reference arrays using &lt;code&gt;array_field_reader&lt;/code&gt;, validates the typed records and runs the checker.&lt;/li&gt;
&lt;li&gt;The model explains that result with exact source document IDs. The structured result is authoritative for the selected plan.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The actual result
&lt;/h3&gt;

&lt;p&gt;One live conversation on October 3 made &lt;strong&gt;four model HTTP requests and four agent tool calls&lt;/strong&gt;: two KB reads and two plan checks. Each plan check retrieved 19 structured records and restored four canonical reference arrays.&lt;/p&gt;

&lt;p&gt;The final answer cited &lt;code&gt;cc-claim-north-current&lt;/code&gt; for &lt;strong&gt;690 mm&lt;/strong&gt; and &lt;code&gt;cc-claim-north-old&lt;/code&gt; for the explicitly superseded &lt;strong&gt;940 mm&lt;/strong&gt; survey. It blocked the &lt;strong&gt;880 mm&lt;/strong&gt; unfolded cart, a &lt;strong&gt;190 mm&lt;/strong&gt; shortfall. The roster and 18:07 calculated finish passed; width was the blocker.&lt;/p&gt;

&lt;p&gt;The response also kept its scope: it described folding's requirements but did not claim to have evaluated the folded variant. That alternative is shown separately as a local check.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where retrieval needed a stricter boundary
&lt;/h3&gt;

&lt;p&gt;Context can outline an array instead of returning its full contents, even when it has one reference. The first adapter rejected that output rather than calculating with incomplete links. The corrected adapter reads each outlined array through &lt;code&gt;array_field_reader&lt;/code&gt; and checks its document ID, field, count, indices and cropping flags. A truncated response fails closed.&lt;/p&gt;

&lt;p&gt;The KB also has one reviewed, unresolved issue: a derived east-passage entry omits a closure interval that exists in the original structured source. The issue has not been dismissed. Exact intervals and measurements come from the validated records, not generated KB prose. The saved dataset was read back and matched the original seed's full typed-content hash.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost and runtime limits
&lt;/h3&gt;

&lt;p&gt;The project was automatically assigned a &lt;strong&gt;USD0 Growth Trial&lt;/strong&gt;, with automatic downgrade to Free after 30 days. Only Free-included features were used; there was no paid upgrade or hosting purchase. Paid overages were disallowed.&lt;/p&gt;

&lt;p&gt;A durable one-conversation permit was reserved before inference. Limits were four model requests, four agent tool calls, 90 seconds, no SDK retries and 512 KiB per model response. The provider has no token-output cap, so none is claimed. No further live attempts were made. Immediately afterward, the usage screen still showed 0/1,000 credits and USD0; the settled consumed-credit count was not yet reflected and is not claimed to be zero.&lt;/p&gt;

&lt;p&gt;The public site has no model endpoint or tokens. The optional agent server is loopback-only. The README discloses two low-severity production dependency advisories and 12 separate Studio/CLI advisories, including the mitigations and remaining limits. No all-clean security audit is claimed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project ID:&lt;/strong&gt; &lt;code&gt;r7tbo2j3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; &lt;code&gt;production&lt;/code&gt; (public, original fictional records)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document types:&lt;/strong&gt; &lt;code&gt;ccSource&lt;/code&gt;, &lt;code&gt;ccClaim&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge Base:&lt;/strong&gt; Constraint Clerk — North Pier Room (&lt;code&gt;kb4e4xT6fMwh&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset source filter:&lt;/strong&gt; &lt;code&gt;_type in ["ccSource", "ccClaim"] &amp;amp;&amp;amp; _id match "cc-*"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a separate project from the Path Two entry. Neither its content nor its submission was changed for this build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Session
&lt;/h2&gt;

&lt;p&gt;The public &lt;a href="https://github.com/HyunsikParker/constraint-clerk/blob/main/src/recorded-verification.json" rel="noopener noreferrer"&gt;recorded verification&lt;/a&gt; contains the actual question, selected plan, answer, structured result and timestamped retrieval trace. Its answer and trace match the original private run receipt. Credentials, private paths and unrelated work are excluded. This is the application agent's verification record, not an uploaded coding-session transcript.&lt;/p&gt;

&lt;p&gt;The integration follows the official &lt;a href="https://www.sanity.io/docs/ai/sanity-context-mcp" rel="noopener noreferrer"&gt;Sanity Context MCP&lt;/a&gt;, &lt;a href="https://www.sanity.io/docs/ai/sanity-context-create-knowledge-base" rel="noopener noreferrer"&gt;Knowledge Base&lt;/a&gt; and &lt;a href="https://reference.sanity.io/content-agent/" rel="noopener noreferrer"&gt;Content Agent&lt;/a&gt; interfaces.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
    <item>
      <title>Alibi Atlas: A Continuity Workbench for Fiction</title>
      <dc:creator>Hyunsik Park</dc:creator>
      <pubDate>Sat, 03 Oct 2026 09:22:23 +0000</pubDate>
      <link>https://dev.to/hyunsik_park_7c24c2e4f4fc/alibi-atlas-a-continuity-workbench-for-fiction-2gej</link>
      <guid>https://dev.to/hyunsik_park_7c24c2e4f4fc/alibi-atlas-a-continuity-workbench-for-fiction-2gej</guid>
      <description>&lt;p&gt;&lt;em&gt;An entry for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge — Path Two&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Alibi Atlas is a continuity workbench for fiction. It asks a narrow question: can a character be where the scene puts them?&lt;/p&gt;

&lt;p&gt;The starter case, &lt;strong&gt;The missing lantern&lt;/strong&gt;, has three characters, four places and six events. Mira appears at the archive and the dock at overlapping times. Theo has four minutes to make a ten-minute journey. Jules says they visited the dock, but that statement is not treated as an established scene.&lt;/p&gt;

&lt;p&gt;The app puts physical conflicts beside a timeline and a diagram of authored routes. Select an event, try different times or a different place, and see which conflicts remain. These preview changes stay in the browser; they do not rewrite the author's Sanity content.&lt;/p&gt;

&lt;p&gt;It is a tool for checking a fictional draft, not for deciding whether a real person's account is truthful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hyunsikparker.github.io/alibi-atlas/" rel="noopener noreferrer"&gt;Open Alibi Atlas&lt;/a&gt; — no login needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyz37g2pp4dhpf7bky2bh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyz37g2pp4dhpf7bky2bh.jpg" alt="The published story contains two planted continuity errors" width="800" height="982"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try this short walkthrough:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;strong&gt;Checks the departing boat&lt;/strong&gt;. Change its time to &lt;strong&gt;18:34–18:44&lt;/strong&gt;, then choose &lt;strong&gt;Apply to preview&lt;/strong&gt;. Mira's overlap is resolved, with enough time for the eight-minute journey.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Delivers to the keeper&lt;/strong&gt; and try &lt;strong&gt;18:30–18:38&lt;/strong&gt;. The second physical conflict disappears.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Jules's account needs a decision&lt;/strong&gt;. Change it to &lt;strong&gt;This happens in the story&lt;/strong&gt;. A new overlap appears: the keeper is now established at the tower and the dock at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reset preview&lt;/strong&gt; restores the loaded story. &lt;strong&gt;Inspect report JSON&lt;/strong&gt; shows the case, changes, findings and source metadata for inspection or manual copying.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0gvd7m0cjvjzlikb3ecb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0gvd7m0cjvjzlikb3ecb.jpg" alt="Two preview corrections remove the physical conflicts; the reported account stays separate" width="800" height="1006"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/HyunsikParker/alibi-atlas/blob/main/docs/preview-example.json" rel="noopener noreferrer"&gt;Example preview report&lt;/a&gt; · &lt;a href="https://github.com/HyunsikParker/alibi-atlas/blob/main/docs/mobile.jpg" rel="noopener noreferrer"&gt;390px layout&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/HyunsikParker/alibi-atlas" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; — MIT licensed, with local setup, Studio instructions and tests.&lt;/p&gt;

&lt;p&gt;The public viewer is a Next.js static export on GitHub Pages. It reads the real public Sanity dataset through &lt;code&gt;@sanity/client&lt;/code&gt;. Studio runs separately for authenticated authoring. No write token is bundled into the viewer, and there is no runtime AI call.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Build Process
&lt;/h2&gt;

&lt;p&gt;Codex generated and revised the implementation, fictional case, documentation and this article. This is a record of an autonomous AI build, not a claim that I wrote the code by hand or a verbatim prompt transcript.&lt;/p&gt;

&lt;h3&gt;
  
  
  The brief and the schema
&lt;/h3&gt;

&lt;p&gt;The working brief, paraphrased, was to build a fiction-continuity editor backed by real Sanity content. Characters, places, journeys and scenes needed their own documents. A character's statement had to remain separate from an event established by the author. Readers needed to test corrections without changing the published story.&lt;/p&gt;

&lt;p&gt;That distinction became an explicit &lt;code&gt;certainty&lt;/code&gt; field rather than something inferred from the wording of a scene. Each event also keeps the author's scene or dialogue note visible in the inspector.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Document&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;What it records&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Character&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Name and role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Place&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Name and diagram coordinates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Journey&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Two place references and a minimum time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Story event&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Character, place, interval, certainty and source note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Case&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Time window and references to the other documents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The case references those documents instead of storing the story as one block of prose. A GROQ query dereferences them into the graph used by the workbench. Journey times are authored constraints, not estimates from map coordinates. The analysis uses the shortest connected, undirected route; a missing route stays unknown and appears as a review item.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the early attempts broke
&lt;/h3&gt;

&lt;p&gt;An initial-value resolver tried to use &lt;code&gt;context.documentId&lt;/code&gt;. TypeScript rejected that API. The replacement uses fixed starter IDs and explicit initial-value templates. The starter tool creates the 18 linked documents through the authenticated Studio client and reads them back without overwriting existing documents.&lt;/p&gt;

&lt;p&gt;The first architecture put Studio inside the Next.js app. It was separated so the public reader could be a static export while authoring retained its authenticated session. This build uses the Sanity client and Studio, not the App SDK or Sanity Workflows.&lt;/p&gt;

&lt;p&gt;The first all-clear wording was too strong when a route could be missing. It now says only that no physical conflicts were detected under the authored constraints. Unknown journeys still need review.&lt;/p&gt;

&lt;p&gt;Browser checks also caught a stale validation message after reset. Reset now starts a fresh inspector without remounting it on every input change. The time-input automation changed displayed values without updating React until native key input was used; the final correction checks used keyboard input.&lt;/p&gt;

&lt;h3&gt;
  
  
  What was checked
&lt;/h3&gt;

&lt;p&gt;All 12 domain tests passed, including nested overlaps, route choice, reported accounts, missing routes and invalid references or intervals. The public Pages workflow passed tests, type checking, the static build and deployment. At 390px, the page had no horizontal overflow; the timeline scrolls inside its own container.&lt;/p&gt;

&lt;p&gt;A published description change produced the live-update notice and kept an in-progress preview. An actual Studio event edit reduced the public viewer from two physical conflicts to one after refresh, without preview edits. That test edit was then restored. A complete case readback matched the pre-test snapshot, and the dataset again contained the original 18 documents.&lt;/p&gt;

&lt;p&gt;The visible report was read back with two preview edits, zero physical conflicts and one reported-account review. Browser automation did not confirm a completed file download or clipboard contents, so neither is claimed from the buttons alone. The inspectable JSON and linked example make the payload available directly. The controller also stalled on a dirty-preview refresh confirmation; a separate reader verified the new published content, and clean-preview refresh worked.&lt;/p&gt;

&lt;p&gt;The prototype handles one case and one time window. It does not infer travel times, prove a statement is true or review every kind of plot hole. The aim is to expose contradictions the author can inspect, with the underlying event and source note still visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project ID:&lt;/strong&gt; &lt;code&gt;0k0a3q37&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; &lt;code&gt;production&lt;/code&gt; — public, original fictional demonstration content only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Published documents:&lt;/strong&gt; 18 across the five schema types above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The viewer queries published content without a token. Sanity remains the authoring source: publishing a change raises a notice, and the reader chooses when to reload it. A connected query failure shows an error instead of silently substituting the local fixture.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
