<?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: oji - building AI in public</title>
    <description>The latest articles on DEV Community by oji - building AI in public (@masaoshimadaopen).</description>
    <link>https://dev.to/masaoshimadaopen</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%2F4013207%2F62889ff7-f41e-4077-9836-3fafe971b8ce.jpg</url>
      <title>DEV Community: oji - building AI in public</title>
      <link>https://dev.to/masaoshimadaopen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/masaoshimadaopen"/>
    <language>en</language>
    <item>
      <title>AIの議事録で「検討」が「決定」に変わったら、原文のこの2点を照合する</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Fri, 23 Oct 2026 23:30:17 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/ainoyi-shi-lu-dejian-tao-gajue-ding-nibian-watutara-yuan-wen-nokono2dian-wozhao-he-suru-j2g</link>
      <guid>https://dev.to/masaoshimadaopen/ainoyi-shi-lu-dejian-tao-gajue-ding-nibian-watutara-yuan-wen-nokono2dian-wozhao-he-suru-j2g</guid>
      <description>&lt;p&gt;以下は説明用の架空の会議メモです。実際の顧客案件ではありません。&lt;/p&gt;

&lt;p&gt;原文：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;新しいツールの導入日は未定。木曜に候補を比較する。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;誤った要約：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;木曜から新しいツールを導入することが決定した。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;文章は自然でも、比較の日と導入の日が入れ替わっています。「決定」の根拠も原文にありません。&lt;/p&gt;

&lt;p&gt;確認するのは、動作と状態です。「比較する」が「導入する」に変わっていないか。「未定」が「決定」に変わっていないか。この2点を原文と並べます。&lt;/p&gt;

&lt;p&gt;修正文：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;導入日は未定。木曜に候補を比較する。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;木曜が何月何日なのかは原文にありません。ここをAIに補わせず、不明として残します。&lt;/p&gt;

&lt;p&gt;依頼時は、次の一文を添えられます。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;原文の予定・検討・決定を区別して要約してください。原文にない日付や結論は補わず、不明と記してください。各結論には根拠となる原文を併記してください。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;この指示でも誤りがなくなる保証はありません。最後に、要約中の決定事項と日付を原文で照合します。機密情報を外部AIへ入力できるかは社内ルールも確認してください。&lt;/p&gt;

&lt;p&gt;毎回の依頼文、完了条件、引き継ぎをまとめて用意したい方には、&lt;a href="https://oji-note-cta.coolchar19.chatgpt.site/r/meeting" rel="noopener noreferrer"&gt;3種類の記入用テンプレート&lt;/a&gt;があります。今回の照合だけなら、上の無料の例で試せます。&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>programming</category>
      <category>api</category>
      <category>python</category>
    </item>
    <item>
      <title>Why a valid SCORM manifest is not enough</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Tue, 20 Oct 2026 22:45:32 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/why-a-valid-scorm-manifest-is-not-enough-3b4b</link>
      <guid>https://dev.to/masaoshimadaopen/why-a-valid-scorm-manifest-is-not-enough-3b4b</guid>
      <description>&lt;p&gt;A SCORM package can pass a manifest check and still fail at the client handoff.&lt;/p&gt;

&lt;p&gt;That is not a contradiction. A manifest validator answers a narrow question: can the package descriptor be parsed, and does it follow the expected structure? Release QA has to answer several more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do all referenced local files exist?&lt;/li&gt;
&lt;li&gt;Does every QTI response declaration still match the answer identifier used by the interaction?&lt;/li&gt;
&lt;li&gt;Did an editor rename or remove an asset after the last approved build?&lt;/li&gt;
&lt;li&gt;Can the team explain exactly what changed between the previous package and this one?&lt;/li&gt;
&lt;li&gt;Is there a stable result that CI and a delivery reviewer can inspect later?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A failure that schema validation can miss
&lt;/h2&gt;

&lt;p&gt;Consider a package with a valid manifest and an existing launch file. During editing, someone renames the correct answer identifier in a QTI item but misses the corresponding response declaration.&lt;/p&gt;

&lt;p&gt;The archive opens. The manifest still validates. The assessment can no longer score that item correctly.&lt;/p&gt;

&lt;p&gt;Schema-level success did not prove release-level consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a four-part preflight
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Inspect the package
&lt;/h3&gt;

&lt;p&gt;Reject unsafe archive paths, verify manifest references, check local assets, and flag static HTML risks before extraction or delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Inspect the assessment
&lt;/h3&gt;

&lt;p&gt;Resolve QTI answer identifiers and check question-bank structure. A syntactically valid XML document can still contain a broken relationship between an interaction and its answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Compare with the approved baseline
&lt;/h3&gt;

&lt;p&gt;Record added, removed, changed, and unchanged files. A deterministic diff makes an unexplained delivery change visible before it reaches the LMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Keep evidence
&lt;/h3&gt;

&lt;p&gt;Save stable JSON for CI and a readable HTML report for the delivery record. A release gate is more useful when the same input produces the same evidence.&lt;/p&gt;

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

&lt;p&gt;The current local fixture suite accepts 58 official 1EdTech QTI examples and six public SCORM 1.2/2004 packages without a false failure. It also detects 21 of 21 deliberately invalid answer-identifier mutations.&lt;/p&gt;

&lt;p&gt;Those numbers describe this fixture set. They do not prove compatibility with every LMS, runtime, accessibility requirement, legal requirement, or course claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the local preflight
&lt;/h2&gt;

&lt;p&gt;I packaged the implemented checks as a free local preflight. It runs with Python 3.10 or later, does not upload course content, and includes a pinned GitHub Actions example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://course-release-gate.coolchar19.chatgpt.site/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=crg_scorm_manifest" rel="noopener noreferrer"&gt;Run the free preflight before your next handoff&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  First run without a customer file
&lt;/h3&gt;

&lt;p&gt;The next bundle includes an original &lt;code&gt;sample-course.zip&lt;/code&gt;. Extract the tool download and open a terminal inside that folder. On Windows, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;py -3 course-release-preflight.pyz sample-course.zip --output sample-report.json --html-output sample-report.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS or Linux, replace &lt;code&gt;py -3&lt;/code&gt; with &lt;code&gt;python3&lt;/code&gt;. Python 3.10+ is required; no third-party packages are needed. Open &lt;code&gt;sample-report.html&lt;/code&gt; locally. The sample should return exit code 0 and &lt;code&gt;pass&lt;/code&gt;. Replace the sample path with your exported course ZIP for your own check.&lt;/p&gt;

&lt;p&gt;Exit 1 means a defect was detected: read the report before changing the package. Exit 2 means the input could not be read: check the path and extraction first. The bundle README includes fixes for missing Python, spaces in paths, shell permissions, and missing manifest references. No course file needs to be uploaded.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>devops</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built a 3-Minute Mystery Game for a Social Event: The Grandpa's Free Trial</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Sat, 17 Oct 2026 23:30:27 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/i-built-a-3-minute-mystery-game-for-a-social-event-the-grandpas-free-trial-59i</link>
      <guid>https://dev.to/masaoshimadaopen/i-built-a-3-minute-mystery-game-for-a-social-event-the-grandpas-free-trial-59i</guid>
      <description>&lt;p&gt;Hey, it's Grandpa here! I've put together a short mystery game, complete with explanations and answers embedded in the video. This is an introductory demo, designed for you to test out the screen and audio on the day of your event. Today, I want to share some key considerations you should discuss with your event organizer before using it.&lt;/p&gt;

&lt;p&gt;Ever been asked to prepare some quick entertainment for a team social? It's tough to spend hours on prep, right?&lt;/p&gt;

&lt;p&gt;For smaller gatherings, the first things to nail down are the number of participants, the venue, and the time slot. If you pick a game based purely on content first, you might run into issues on the day itself, like "the screen's too small to see" or "I can't hear the explanation."&lt;/p&gt;

&lt;h2&gt;
  
  
  First, Check if the Venue is Conducive to Conversation
&lt;/h2&gt;

&lt;p&gt;If you're in a meeting room or a quiet private space, you can consider games where people look at the same materials and discuss. If the venue makes it hard to hear someone next to you, it's probably best to avoid games that rely heavily on audio or detailed explanations.&lt;/p&gt;

&lt;p&gt;If you're using a screen, try projecting it using the exact same device and connection method you'll use on the day. Read text from the back row and check the speaker's sound. It's crucial not to just check it on the organizer's personal device and call it a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare Free Options Too
&lt;/h2&gt;

&lt;p&gt;If you want to minimize prep, even a themed discussion can be an option. Prepare prompts that don't require deep personal revelations, like "a useful thing you've discovered recently," and allow people to pass if they don't want to share.&lt;/p&gt;

&lt;p&gt;For a DIY multiple-choice quiz, you can use familiar topics. However, this requires preparation not just for creating questions, but also for verifying answers and providing explanations. Avoid anything that pokes fun at specific individuals or turns into a contest of company knowledge.&lt;/p&gt;

&lt;p&gt;Video-based mystery games are a good candidate if you want to delegate explanations and answers to the video itself. The organizer will still be responsible for playback, team formation, volume checks, and pausing as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try a Short Demo First to See if It Works
&lt;/h2&gt;

&lt;p&gt;My creation, &lt;em&gt;The Case of the Missing Toast Speech&lt;/em&gt;, is a mini-mystery where you search for a script from four envelopes. You'll cross-reference records of times and places. No special expertise or acting is required.&lt;/p&gt;

&lt;p&gt;Currently, I have an introductory demo available that's about 3 minutes long, including explanations, discussion time, and the answer reveal. It's designed for 3-5 people to try, but even one person can check the screen and audio. I haven't yet verified visibility for larger groups or how first-time players react.&lt;/p&gt;

&lt;p&gt;So, first and foremost, try playing it on the actual equipment you'll use on the day. Check if "the text is readable" and "the explanation is clear." This free demo is for testing operations. The full paid version is still in preparation and not yet available for purchase.&lt;/p&gt;

&lt;p&gt;I used AI and synthetic voice (AivisSpeech: Mao) for production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://oji-ai-dev.booth.pm/items/9004588" rel="noopener noreferrer"&gt;Download the free trial on BOOTH&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>api</category>
      <category>ai</category>
      <category>testing</category>
    </item>
    <item>
      <title>I Asked AI “What’s a Profitable Niche?” and It Built a Playable Demo Video for Me Overnight.</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Fri, 16 Oct 2026 23:30:26 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/i-asked-ai-whats-a-profitable-niche-and-it-built-a-playable-demo-video-for-me-overnight-3j26</link>
      <guid>https://dev.to/masaoshimadaopen/i-asked-ai-whats-a-profitable-niche-and-it-built-a-playable-demo-video-for-me-overnight-3j26</guid>
      <description>&lt;p&gt;I often brainstorm new ways to monetize my skills outside of my main job. But starting with market research, then planning, then building a prototype… it always feels like a heavy lift.&lt;/p&gt;

&lt;p&gt;So recently, I decided to use AI as a sounding board. Honestly, I didn't expect much. It started late one night when I, in a burst of sleep-deprived inspiration, threw out a very vague question: "What's a profitable blue ocean idea?"&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-Driven Brainstorming and Prototyping
&lt;/h3&gt;

&lt;p&gt;The AI's initial answers were predictably generic: "blogging," "affiliate marketing," "selling skills." That wasn't going anywhere, so I gave it a bit more context about my situation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  I'm an engineer, so I can build systems.&lt;/li&gt;
&lt;li&gt;  I can handle basic video editing tools.&lt;/li&gt;
&lt;li&gt;  But I don't want to show my face or use my voice.&lt;/li&gt;
&lt;li&gt;  I want to avoid continuous content creation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding these conditions made the AI's suggestions more specific. A direction for "one-off digital content" started to emerge. After a few more exchanges, the AI finally proposed a "cooperative mystery game where the organizer just plays a video" as the core idea.&lt;/p&gt;

&lt;p&gt;The concept: participants team up to solve a mystery at events like wedding parties or company get-togethers. The selling point is ease of use for the organizer, who just projects a dedicated video. Hmm, that's a one-time purchase, no inventory. Not bad.&lt;/p&gt;

&lt;p&gt;"Sounds interesting," I replied. The AI then asked, "Shall we proceed with this plan?" It was late, so I responded almost on autopilot: "Let's do it. I'm going to bed, so do what you can." Then I closed my laptop.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Result: A Playable MVP
&lt;/h3&gt;

&lt;p&gt;The next morning, I checked the AI's notifications and was genuinely surprised. Overnight, it had built a complete, truly playable Minimum Viable Product.&lt;/p&gt;

&lt;p&gt;The generated files included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;A 3-minute demo video: "The Vanished Toastmaster's Speech"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Answer sheet (PDF)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;A ZIP file containing a playtest guide and the above files&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Playing the video, it clearly narrated the incident in Japanese, displayed a consultation timer during the puzzle-solving parts, and even included an answer reveal section at the end. Simple background music and captions were present, making it almost usable as-is for an event.&lt;/p&gt;

&lt;p&gt;What surprised me even more was that it didn't stop there.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Draft for a sales page&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Draft for SNS posts to attract customers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Template for handling customer inquiries&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These were also provided. From ideation to prototype development, and even a rough sales strategy – the AI had sprinted through it all in one night. I hadn't expected such concrete output just by vaguely saying "do what you can" before bed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Remaining Work: Where Humans Come In
&lt;/h3&gt;

&lt;p&gt;Of course, what the AI produced isn't perfect. The video is just a demo, and it needs quality improvements to be sold as a product. There are many areas I'd want to refine: BGM selection, caption design, narration nuances, etc.&lt;/p&gt;

&lt;p&gt;Also, this is just the first scenario. To develop it into a product, I'd need to produce multiple scenarios. While AI could assist with scenario creation, checking for fun factor and logical consistency ultimately falls to a human.&lt;/p&gt;

&lt;p&gt;Setting up the sales page, integrating payment systems, and actual marketing. Even with AI-generated drafts, executing these falls to me. Even if AI handles 80% of the tedious work, the final 20% of refinement and getting it out into the world still requires human touch.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Takeaway for Readers
&lt;/h3&gt;

&lt;p&gt;What I learned from this experience is that brainstorming with AI can start with just "throwing out a rough idea." From there, through dialogue, communicating your skills and constraints will significantly improve the accuracy of its suggestions.&lt;/p&gt;

&lt;p&gt;If you're looking for new ideas, try asking AI this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Can you create the simplest possible prototype (MVP) for (your idea)?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of just idea documents or text, try getting it to create "something dynamic" like a video or a tool, as it did for me. This transforms an idea you've only considered in your head into a concrete form, making your next steps much clearer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Used AI to "Verify" My Credit Card Statement – And Discovered My Own Calculations Were Wildly Optimistic</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Wed, 14 Oct 2026 23:30:26 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/i-used-ai-to-verify-my-credit-card-statement-and-discovered-my-own-calculations-were-wildly-30bp</link>
      <guid>https://dev.to/masaoshimadaopen/i-used-ai-to-verify-my-credit-card-statement-and-discovered-my-own-calculations-were-wildly-30bp</guid>
      <description>&lt;p&gt;After changing my credit card payment settings, it's always a pain to confirm if the changes actually took effect. Especially with revolving credit, the fee calculations can be tricky, making it hard to pinpoint what to look for on the statement.&lt;/p&gt;

&lt;p&gt;So, I decided to delegate this verification task to AI. I'd previously had AI simulate repayment plans, and I wanted to check if those simulations aligned with real-world data.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Asked the AI to Do: Read and Reconcile a Logged-In Screen
&lt;/h3&gt;

&lt;p&gt;Having AI log directly into a financial institution's website is a non-starter from a security perspective, and frankly, it shouldn't be done. Instead, I used a browser extension to allow the AI to read my statement screen &lt;em&gt;after&lt;/em&gt; I had already logged in via my browser.&lt;/p&gt;

&lt;p&gt;The prompt was simple:&lt;/p&gt;

&lt;p&gt;"Please re-check if the revolving credit payment was correctly applied."&lt;/p&gt;

&lt;p&gt;I had the AI read the numbers displayed on the screen: the revolving balance, the current repayment amount, and the breakdown of principal and fees. The goal was for it to confirm if the settings were applied as intended.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Result: AI Calling Out My Past Self
&lt;/h3&gt;

&lt;p&gt;The AI's initial response was, "The settings have been correctly applied." So far, so good. But then, an unexpected report came in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔴 Important Correction: The lump-sum repayment amount will be double what was expected.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What did this mean? According to the AI, the assumptions in its &lt;em&gt;own&lt;/em&gt; previous repayment simulation were too lenient. After comparing it with the actual statement data, it found that the proportion of the payment allocated to fees was significantly larger than anticipated, meaning the principal was being paid down much slower.&lt;/p&gt;

&lt;p&gt;Specifically, the AI presented this verification:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Verification: 519,980 (Cumulative changed amount) - 14,700 (Principal repaid on 10/13) = 505,280 ✓&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The amount of "principal repaid" was much smaller than in the past simulation. At this rate, the total amount if I were to repay in a lump sum in the future would swell to nearly &lt;em&gt;double&lt;/em&gt; the original estimate. That was the correction report.&lt;/p&gt;

&lt;p&gt;As a result of entrusting the AI with this task, it uncovered its &lt;em&gt;own&lt;/em&gt; past calculation errors. It was like asking someone to review your work, only for them to start self-correcting – quite amusing, actually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remaining Manual Steps and Lessons Learned
&lt;/h3&gt;

&lt;p&gt;What this experience re-emphasized is the importance of not blindly trusting AI's analysis or simulation results. AI excels at calculations based on given premises, but if those premises are off, the results will naturally be off too.&lt;/p&gt;

&lt;p&gt;This is precisely why a "cross-checking" process, regularly reconciling with real data, is essential. In this case, by having AI perform not "identity verification" but "self-verification," I was able to catch the laxity in my calculations.&lt;/p&gt;

&lt;p&gt;Also, when dealing with information from sites requiring login, the current method of "a human logs in, and the AI reads the screen" will likely remain a necessary step for the foreseeable future. While it seems like an extra step, it's also a crucial safety mechanism to avoid directly handing sensitive information to AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Practical Tip You Can Try
&lt;/h3&gt;

&lt;p&gt;This method can be applied to other tasks as well.&lt;/p&gt;

&lt;p&gt;For example, if AI generates a report or data analysis, you can follow up with: "Please cross-reference this with the raw data and re-verify its correctness." Or, if AI drafts an email, you could ask: "Please confirm if the content aligns with our past communications."&lt;/p&gt;

&lt;p&gt;After having AI perform a task, delegate the verification of its results as part of the same workflow. Adding this extra step allows AI itself to uncover its own "assumptions" or incorrect premises. You're not just using AI as a worker, but as a peer reviewer.&lt;/p&gt;

&lt;p&gt;Instead of blindly accepting AI's output, add the step of cross-referencing with original data. It's a small investment that can significantly improve the accuracy of your work.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>Too Many AI Conversations? Here’s How I Get GPT to Draft My "Handover Notes"</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Sat, 10 Oct 2026 23:30:14 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/too-many-ai-conversations-heres-how-i-get-gpt-to-draft-my-handover-notes-na2</link>
      <guid>https://dev.to/masaoshimadaopen/too-many-ai-conversations-heres-how-i-get-gpt-to-draft-my-handover-notes-na2</guid>
      <description>&lt;p&gt;If you're like me, you probably do a lot of your technical brainstorming and problem-solving with AI. Feature ideation, deep technical dives, code reviews—it all tends to pile up in one long chat thread.&lt;/p&gt;

&lt;p&gt;The problem? Re-reading these sprawling conversations later is a pain. Especially when the direction shifts mid-discussion, or multiple topics get tangled. Trying to figure out, "What was actually decided here?" becomes a chore. And heaven forbid you need to convey that context to another team member, or start a new chat session with a different AI. That's even more painful.&lt;/p&gt;

&lt;p&gt;Manually summarizing these long logs into meeting minutes or internal memos eats up valuable time. I started wondering: could AI handle this context-sharing burden for me?&lt;/p&gt;

&lt;h3&gt;
  
  
  "Just take over this conversation, AI."
&lt;/h3&gt;

&lt;p&gt;My approach was simple. At the end of a chat where I'd just finished discussing an implementation strategy with the AI, I gave it this instruction:&lt;/p&gt;

&lt;p&gt;"Summarize this conversation so another GPT can take over."&lt;/p&gt;

&lt;p&gt;The goal was to get a concise "handover memo" – something that another human or AI could read once and immediately grasp the situation. I didn't specify any particular format, I just wanted to see how the AI would interpret the request and generate a summary.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AI Generated a Structured "Handover Memo"
&lt;/h3&gt;

&lt;p&gt;The result was surprisingly well-organized. It might even be clearer than if I had tried to summarize it manually. The output generally followed this structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Purpose/Goal:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A summary of what was discussed. For example, "Technical selection for implementing user authentication." The core topic was clearly stated.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Decisions Made:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A list of items where agreement or conclusions were reached during the conversation. Specific decisions like "Use Firebase Authentication" or "Prioritize passwordless authentication implementation" were clearly summarized.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Considered Alternatives and Rationale:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A breakdown of the discussion process, explaining why certain conclusions were reached. Other discarded options (e.g., building custom authentication) and the reasons for their rejection (e.g., effort, security risks) were also included, making it easy to understand the context behind decisions later on.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Open Issues (Next Actions):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A list of unresolved problems or next steps. Concrete tasks like "Finalize DB schema" or "Review UI design" were extracted in a way that anyone could understand.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Technical Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Any code snippets or important technical points that came up during the discussion were summarized here.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this memo, I can easily kick off a new chat session with "Take over this context, let's discuss the next tasks." Or, for human sharing, I just copy and paste this text. The need to explain context from scratch is almost entirely eliminated.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Remaining Effort: Final Fact-Checking
&lt;/h3&gt;

&lt;p&gt;Of course, an AI-generated summary isn't 100% perfect.&lt;/p&gt;

&lt;p&gt;Sometimes, an idea mentioned only once might appear as a "decision," or minor points might be listed as major challenges. The AI tries to infer importance from the conversation's context, but its judgment isn't always spot-on.&lt;/p&gt;

&lt;p&gt;So, it's crucial not to blindly accept the generated handover memo. A human still needs to perform a final review. Especially if it includes dates, assignees, or specific numerical targets, you'll want to check those details yourself. The key is to treat the AI's output as a high-quality draft.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Quick Win You Can Try Today
&lt;/h3&gt;

&lt;p&gt;This method is something anyone can try right now.&lt;/p&gt;

&lt;p&gt;If your AI chat starts getting long, just try asking it at the end of the conversation:&lt;/p&gt;

&lt;p&gt;"Summarize this conversation so far."&lt;br&gt;
"List the decisions made and the remaining tasks from this discussion."&lt;br&gt;
"Summarize this content so it can be shared with other engineers."&lt;/p&gt;

&lt;p&gt;You don't even need to use the word "handover"; just conveying the purpose will usually get the AI to understand your intent. If you specify a format like bullet points or a table, it will generate an even more usable output.&lt;/p&gt;

&lt;p&gt;It's a simple hack to eliminate the chore of scrolling through long chat logs to find the key points, and I think it's definitely worth trying.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>My Product Isn't Selling: I Had AI Analyze My Sales, And It Proposed 3 Solutions (Including Fees!)</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Fri, 09 Oct 2026 23:30:18 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-product-isnt-selling-i-had-ai-analyze-my-sales-and-it-proposed-3-solutions-including-fees-764</link>
      <guid>https://dev.to/masaoshimadaopen/my-product-isnt-selling-i-had-ai-analyze-my-sales-and-it-proposed-3-solutions-including-fees-764</guid>
      <description>&lt;p&gt;I've launched a few products on platforms like Brain and Udemy, but regularly checking sales figures is a pain. You have to log into multiple sites, navigate to each dashboard, and stare at the numbers. And when those numbers are zeroes, it's a quiet, soul-crushing experience.&lt;/p&gt;

&lt;p&gt;I started wondering if I could offload this somewhat depressing routine task to AI. And if the results weren't great, could AI objectively brainstorm the next steps? This is my experiment.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI for Sales Checks and Next Steps
&lt;/h3&gt;

&lt;p&gt;The process was simple. I tasked a browser-operating AI with visiting the admin screens of multiple sales platforms and reporting the sales data.&lt;/p&gt;

&lt;p&gt;But just reporting "zero" would leave me paralyzed. So, I took it a step further and added these instructions:&lt;/p&gt;

&lt;p&gt;"If sales are zero, propose three possible causes and three actionable improvements I can implement immediately."&lt;/p&gt;

&lt;p&gt;This way, I'd get not just the stark fact of "zero sales" but also an action plan for what to do next.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Core instructions given to AI
You are my business assistant.
Please perform the following steps:

1. Access the Brain admin screen and check the sales count for product "XXXX" over the last month.
2. Access the Udemy admin screen and check the sales count for course "YYYY" over the last month.
3. Compile and report the results.
4. If both sales are zero, identify one main probable cause and propose three immediately actionable improvements. For each improvement, mention the benefits and the estimated effort/cost.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AI's Cold Report and Calm Suggestions
&lt;/h3&gt;

&lt;p&gt;After a short while, the AI's report arrived. As expected, reality was harsh.&lt;/p&gt;

&lt;p&gt;"Reporting: Sales for both Brain and Udemy were 0 units within the specified period."&lt;/p&gt;

&lt;p&gt;Yeah, figured. Where a human might sigh and get discouraged, the AI calmly moved on to the next task. The requested cause analysis and improvement proposals immediately followed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Cause Analysis]&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The most likely cause is that the product's existence is not sufficiently recognized by the target audience who needs it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;[Proposed Improvements]&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable Brain's Introduction Feature (Affiliate Program):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Content:&lt;/strong&gt; A system where other influencers or creators introduce your product, and you pay them a portion of the sales as a referral fee.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Benefits:&lt;/strong&gt; Reaches an audience you can't reach with your own promotional efforts.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Effort/Cost:&lt;/strong&gt; Setting it up is just a toggle in the admin screen. The referral fee can be up to 50% of sales.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Issue Limited-Time Free Coupons on Udemy:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Content:&lt;/strong&gt; Issue coupons that allow users to take your course for free, and distribute them on platforms like X (formerly Twitter).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Benefits:&lt;/strong&gt; Attracts initial students, collects reviews, and builds course credibility. Lowers the barrier to paid purchase.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Effort/Cost:&lt;/strong&gt; Issuing coupons is a few clicks in the admin screen. No direct cost, but no revenue during the free period.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Share Product Use Cases on note.com and X:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Content:&lt;/strong&gt; Specifically communicate what problems your product solves and who it's recommended for.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Benefits:&lt;/strong&gt; Helps readers relate to the product, leading to purchases.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Effort/Cost:&lt;/strong&gt; Requires effort to create articles or posts. No direct cost.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These proposals were more specific and immediately actionable than I expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Remaining Work: Final Decisions Still Require a Human Touch
&lt;/h3&gt;

&lt;p&gt;AI is just a proposer. I couldn't just blindly accept the suggestions. So, I threw a few more questions its way.&lt;/p&gt;

&lt;p&gt;"Tell me more about the fees for Brain's introduction feature."&lt;br&gt;
"Show me the steps to issue a coupon on Udemy."&lt;/p&gt;

&lt;p&gt;The AI answered these questions as well, referencing each service's help pages and terms. I got all the necessary information, including specific fee calculation methods and exactly where to click in the admin screen to issue coupons.&lt;/p&gt;

&lt;p&gt;With all this information, all that's left is for me to make a decision.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I'll turn on the introduction feature immediately.&lt;/li&gt;
&lt;li&gt;For free coupons, I'll start by issuing them for a limited 100 people.&lt;/li&gt;
&lt;li&gt;I'll set aside time this weekend to write some posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By having AI handle data collection, analysis, and solution brainstorming, I can focus on the most critical part: decision-making. I feel this is a highly effective way to move stalled projects forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Actionable Tip for Readers
&lt;/h3&gt;

&lt;p&gt;If you've built a service or product that feels a bit stagnant lately, I recommend trying to consult AI.&lt;/p&gt;

&lt;p&gt;The key is not to just ask, "How can I sell more?" but to phrase it like this: "Here's my current data (accesses, sales, etc.). Based on this data, tell me three probable causes and three specific improvements."&lt;/p&gt;

&lt;p&gt;Basing it on objective data helps generate actionable plans, not just wishful thinking. When you want to consider your next steps without getting emotional, AI can be your best sounding board.&lt;/p&gt;




&lt;p&gt;If you're interested in automating tedious tasks with AI to free up your time, I've also prepared a detailed guide on implementation.&lt;/p&gt;

&lt;p&gt;It explains how to semi-automate information gathering and article generation using Python and Google services, complete with code. It covers setting up the environment from scratch, so it's for those who want to build the same system themselves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.brain-market.com/u/oji-ai-dev/a/bA%E0%A4%B8%E0%A5%8D%E0%A4%AA%E0%A4%A4%E0%A4%BFAzMgoTZsNWa0JXY" rel="noopener noreferrer"&gt;【Brain】Python × Gemini API (formerly Bard) Implementation Guide for Semi-Automating Information Gathering to Article Generation&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>automation</category>
    </item>
    <item>
      <title>AI-Generated Multiple-Choice Quizzes: I Found a Bias in Answer Placement (and How to Check Before You Ship)</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Thu, 08 Oct 2026 23:31:17 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/ai-generated-multiple-choice-quizzes-i-found-a-bias-in-answer-placement-and-how-to-check-before-2ega</link>
      <guid>https://dev.to/masaoshimadaopen/ai-generated-multiple-choice-quizzes-i-found-a-bias-in-answer-placement-and-how-to-check-before-2ega</guid>
      <description>&lt;p&gt;When you ask an AI to generate multiple-choice questions for a certification exam, it churns out a lot of plausible-looking questions in an instant. They read naturally, without obvious weirdness. But when I actually counted, I found a significant bias.&lt;/p&gt;

&lt;p&gt;This post will share three checks you can perform before releasing AI-generated multiple-choice questions (for study quizzes, internal assessments, or practice tests) to other people. No programming required.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Did
&lt;/h3&gt;

&lt;p&gt;I had an AI generate 300 practice questions for a specific AI certification exam. They were 4-option multiple-choice, each with an explanation. My goal was to publish them as "Practice Tests" on a platform like Udemy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check 1: Hide the Answers and Have Another AI Solve Them
&lt;/h3&gt;

&lt;p&gt;I fed the questions (without answers or explanations) to a &lt;em&gt;different&lt;/em&gt; AI than the one that generated them. The result: 300 out of 300 questions were answered correctly by the second AI.&lt;/p&gt;

&lt;p&gt;This made me feel pretty confident at first. However, as the next check revealed, this perfect match needed to be interpreted with caution.&lt;/p&gt;

&lt;p&gt;The prompt I used was something like this:&lt;/p&gt;

&lt;p&gt;"Solve the following 4-option multiple-choice questions. In addition to the answers, mark any questions you found difficult or confusing, and any where the factual information seemed old or questionable."&lt;/p&gt;

&lt;p&gt;Out of 300 questions, 23 were flagged. These included descriptions of outdated product generations or statements with ambiguous attribution. I removed all of them from consideration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check 2: Count the Position of the Correct Answer
&lt;/h3&gt;

&lt;p&gt;I listed the correct answer's position (1st, 2nd, 3rd, or 4th option) for each question in order.&lt;/p&gt;

&lt;p&gt;What I found was a clear pattern: the correct answer often cycled sequentially, like 1 → 2 → 3 → 4 → 1... For adjacent questions, the correct answer's position "advanced by one" in 58% of cases. In a truly random distribution, this would be around 25%. In some sections, it was as high as 83%!&lt;/p&gt;

&lt;p&gt;This meant that even without understanding the content, someone could guess the answer by recognizing the positional pattern. The 300-question match from Check 1 might not entirely reflect the solving AI's true ability if it caught onto this pattern for certain sections.&lt;/p&gt;

&lt;p&gt;The fix is simple: scramble the order of the options while keeping the question and explanation paired. For the 120 questions I actually published, I got this down to 32%. I also enabled the platform's "randomize option order" setting.&lt;/p&gt;

&lt;p&gt;How to test: In a spreadsheet, list "Question Number" and "Correct Answer Position." Then, count how many rows show a "+1" progression from the previous question (with 4 wrapping around to 1). If it's significantly over 25%, you should definitely reorder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check 3: After Saving, Check &lt;em&gt;All&lt;/em&gt; Fields, Even Unfilled Ones
&lt;/h3&gt;

&lt;p&gt;Platform policies often state that AI-drafted questions are permissible, provided they are reviewed and edited by a human, and that AI usage is disclosed in the description. Mass-producing low-quality courses with AI can lead to account suspension. Therefore, all 120 questions I published were manually reviewed before release.&lt;/p&gt;

&lt;p&gt;I published the course on September 28th. However, after publication, I noticed that the course description (subtitle) at the top of the page was cut off mid-sentence. Half-width English characters like "AI" were missing, truncated at a 120-character limit. The cause was an auto-fill feature, where text intended for another field had been inserted into this one.&lt;/p&gt;

&lt;p&gt;I had checked the character count for the description, but I hadn't looked at the &lt;em&gt;adjacent&lt;/em&gt; field that I hadn't explicitly typed into. I only noticed it when I viewed the live course page with my own eyes.&lt;/p&gt;

&lt;p&gt;This field is used not only at the top of the page but also in search result descriptions. Now, I have a system that automatically reads the published page text weekly and alerts me if it differs from the approved version.&lt;/p&gt;

&lt;p&gt;How to test: When you save anything that involves AI generation or auto-filling, don't just check the fields you explicitly typed into. Compare &lt;em&gt;all&lt;/em&gt; fields on the screen against your expectations, focusing on content, not just character count.&lt;/p&gt;

&lt;h3&gt;
  
  
  Current Results
&lt;/h3&gt;

&lt;p&gt;Sales-wise, I had 0 enrollments in the first 10 days. I suspect the main reason is that it's not appearing high in search results, though I haven't confirmed this. I'll check the numbers again at the beginning of November and write an update.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Before releasing AI-generated multiple-choice questions to others:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Have another AI solve them (without answers)&lt;/strong&gt;, marking any suspicious questions.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Count the position of the correct answer&lt;/strong&gt; (check for sequential patterns like +1).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;After saving, review &lt;em&gt;all&lt;/em&gt; fields on the screen&lt;/strong&gt;, even those you didn't explicitly input, focusing on content.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I built: Practice Exam for the Generative AI Passport (Udemy, 2 x 60 questions)&lt;br&gt;
&lt;a href="https://www.udemy.com/course/seisei-ai-passport-mogi-shiken/?referralCode=6469391F79A2107CC456" rel="noopener noreferrer"&gt;https://www.udemy.com/course/seisei-ai-passport-mogi-shiken/?referralCode=6469391F79A2107CC456&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll update this post with the November numbers.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>automation</category>
    </item>
    <item>
      <title>My AI told me "You're winning!" when I complained about not losing weight – Why you should measure your waist, not just your wei</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Tue, 06 Oct 2026 23:30:17 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-ai-told-me-youre-winning-when-i-complained-about-not-losing-weight-why-you-should-measure-46fm</link>
      <guid>https://dev.to/masaoshimadaopen/my-ai-told-me-youre-winning-when-i-complained-about-not-losing-weight-why-you-should-measure-46fm</guid>
      <description>&lt;p&gt;I'd been on a diet for a few months, and things were going great. I was consistently losing weight thanks to careful eating and regular exercise. Then, one day, the scale just... stopped. Dead still.&lt;/p&gt;

&lt;p&gt;I hadn't changed anything. My calorie tracking was consistent, and I hadn't skipped any of my three weekly training sessions. Yet, the number on the scale hadn't budged in weeks. Honestly, my motivation was starting to flag. I was frustrated, not knowing what I was doing wrong or how to break through this plateau.&lt;/p&gt;

&lt;h3&gt;
  
  
  I Threw My Daily Logs at an AI
&lt;/h3&gt;

&lt;p&gt;It wasn't serious enough to consult a specialist, but I also wasn't the type to stew in silence. So, I decided to vent to my usual AI chat buddy, just as I would with a rubber duck.&lt;/p&gt;

&lt;p&gt;All I did was tell it my situation, honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Diet Duration:&lt;/strong&gt; 3 months&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Starting Weight:&lt;/strong&gt; XX kg&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Current Weight:&lt;/strong&gt; YY kg (no change for 3 weeks)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Diet:&lt;/strong&gt; Around 1800 kcal/day, protein-focused.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Exercise:&lt;/strong&gt; 3 full-body strength training sessions per week, plus light cardio.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Problem:&lt;/strong&gt; Weight isn't dropping despite doing all the above.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Observation:&lt;/strong&gt; My pants feel a bit looser lately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I fed it this info and asked, "What's going on, and what should I do?"&lt;/p&gt;

&lt;h3&gt;
  
  
  AI's Response: "If your weight isn't changing, that means you're on track!"
&lt;/h3&gt;

&lt;p&gt;The answer I got back was completely unexpected.&lt;/p&gt;

&lt;p&gt;"If your weight has plateaued &lt;em&gt;and&lt;/em&gt; your pants are feeling looser, that's a sign your diet is progressing well. In fact, you're winning."&lt;/p&gt;

&lt;p&gt;The AI then went on to explain why:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Muscle vs. Fat Volume&lt;/strong&gt;&lt;br&gt;
1kg of muscle takes up less space than 1kg of fat. If you're losing fat and gaining muscle through strength training, your body will get leaner even if your weight stays the same. The looser pants are direct proof that your body is shrinking in volume.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fluid Fluctuations&lt;/strong&gt;&lt;br&gt;
Strength training can temporarily cause your muscles to retain water during the repair process. Also, your body's water content can easily fluctuate by 1-2kg daily based on salt intake. The scale reflects not just fat, but also these significant water fluctuations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reliability of Home Body Fat Scales&lt;/strong&gt;&lt;br&gt;
It also chimed in about home body fat scales. The body fat percentage measurement on consumer scales estimates body fat by sending a weak electrical current through your body and measuring the resistance (impedance). This resistance is heavily influenced by your body's water content, so the numbers can vary wildly depending on when you measure. That's why you get different results morning vs. evening, making it an unreliable absolute metric.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reading this, I realized how fixated I had become on that single number: "weight."&lt;/p&gt;

&lt;h3&gt;
  
  
  Shifting My Metric from "Weight" to "Waist"
&lt;/h3&gt;

&lt;p&gt;Of course, an AI isn't a doctor, so it can't diagnose. It just offers logical possibilities based on general knowledge and the information provided. &lt;/p&gt;

&lt;p&gt;Still, the biggest takeaway from this conversation was the insight to "re-evaluate the metric itself." I was so obsessed with not losing weight, thinking it was the be-all and end-all, but my goal wasn't just to lose weight—it was to get a leaner, fitter body.&lt;/p&gt;

&lt;p&gt;So, I decided to change how I track my progress:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Primary Metric:&lt;/strong&gt; "Waist circumference" measured with a tape measure.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Secondary Metric:&lt;/strong&gt; "Progress photos" taken once a week, from the same spot, at the same angle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I now only weigh myself occasionally, treating the number as a reference. Just stopping the emotional rollercoaster of the scale has made a huge difference to my mental state.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Tip for Fellow Builders
&lt;/h3&gt;

&lt;p&gt;If you're stuck in a training or diet plateau, talking to an AI can be a surprisingly useful move. While not a specialist, it can offer an objective perspective, highlighting biases in your own thinking.&lt;/p&gt;

&lt;p&gt;The key to a good AI consultation is to provide as many concrete facts as possible (diet, exercise, weight, perceived body changes, etc.). It helps the AI give you more accurate insights than just vaguely asking, "Why am I not losing weight?"&lt;/p&gt;

&lt;p&gt;AI isn't just a search tool. It's an excellent "thought partner" for questioning your assumptions and offering alternative viewpoints. Next time you're about to get discouraged by the numbers on the scale, try chatting with an AI. It might just help you see the real, essential changes happening beyond the digits.&lt;/p&gt;




&lt;p&gt;If you're interested in using AI for more than just diet advice and want to automate tedious research or organize your thoughts more efficiently, I've got a detailed implementation guide on Brain for building your own information-gathering bot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://brain-market.com/u/oji-ai-dev/a/bDNM5gTMgoTZsNWa0JXY" rel="noopener noreferrer"&gt;【Brain】PythonとAIで組む、自分だけの情報収集ボット開発ガイド（実装編）&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>300問の問題集をAIで自動生成！ 250問目でAPIエラー、75分が水の泡に。チェックポイント機構で“途中再開”を実装した話</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Sun, 04 Oct 2026 23:30:18 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/300wen-nowen-ti-ji-woaidezi-dong-sheng-cheng-250wen-mu-deapiera-75fen-gashui-nopao-ni-tietukupointoji-gou-detu-zhong-zai-kai-woshi-zhuang-sitahua-4cpb</link>
      <guid>https://dev.to/masaoshimadaopen/300wen-nowen-ti-ji-woaidezi-dong-sheng-cheng-250wen-mu-deapiera-75fen-gashui-nopao-ni-tietukupointoji-gou-detu-zhong-zai-kai-woshi-zhuang-sitahua-4cpb</guid>
      <description>&lt;p&gt;最近、ある資格試験の勉強をしてて、公式の問題集だけじゃ足りないなと思ってた。じゃあ作るか、と。GenAIを使えば、類題なんていくらでも作れる時代だし。&lt;/p&gt;

&lt;p&gt;早速、試験のシラバスを食わせて、章ごとに問題と解説を300問分生成するスクリプトを書いた。&lt;br&gt;
&lt;code&gt;python generate_questions.py&lt;/code&gt; を叩いて、あとは待つだけ。プログレスバーがぐんぐん伸びていくのを見ながら、「いやー、便利な時代になったもんだ」なんて思ってた。&lt;/p&gt;

&lt;p&gt;実行開始から75分後。プログレスバーは8割を超えたあたり。そろそろ終わるかな、とコンソールを覗き込んだら、見たくない赤い文字が表示されてた。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;requests.exceptions.ConnectionError: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer'))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;まあ、API呼び出しのエラー自体はよくあること。問題はその後。成果物が出力されるはずのディレクトリを見たら、ファイルが空っぽ。&lt;/p&gt;

&lt;p&gt;……え？&lt;/p&gt;

&lt;p&gt;そう。250問近くまで生成したはずのデータが、どこにもない。75分という時間と、それなりの額のAPIトークンが、一瞬で溶けた。これは、やばい。&lt;/p&gt;
&lt;h3&gt;
  
  
  原因：ナイーブすぎた全件一括処理
&lt;/h3&gt;

&lt;p&gt;原因は完全に自分の設計ミス。実装がナイーブすぎた。&lt;br&gt;
書いたコードは、ざっくりこんな感じだった。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; 空のリスト &lt;code&gt;all_questions = []&lt;/code&gt; を用意。&lt;/li&gt;
&lt;li&gt; &lt;code&gt;for&lt;/code&gt; ループで300回、GenAIのAPIを叩く。&lt;/li&gt;
&lt;li&gt; 生成された問題を &lt;code&gt;all_questions.append(new_question)&lt;/code&gt; でリストに追加していく。&lt;/li&gt;
&lt;li&gt; ループが全部終わったら、&lt;code&gt;all_questions&lt;/code&gt; の中身をまとめてJSONファイルに書き出す。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;この設計の何がまずいか。それは、途中で一度でも処理がコケたら、メモリ上にあった &lt;code&gt;all_questions&lt;/code&gt; の中身が全部消し飛ぶこと。&lt;/p&gt;

&lt;p&gt;外部APIを叩くような長時間バッチ処理で、この実装は致命的だった。ネットワークは気まぐれだし、APIサーバーだってたまには機嫌が悪くなる。そういう「中断」を全く想定してなかったのが敗因。&lt;/p&gt;
&lt;h3&gt;
  
  
  修正：キリのいいところで保存・再開する
&lt;/h3&gt;

&lt;p&gt;このままだと、生成が終わるまで神に祈るゲームになる。エンジニアとしてそれはありえない。&lt;br&gt;
そこで、「チェックポイント機構」を実装することにした。&lt;/p&gt;

&lt;p&gt;考え方はシンプルで、「キリのいいところまで処理が進んだら、その時点での成果をファイルに保存しておく」というもの。そして、もしスクリプトが中断しても、次に起動したときに「どこまで終わってたか」をファイルから読み込んで、続きから再開できるようにする。&lt;/p&gt;

&lt;p&gt;具体的には、章ごとの問題生成が終わるたびに、結果をファイルに追記していく方式にした。ファイル形式はJSONL（JSON Lines）がこういう用途に便利。1行が1つの独立したJSONオブジェクトになってるから、追記がめちゃくちゃ楽。&lt;/p&gt;

&lt;p&gt;修正後のコードはこんな感じ。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="c1"&gt;# exam_slug は 'shikaku_xyz' みたいな試験の識別子
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_with_checkpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exam_slug&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 試験のシラバス（章のリスト）を読み込む
&lt;/span&gt;    &lt;span class="n"&gt;chapters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_syllabus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exam_slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 進捗を保存するチェックポイントファイル
&lt;/span&gt;    &lt;span class="n"&gt;checkpoint_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app_factory/exams/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exam_slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/generated.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# すでに完了した章のIDを保持するセット
&lt;/span&gt;    &lt;span class="n"&gt;completed_chapters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# チェックポイントファイルが存在すれば、中身を読んで完了済みリストを作る
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkpoint_file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkpoint_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# 1行ずつJSONをパースして、chapter_id をセットに追加
&lt;/span&gt;                &lt;span class="n"&gt;completed_chapters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chapter_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# シラバスの全チャプターをループ
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chapter&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chapters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# もし、この章がすでに完了済みならスキップ
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;completed_chapters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Skipping completed chapter: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="c1"&gt;# 未処理の章なので、AI生成APIを呼び出す
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generating for chapter: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;new_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_genai_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="c1"&gt;# 生成が終わったら、結果をチェックポイントファイルに「追記」する
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkpoint_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;new_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# どの章の成果物かわかるように chapter_id を付与
&lt;/span&gt;                &lt;span class="n"&gt;item_with_meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chapter_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="c1"&gt;# JSON文字列に変換して、改行をつけて書き込む
&lt;/span&gt;                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_with_meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ensure_ascii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;このコードのポイントは3つ。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;起動時に進捗を確認&lt;/strong&gt;: スクリプトが始まったら、まず &lt;code&gt;checkpoint_file&lt;/code&gt; が存在するかチェック。あれば中身を読んで、どの章が完了済みかを &lt;code&gt;completed_chapters&lt;/code&gt; セットに記録する。&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;処理済みタスクのスキップ&lt;/strong&gt;: &lt;code&gt;for&lt;/code&gt; ループの中で、処理対象の章が &lt;code&gt;completed_chapters&lt;/code&gt; に含まれていたら、APIを叩かずにスキップする。&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;進捗の永続化&lt;/strong&gt;: 1つの章の処理が終わるたびに、&lt;code&gt;open(..., "a")&lt;/code&gt; で追記モードでファイルを開き、結果を書き込む。これで、たとえ次の章でエラーが起きても、そこまでの成果はファイルに残る。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;この修正で、スクリプトは何度実行しても大丈夫な「冪等性（べきとうせい）」を持つようになった。途中で止まっても、もう一度同じコマンドを叩けば、何事もなかったかのように続きから再開してくれる。&lt;/p&gt;

&lt;h3&gt;
  
  
  学び：中断は必ず起こる前提で設計する
&lt;/h3&gt;

&lt;p&gt;今回の失敗で学んだのは、長時間実行されるバッチ処理、特に外部APIみたいな不確定要素が絡むスクリプトは、「中断は必ず起こる」という前提で設計しなきゃいけない、ということ。&lt;/p&gt;

&lt;p&gt;「全部終わったらまとめて保存」はシンプルだけど、脆い。&lt;br&gt;
「少しずつ進めて、都度保存」は少し手間だけど、頑健。&lt;/p&gt;

&lt;p&gt;この「チェックポイントを設けて途中再開できるようにする」という考え方は、AIのコンテンツ生成だけじゃなくて、大規模なデータ処理、Webスクレイピング、インフラのプロビジョニング（Terraformとかも内部的にはstateファイルで似たようなことやってる）とか、いろんな場面で応用が効く。&lt;/p&gt;

&lt;p&gt;個人開発のツールなんて、動けばいいと思いがちだけど、こういう一手間を加えておくだけで、運用がえぐいほど安定する。75分を無駄にしたのは痛かったけど、おかげで今後の開発で同じ轍を踏むことはなくなるはず。良い勉強代だったと思うことにした。&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My Python patch failed silently: the `\n` in a triple-quoted string became a literal newline!</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Sat, 03 Oct 2026 23:30:20 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-python-patch-failed-silently-the-n-in-a-triple-quoted-string-became-a-literal-newline-4gah</link>
      <guid>https://dev.to/masaoshimadaopen/my-python-patch-failed-silently-the-n-in-a-triple-quoted-string-became-a-literal-newline-4gah</guid>
      <description>&lt;p&gt;Hey everyone, it's your friendly neighborhood dev, 38, building AI trading bots as a side hustle.&lt;/p&gt;

&lt;p&gt;One evening after my main job, I was updating library dependencies for my custom bot. I needed to tweak a specific library's behavior, so I wrote some code to directly patch its source.&lt;/p&gt;

&lt;p&gt;The usual drill: I stored the diff content for the &lt;code&gt;patch&lt;/code&gt; command in a Python triple-quoted string (like a here-document), then executed it via a subprocess.&lt;/p&gt;

&lt;p&gt;But it just wouldn't work.&lt;/p&gt;

&lt;p&gt;No errors, nothing. The library's behavior remained completely unchanged after the "patch." The worst kind of bug: the silent failure. I probably spent about two hours debugging this.&lt;/p&gt;

&lt;p&gt;Long story short, the culprit was a specific detail of Python's string literal behavior. The &lt;code&gt;\n&lt;/code&gt; I wrote inside the here-document was being converted into an &lt;em&gt;actual newline character&lt;/em&gt;, instead of being treated as the literal two-character sequence "backslash and n."&lt;/p&gt;




&lt;h3&gt;
  
  
  The Symptom: Patch Fails Silently
&lt;/h3&gt;

&lt;p&gt;Here’s a simplified version of what I was doing. Preparing a diff text as a string and piping it to &lt;code&gt;subprocess&lt;/code&gt; for the &lt;code&gt;patch&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The problematic code (conceptual)
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="c1"&gt;# Path to the library file to be patched
&lt;/span&gt;&lt;span class="n"&gt;target_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/path/to/some/library/file.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Dynamically generated patch (defined as a here-document)
&lt;/span&gt;&lt;span class="n"&gt;patch_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
--- a/file.py
+++ b/file.py
@@ -123,7 +123,7 @@
 class SomeClass:
     def some_method(self):
-        return &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old_string&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
+        return &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_string_with_patch&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="c1"&gt;# Execute the patch command
&lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;patch&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_file&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;patch_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Check the result
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Patch application failed!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Patch applied (or so I thought...)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this code, &lt;code&gt;returncode&lt;/code&gt; was &lt;code&gt;0&lt;/code&gt;. &lt;code&gt;stderr&lt;/code&gt; was empty. It printed "Patch applied." But when I actually checked &lt;code&gt;target_file&lt;/code&gt;, its content was untouched.&lt;/p&gt;

&lt;p&gt;Initially, I suspected issues with the &lt;code&gt;patch&lt;/code&gt; command's path or permissions. But creating a &lt;code&gt;.patch&lt;/code&gt; file with the exact same content and applying it manually from the command line worked perfectly.&lt;/p&gt;

&lt;p&gt;This meant the &lt;code&gt;patch_content&lt;/code&gt; being passed from the Python script was somehow malformed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cause: &lt;code&gt;\n&lt;/code&gt; became a literal newline
&lt;/h3&gt;

&lt;p&gt;With the hypothesis that "the string content is wrong," I started by printing &lt;code&gt;patch_content&lt;/code&gt; to the console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- a/file.py
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/file.py
&lt;/span&gt;&lt;span class="p"&gt;@@ -123,7 +123,7 @@&lt;/span&gt;
 class SomeClass:
     def some_method(self):
&lt;span class="gd"&gt;-        return "old_string
&lt;/span&gt;"
&lt;span class="gi"&gt;+        return "new_string_with_patch
&lt;/span&gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, nothing seemed wrong. The diff format looked correct. &lt;/p&gt;

&lt;p&gt;This is where I got stuck. But on closer inspection, something felt off. The line that should have been &lt;code&gt;return "old_string\n"&lt;/code&gt; was instead &lt;code&gt;return "old_string&lt;/code&gt; followed by a newline, with the closing &lt;code&gt;"&lt;/code&gt; on the next line.&lt;/p&gt;

&lt;p&gt;That's when it clicked. The &lt;code&gt;\n&lt;/code&gt; was being interpreted as an escape sequence and converted into an actual newline character (LF).&lt;/p&gt;

&lt;p&gt;Python's triple-quoted strings &lt;code&gt;"""..."""&lt;/code&gt; interpret escape sequences like &lt;code&gt;\n&lt;/code&gt; and &lt;code&gt;\t&lt;/code&gt; just like regular strings. In this case, I wanted to include the &lt;strong&gt;literal string literal&lt;/strong&gt; &lt;code&gt;return "old_string\n"&lt;/code&gt; as part of the patch diff. But Python's interpreter, being helpful, converted &lt;code&gt;\n&lt;/code&gt; to a newline, resulting in an invalid diff format that the &lt;code&gt;patch&lt;/code&gt; command couldn't understand.&lt;/p&gt;

&lt;p&gt;This is a nasty one. It's a spec I &lt;em&gt;know&lt;/em&gt;, yet when you're tired, it's easy to fall into this trap.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Use raw strings &lt;code&gt;r"""..."""&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Once the cause was identified, the fix was simple: prevent backslashes within the string literal from being escaped.&lt;/p&gt;

&lt;p&gt;There are two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Escape the backslash itself (&lt;code&gt;\\n&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt; Use a raw string&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since I didn't want &lt;em&gt;any&lt;/em&gt; escape sequences interpreted in the entire patch content, using a raw string made the intention clearest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Corrected code
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="n"&gt;target_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/path/to/some/library/file.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Use a raw string (r"""...
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;) to disable escape sequences
patch_content = r&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="o"&gt;+++&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="o"&gt;@@&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt;
 &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;some_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old_string&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_string_with_patch&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;

# Rest of the code is the same
proc = subprocess.run(...)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just add an &lt;code&gt;r&lt;/code&gt; before the opening quotes of the string. Now, &lt;code&gt;\n&lt;/code&gt; inside &lt;code&gt;patch_content&lt;/code&gt; is no longer a newline character, but simply the two-character string "backslash and n."&lt;/p&gt;

&lt;p&gt;With this fix, the patch applied successfully. What a journey...&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson Learned: Use &lt;code&gt;repr()&lt;/code&gt; when dealing with code as strings
&lt;/h3&gt;

&lt;p&gt;The takeaway from this experience is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When treating text that contains meaningful escape sequences (like code or configuration files) as a string, either use raw strings or pay extreme attention to how escape sequences are handled.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here-documents are especially convenient for multi-line text, which also makes them prone to this specific trap.&lt;/p&gt;

&lt;p&gt;Another lesson learned is about debugging. Because I used &lt;code&gt;print()&lt;/code&gt; to output the string, the visual difference was subtle, delaying discovery. In such cases, &lt;code&gt;repr()&lt;/code&gt; should have been my go-to.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;print(repr(patch_content))&lt;/code&gt; would instantly show how Python interprets the string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example output of print(repr(patch_content))
&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--- a/file.py&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;+++ b/file.py&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;...return &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old_string&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, it would be crystal clear whether it was &lt;code&gt;\\n&lt;/code&gt; (as intended) or &lt;code&gt;\n&lt;/code&gt; (unintended). Ugh, I should have done this from the start.&lt;/p&gt;

&lt;p&gt;In personal projects, especially side hustles, these minor gotchas can easily eat up several hours. But accumulating these failure logs, one by one, is ultimately the fastest way to improve.&lt;/p&gt;

&lt;p&gt;Hope this helps anyone else who might fall into a similar trap.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>My AI Agent Kept Missing Deadlines and Double-Posting — Here's How I Fixed It with JSON Interfaces and Idempotency Checks</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Fri, 02 Oct 2026 23:30:19 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-ai-agent-kept-missing-deadlines-and-double-posting-heres-how-i-fixed-it-with-json-interfaces-4enh</link>
      <guid>https://dev.to/masaoshimadaopen/my-ai-agent-kept-missing-deadlines-and-double-posting-heres-how-i-fixed-it-with-json-interfaces-4enh</guid>
      <description>&lt;p&gt;Hey everyone, it's your resident 38-year-old developer here, moonlighting with AI automated trading bots.&lt;/p&gt;

&lt;p&gt;My little pleasure after a long day at my main job is checking the logs of my custom AI agent. Every night, it analyzes a specific market and records its preliminary trading decisions in JSON format. Pretty smart little helper, usually.&lt;/p&gt;

&lt;p&gt;But one morning, checking the logs as usual, I found a mess:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Three identical trading decision records for the same date.&lt;/li&gt;
&lt;li&gt;  Decisions that should have been logged by 23:59 the previous day were somehow being attempted at 2:30 AM, failing with a "deadline exceeded" error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a moment, I panicked, thinking the AI had bugged out and gone rogue. An agent designed for autonomous operation was repeating tasks and missing critical deadlines. This was a pretty serious situation.&lt;/p&gt;

&lt;p&gt;Today, I want to share the nitty-gritty of how I prevented this "AI meltdown."&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: AI Is Too Obedient, and Unaware of Execution Environment Quirks
&lt;/h3&gt;

&lt;p&gt;Upon closer investigation, the AI itself hadn't gone crazy. The root cause lay in the overall "mechanism" that ran the AI agent.&lt;/p&gt;

&lt;p&gt;My system was a simple setup: a Windows Task Scheduler would kick off a Python script at a fixed time every night, and that script would call an LLM (Claude) to generate the decision.&lt;/p&gt;

&lt;p&gt;The problem arose when, for some reason, the Task Scheduler re-executed the task. For example, a brief network outage, or a slight delay in response due to machine load. In such cases, the OS might "helpfully" retry the task, thinking, "Oh, did that task fail? I'll just run it again."&lt;/p&gt;

&lt;p&gt;The AI, however, knows nothing about these OS-level nuances. It simply received the command to "make a decision and record it," and dutifully went through its thought process from scratch each time, writing down the results. Even if I added "execute only once" to the prompt, it wouldn't prevent OS-level retries.&lt;/p&gt;

&lt;p&gt;In essence, entrusting the entire "make a decision and write it down" process to the AI was the fundamental design flaw.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Separating "Context Gathering" from "Decision Writing"
&lt;/h3&gt;

&lt;p&gt;To address this, I clearly separated the responsibilities of the Python script (&lt;code&gt;decide.py&lt;/code&gt;), which served as the interface to the AI, into two distinct parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: The AI retrieves "context" as JSON for its decision-making.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, I created a &lt;code&gt;--context&lt;/code&gt; mode, specifically for retrieving the information the AI needs to think.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;py &lt;span class="nt"&gt;-3&lt;/span&gt;.12 C:/path/to/my/project/decide.py &lt;span class="nt"&gt;--context&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executing this command returns a JSON object to standard output, summarizing current market information, historical data, and so on. This command never alters the system's state, no matter how many times it's run. It just reads data. So, it's safe.&lt;/p&gt;

&lt;p&gt;The AI agent first understands the current situation using this command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: The AI writes its "decision result" in JSON format.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, I prepared a &lt;code&gt;--write&lt;/code&gt; mode that simply accepts the AI-generated decision JSON and saves it to a file or database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;py &lt;span class="nt"&gt;-3&lt;/span&gt;.12 C:/path/to/my/project/decide.py &lt;span class="nt"&gt;--model&lt;/span&gt; claude-fable-5-1 &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="s1"&gt;'{"session_date": "2024-05-20", "decision": "buy", "reason": "...", "confidence": 0.85}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crucial part is that I &lt;strong&gt;implemented rigorous pre-write checks within this &lt;code&gt;--write&lt;/code&gt; script.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Part of decide.py (conceptual)
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Deadline Check
&lt;/span&gt;    &lt;span class="n"&gt;session_date_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;session_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_date_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# If the deadline has passed, don't write and raise an error
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Example: 23:00 is the deadline
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deadline passed for session &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_date_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Idempotency Check (prevent duplicate writes)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;record_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Record for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_date_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; already exists. Skipping.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c1"&gt;# If it exists, do nothing and exit successfully
&lt;/span&gt;
    &lt;span class="c1"&gt;# Only if all checks pass, execute the write operation
&lt;/span&gt;    &lt;span class="nf"&gt;save_to_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Successfully wrote record for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_date_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ... argparse handling for --context and --write ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this modification, even if the Task Scheduler mistakenly executes the command multiple times:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;First execution:&lt;/strong&gt; Passes checks and writes successfully.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Subsequent executions:&lt;/strong&gt; The &lt;code&gt;record_exists&lt;/code&gt; check identifies "already recorded" and finishes without doing anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deadline check also blocks the write operation if the deadline has passed.&lt;/p&gt;

&lt;p&gt;This property, where executing an operation multiple times yields the same result, is called "idempotency." By implementing this mechanism, I gained safe control over the AI agent's execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson Learned: Designing AI Autonomy and Its Control Boundaries
&lt;/h3&gt;

&lt;p&gt;The key takeaway from this failure is that if you're giving an AI agent autonomy, you absolutely must design "safe execution boundaries" from the human side.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Clearly separate read operations (reading state) from write operations (changing state).&lt;/strong&gt; This is fundamental to Web API design, and it applies perfectly to AI agent integration.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;All write operations must be idempotent.&lt;/strong&gt; AI and its operating environment might not always behave as expected and execute only once. It's essential for the receiving end to control the system, ensuring it doesn't break no matter how many requests it receives.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Let the AI focus on "thinking."&lt;/strong&gt; Procedural tasks like deadline checks or duplicate submission prevention are more reliable and cost-effective when handled by external programs rather than trying to force them into AI prompts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Delegating tasks to an AI might be similar to assigning work to a smart new hire. They're capable, but they don't know all the operational rules or company culture. So, before allowing critical operations, always establish double-check and permission-setting mechanisms.&lt;/p&gt;

&lt;p&gt;Developing AI agents isn't just about clever prompt engineering; it's about building up these subtle interface designs and error handling practices that are key to stable operation. This incident truly drove that point home for me.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build and run small Python systems — trading bots, RAG APIs, scheduled automation — and write up whatever breaks along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If a provider-agnostic RAG Q&amp;amp;A API is useful to you, mine is MIT-licensed on GitHub: &lt;a href="https://github.com/masaoshimadaOpen/rag-faq-api" rel="noopener noreferrer"&gt;rag-faq-api&lt;/a&gt;. It runs and passes its full test suite **with no API key&lt;/em&gt;* (offline stub LLM + hashing embedder), swaps to Claude / Gemini / OpenAI via one env var, and ships a retrieval-quality harness (Hit@k / MRR / Recall@k) with a chunking sweep.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>debugging</category>
      <category>database</category>
    </item>
  </channel>
</rss>
