<?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: kohei matsunobu</title>
    <description>The latest articles on DEV Community by kohei matsunobu (@koheip).</description>
    <link>https://dev.to/koheip</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%2F4190326%2F1903be46-5d21-4631-9c60-2ad32b3a8b7b.jpg</url>
      <title>DEV Community: kohei matsunobu</title>
      <link>https://dev.to/koheip</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/koheip"/>
    <language>en</language>
    <item>
      <title>What Happens When You Tell an LLM a Story Backward?</title>
      <dc:creator>kohei matsunobu</dc:creator>
      <pubDate>Sat, 07 Nov 2026 10:58:12 +0000</pubDate>
      <link>https://dev.to/koheip/what-happens-when-you-tell-an-llm-a-story-backward-1hj0</link>
      <guid>https://dev.to/koheip/what-happens-when-you-tell-an-llm-a-story-backward-1hj0</guid>
      <description>&lt;h2&gt;
  
  
  Fluent answers are not the whole story
&lt;/h2&gt;

&lt;p&gt;When I build with an LLM, I usually start with practical questions: Is the answer useful? Can someone check its sources? Does it make sense?&lt;/p&gt;

&lt;p&gt;That last question hides several different problems. An answer can be grammatically correct and still put events in a strange order. How could we measure that distinction without simply asking another model to judge it?&lt;/p&gt;

&lt;p&gt;I'm Kohei Matsunobe, a second-year correspondence student at ZEN University. I build KamiMeg, an iOS goshuin (shrine-stamp) app with a generative-AI shrine guide, and a Japanese PDF RAG demo that makes its sources visible. A recent paper by Takahiro Sagawa, a professor in the Department of Applied Physics, Graduate School of Engineering at the University of Tokyo, caught my attention because it approaches this question from an unexpected direction: stochastic thermodynamics.&lt;/p&gt;

&lt;p&gt;This is a builder's reading note based on the university's press release and the paper's abstract, not a reproduction of its experiments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change the order, not the words
&lt;/h2&gt;

&lt;p&gt;Consider this example, translated from the press release:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The cup fell on the floor. The cup broke.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now reverse the sentences:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The cup broke. The cup fell on the floor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both versions contain readable sentences. But the first follows a familiar progression; the second makes me look for a different explanation. It is not impossible, just different.&lt;/p&gt;

&lt;p&gt;The research compares how likely such sequences are under a language model. Crucially, it reverses &lt;strong&gt;sentence order while preserving the word order inside each sentence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Reversing every token would break grammar. The resulting probability difference could mostly reflect broken syntax rather than anything interesting about events. Sentence-level reversal is a way to change the relationship between sentences without destroying their internal structure.&lt;/p&gt;

&lt;p&gt;That experimental choice is what first made the work click for me: a useful measurement starts by deciding what to disturb and what to leave intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thermodynamics without measuring GPU heat
&lt;/h2&gt;

&lt;p&gt;The quantity at the center of the paper is &lt;strong&gt;entropy production&lt;/strong&gt;: a way to quantify irreversibility by comparing the likelihood of forward and reversed sequences.&lt;/p&gt;

&lt;p&gt;Here, thermodynamics does not mean measuring the computer's physical heat output or energy consumption. The object of study is the model's generation process and its directional asymmetry.&lt;/p&gt;

&lt;p&gt;The theory is broader than text. It provides a unified framework for autoregressive generative models, ranging from Transformers to Kalman filters.&lt;/p&gt;

&lt;p&gt;There is a technical obstacle: the next output can depend on a long history, not just the immediately previous output. That makes the observed process non-Markovian. Trying to enumerate every possible history would quickly become impractical.&lt;/p&gt;

&lt;p&gt;Sagawa's framework shows how to estimate entropy production efficiently without enumerating all those histories. It also decomposes entropy production into non-negative contributions at each timestep, using an information-theoretic perspective: inferring the past from the future.&lt;/p&gt;

&lt;p&gt;I find that reversal of viewpoint appealing. We normally discuss these models as predictors of what comes next; this theory also asks what looking backward can reveal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the language-model experiments suggest
&lt;/h2&gt;

&lt;p&gt;The experiments used GPT-2 (124M) and Qwen3-4B-Base. Texts describing sequential events tended to have larger sentence-level entropy production than texts listing independent facts. In other words, reversed generation was relatively less likely for the sequential-event texts.&lt;/p&gt;

&lt;p&gt;That suggests the models' generation probabilities reflect something about event order. It offers a quantitative probe for investigating what is often called a model's internal “world model.”&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; establish that an LLM understands causality in the same way a person does.&lt;/p&gt;

&lt;p&gt;There is also an important dataset caveat: the evaluation texts were generated by prompting commercial generative AI. Their construction could introduce features that matter to the comparison. The press release explicitly calls for further verification, including text sets constructed from theoretical definitions of order and causal relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would take back to an app project
&lt;/h2&gt;

&lt;p&gt;In my RAG demo, making sources visible helps a reader inspect where an answer came from. It does not, by itself, establish that the answer describes events in a sensible sequence. Those are separate evaluation questions.&lt;/p&gt;

&lt;p&gt;This paper does not give me a ready-made accuracy score or a proven hallucination detector. I have not tested its measurement in my own apps.&lt;/p&gt;

&lt;p&gt;What it gives me is a sharper question: instead of only asking whether a generated explanation sounds good, what changes when I perturb its event order?&lt;/p&gt;

&lt;p&gt;For me, that is the interesting bridge between theory and building. “Does this model represent the world?” is enormous. Choosing a controlled reversal and studying its probability makes one part of that question more concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;University of Tokyo press release (November 6, 2026): &lt;a href="https://www.t.u-tokyo.ac.jp/press/pr2026-11-06-001" rel="noopener noreferrer"&gt;https://www.t.u-tokyo.ac.jp/press/pr2026-11-06-001&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Takahiro Sagawa, &lt;em&gt;Stochastic Thermodynamics for Autoregressive Generative Models: A Non-Markovian Perspective&lt;/em&gt;, PRX Intelligence. DOI: 10.1103/tv38-b23y: &lt;a href="https://doi.org/10.1103/tv38-b23y" rel="noopener noreferrer"&gt;https://doi.org/10.1103/tv38-b23y&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>computerscience</category>
      <category>llm</category>
    </item>
    <item>
      <title>I Built a Japanese PDF RAG Demo Where You Can See What the Model Looked At</title>
      <dc:creator>kohei matsunobu</dc:creator>
      <pubDate>Fri, 06 Nov 2026 17:57:20 +0000</pubDate>
      <link>https://dev.to/koheip/i-built-a-japanese-pdf-rag-demo-where-you-can-see-what-the-model-looked-at-2m1d</link>
      <guid>https://dev.to/koheip/i-built-a-japanese-pdf-rag-demo-where-you-can-see-what-the-model-looked-at-2m1d</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published in Japanese on &lt;a href="https://zenn.dev/koheip/articles/rag-visible-sources" rel="noopener noreferrer"&gt;Zenn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm Kohei Matsunobe, a second-year student in the online (correspondence) program at ZEN University. I'm working toward becoming an AI engineer, and I learn by building LLM-related systems with my own hands.&lt;/p&gt;

&lt;p&gt;When you ask an LLM about a document, you usually get back an answer in fluent, plausible language. What bothered me was the question: "Which part of the document is that answer actually based on?" I didn't want to judge an answer by whether it &lt;em&gt;sounded&lt;/em&gt; right. I wanted to build something where &lt;strong&gt;it's clear what the model looked at&lt;/strong&gt; when it answered.&lt;/p&gt;

&lt;p&gt;So I built a RAG demo: you upload PDFs, ask questions in Japanese, and get answers &lt;strong&gt;with sources (file name and page number)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/koheip/rag-doc-qa" rel="noopener noreferrer"&gt;https://github.com/koheip/rag-doc-qa&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post isn't a usage guide. Instead, it focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What I decided, and where, in order to show sources accurately&lt;/li&gt;
&lt;li&gt;What I learned once I evaluated it and looked at the numbers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setup instructions are in the README, so please check there.&lt;/p&gt;

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

&lt;p&gt;It's a Streamlit app. You register PDFs and ask a question; it searches for relevant passages and answers using only those passages as evidence. Answers carry numbers like [1], and each number maps to "file name p.page". You can also see the retrieved chunks and their similarity scores in the UI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieval: &lt;code&gt;multilingual-e5-small&lt;/code&gt; + FAISS (&lt;code&gt;IndexFlatIP&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Generation: Google Gemini (official SDK &lt;code&gt;google-genai&lt;/code&gt;). The default is &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;, and you can switch to &lt;code&gt;gemini-3.8-flash&lt;/code&gt; from the sidebar&lt;/li&gt;
&lt;li&gt;Without an API key, it runs in "extractive mode," which shows the retrieved passages as-is&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the flow:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[PDF] --&amp;gt;|pypdf| B[Text per page]
    B --&amp;gt;|Japanese-aware chunking| C[Chunks]
    C --&amp;gt;|"passage: " + e5| D[(FAISS)]
    Q[Question] --&amp;gt;|"query: " + e5| D
    D --&amp;gt; R[top-k chunks + sources]
    R --&amp;gt;|with key| L[Answer with Gemini]
    R --&amp;gt;|no key| E[Extractive mode]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Decision 1: Chunks never cross page boundaries
&lt;/h2&gt;

&lt;p&gt;The decision that mattered most for showing accurate page numbers was how I split text into chunks.&lt;/p&gt;

&lt;p&gt;I wrote the chunker myself instead of using a library. Japanese has no spaces between words, so I measure length in &lt;strong&gt;characters&lt;/strong&gt;. Then I split into roughly 200-character pieces, &lt;strong&gt;preferring sentence boundaries&lt;/strong&gt; like 「。」, and overlap a bit of the previous chunk's tail so context isn't cut off at the seams.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;chunks never cross pages&lt;/strong&gt;. If a chunk spans two pages, you can't say which single page it came from. Since the whole point of the app is showing sources, I prioritized citation accuracy over retrieval convenience here.&lt;/p&gt;

&lt;p&gt;I wrote it myself because I wanted Japanese-specific splitting in a form I could actually explain. If you leave it to a library, it's harder to explain in your own words why the text was cut where it was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 2: Make it say "I don't know," and cite only what it used
&lt;/h2&gt;

&lt;p&gt;In the generation prompt, I number the retrieved chunks, attach their sources, and give instructions like this (from &lt;a href="https://github.com/koheip/rag-doc-qa/blob/main/src/llm.py" rel="noopener noreferrer"&gt;src/llm.py&lt;/a&gt;; the prompt itself is in Japanese):&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="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;あなたは社内ドキュメントに関する質問に答えるアシスタントです。
以下のルールを必ず守ってください。
1. 回答は【参考資料】に書かれている内容だけを根拠にしてください。あなた自身の知識で補ってはいけません。
2. 【参考資料】に答えが書かれていない場合は、推測せずに「わからない」とだけ答えてください（この場合は資料番号を付けないでください）。
3. 回答の文末には、根拠にした資料の番号を [1] [2] のように付けてください。
4. 日本語で、簡潔かつ丁寧に答えてください。&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roughly translated, the rules are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Base your answer only on the provided reference material; don't fill in with your own knowledge.&lt;/li&gt;
&lt;li&gt;If the answer isn't in the material, don't guess — just say 「わからない」 ("I don't know"), with no reference numbers.&lt;/li&gt;
&lt;li&gt;End the answer with the numbers of the passages you relied on, like [1] [2].&lt;/li&gt;
&lt;li&gt;Answer in Japanese, concisely and politely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key point is that &lt;strong&gt;I don't list every retrieved chunk as a "source." Only the numbers the answer actually references are shown as sources.&lt;/strong&gt; Even if the top 4 chunks are passed in, if the answer used just one, only one source is shown. When the answer is 「わからない」, no sources are shown.&lt;/p&gt;

&lt;p&gt;Temperature is set to 0.1, because I wanted answers that stay faithful to the material and don't wobble between runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 3: Let people try it without an API key
&lt;/h2&gt;

&lt;p&gt;Without an API key, the app runs in "extractive mode," showing the top chunks directly as the answer.&lt;/p&gt;

&lt;p&gt;I added this so anyone could try it right away, but once I built it I found another benefit. Extractive mode is &lt;strong&gt;retrieval only&lt;/strong&gt;, so before generation even happens, you can check whether the right passages were found at all. When the RAG answer is off, it became much easier to tell whether the problem is in retrieval or in generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other choices
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Streamlit&lt;/td&gt;
&lt;td&gt;Python only; chat UI and file upload in a small amount of code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF loading&lt;/td&gt;
&lt;td&gt;pypdf&lt;/td&gt;
&lt;td&gt;Pure Python with few dependencies; behaves the same on Windows/Mac/Linux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embeddings&lt;/td&gt;
&lt;td&gt;multilingual-e5-small&lt;/td&gt;
&lt;td&gt;Free, runs locally, supports Japanese. About 470MB and fast enough on CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector store&lt;/td&gt;
&lt;td&gt;FAISS (IndexFlatIP)&lt;/td&gt;
&lt;td&gt;Shows how to use a library that's standard in practice, and the index can be swapped as data grows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;Gemini (google-genai)&lt;/td&gt;
&lt;td&gt;Has a free tier and can be called in a few lines with the official SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;E5 expects a &lt;code&gt;passage:&lt;/code&gt; prefix on documents and a &lt;code&gt;query:&lt;/code&gt; prefix on questions, so I follow that. Vectors are 384-dimensional and normalized to length 1, so FAISS's inner product is exactly cosine similarity.&lt;/p&gt;

&lt;p&gt;At this data size, a numpy matrix multiplication would be plenty. But with FAISS, when data grows I can swap the brute-force &lt;code&gt;IndexFlatIP&lt;/code&gt; for an approximate index. The vector store is isolated in one file, so I can change it without affecting the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the evaluation taught me
&lt;/h2&gt;

&lt;p&gt;I didn't want to stop at "it kind of works," so I made retrieval and answers measurable. The results below are what I wrote up in the README on 2026-10-21.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eval data: 30 questions on the bundled sample PDF (employment rules and an internal FAQ for a fictional company, 11 pages)&lt;/li&gt;
&lt;li&gt;Of the 30, 26 are answerable from the document and 4 are not. It also includes paraphrased questions that use different words from the document&lt;/li&gt;
&lt;li&gt;Retrieval is dense vector search (e5-small, top-k=4)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Retrieval: Hit@3 is high, Hit@1 is still low
&lt;/h3&gt;

&lt;p&gt;With a chunk size of 200 characters (the one I adopted), retrieval on the 26 answerable questions looked like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hit@1&lt;/td&gt;
&lt;td&gt;73.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hit@3&lt;/td&gt;
&lt;td&gt;96.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recall@4&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MRR&lt;/td&gt;
&lt;td&gt;0.856&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hit@3 on paraphrased questions&lt;/td&gt;
&lt;td&gt;92.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every correct passage is within the top 4, but it's ranked first only 73.1% of the time. In other words, the right answer often lands at rank 2–4.&lt;/p&gt;

&lt;p&gt;Right now I pass the top 4 chunks to the LLM together, so this doesn't affect the answers. But if I want to pass fewer chunks to lower cost, Hit@1 needs to improve. The clearest thing the numbers showed me was that "the answer is correct" and "retrieval is good enough" are two different things.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bigger chunks aren't automatically better
&lt;/h3&gt;

&lt;p&gt;I compared chunk sizes of 150, 200, 300, and 400 characters, and 200 gave the best balance. The surprise was 300: Hit@3 on paraphrased questions &lt;strong&gt;dropped from 92.3% to 69.2%&lt;/strong&gt;. My read is that one chunk ends up mixing several rules (articles), so the meaning of the vector gets blurry.&lt;/p&gt;

&lt;p&gt;If I had chosen the size based only on the intuition that "more context should be better," I would have missed this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generation: this dataset doesn't separate models
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; (the default), all 26 answerable questions were answered correctly (26/26), and all 4 unanswerable questions were correctly refused with 「わからない」 (4/4). Latency was 0.56s at p50 and 0.72s at p95. Grading was done by a different LLM than the answering model, comparing each answer with a reference answer and scoring it 0/1 (LLM-as-a-judge).&lt;/p&gt;

&lt;p&gt;I don't read this as "the model is perfect." I read it as &lt;strong&gt;the current dataset is too easy and has hit the ceiling&lt;/strong&gt;. To see differences, I need harder questions — ones that span multiple passages, or that require calculation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limits of this evaluation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;30 questions is small; a single question moves the score by about 4 points&lt;/li&gt;
&lt;li&gt;Both the dataset and the grading come from one person (me) and one LLM&lt;/li&gt;
&lt;li&gt;I also tried comparing against &lt;code&gt;gemini-3.8-flash&lt;/code&gt;, but it was under heavy load (503) during evaluation and didn't respond, so I couldn't compare&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Known limitations
&lt;/h2&gt;

&lt;p&gt;Here are the limits of this demo that I found while building it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E5 similarity scores tend to cluster around 0.8–0.9&lt;/strong&gt;, so it's hard to set a threshold like "below X means unrelated." That's why, for questions not covered by the document, I leave the decision to the LLM via the prompt and have it say 「わからない」&lt;/li&gt;
&lt;li&gt;With paraphrases like 給料 vs. 給与 (both meaning "salary"), the correct passage sometimes isn't ranked first&lt;/li&gt;
&lt;li&gt;Scanned-image-only PDFs need OCR, so they're not supported&lt;/li&gt;
&lt;li&gt;The index lives only in memory and disappears when the session ends (I prioritized simplicity for a demo)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;p&gt;Tests are written with pytest and cover chunking, retrieval, the LLM-related logic, and the evaluation metrics. Retrieval tests use a lightweight fake embedder built from character counts (FakeEmbedder), and generation tests use a fake LLM (FakeLLM), so you can check the main flow &lt;strong&gt;without downloading the model and without calling the API&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next: hybrid search
&lt;/h2&gt;

&lt;p&gt;To improve Hit@1, my next step is hybrid search that combines BM25 (keyword search) with vector search. Keyword search is better at proper nouns, numbers, and article numbers, so the idea is to pair it with vector search, which handles paraphrases better.&lt;/p&gt;

&lt;p&gt;I haven't implemented it yet. I've made the retrieval method swappable and prepared the function specs and tests for &lt;code&gt;src/bm25.py&lt;/code&gt; first. I plan to measure whether Hit@1 and MRR go up on the same 30 questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;To show accurate page numbers, chunks never cross pages, and I wrote my own Japanese-aware chunker&lt;/li&gt;
&lt;li&gt;Only the numbers the answer actually references are shown as sources, and when the document doesn't cover the question, the model says 「わからない」&lt;/li&gt;
&lt;li&gt;In evaluation, Hit@3 was 96.2% but Hit@1 was 73.1%. Widening chunks to 300 characters dropped paraphrase Hit@3 from 92.3% to 69.2%&lt;/li&gt;
&lt;li&gt;"The answer is correct" and "retrieval is good enough" are different things, and I only saw what to do next once I put numbers on it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When building RAG, I found it helps to be able to check "what did it look at to answer?" both in the UI and in numbers, not just how the answer looks. All the code is public, so feel free to try it out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/koheip/rag-doc-qa" rel="noopener noreferrer"&gt;https://github.com/koheip/rag-doc-qa&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>gemini</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>What I Thought About Before Putting a Generative-AI Shrine Guide in My Indie iOS App (API Gateway + Lambda + Gemini, Consent and Privacy)</title>
      <dc:creator>kohei matsunobu</dc:creator>
      <pubDate>Fri, 06 Nov 2026 17:57:15 +0000</pubDate>
      <link>https://dev.to/koheip/what-i-thought-about-before-putting-a-generative-ai-shrine-guide-in-my-indie-ios-app-api-gateway--2g4i</link>
      <guid>https://dev.to/koheip/what-i-thought-about-before-putting-a-generative-ai-shrine-guide-in-my-indie-ios-app-api-gateway--2g4i</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published in Japanese on &lt;a href="https://qiita.com/koheip/items/ae6963c09cd4c4f60a48" rel="noopener noreferrer"&gt;Qiita&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm Kohei Matsunobe. I'm a second-year student in the online (correspondence) program at ZEN University, studying to become an AI engineer.&lt;/p&gt;

&lt;p&gt;On my own, I build an iOS app called &lt;a href="https://apps.apple.com/jp/app/%E3%82%AB%E3%83%9F%E3%82%81%E3%81%90/id6821107881" rel="noopener noreferrer"&gt;KamiMeg (カミメグ)&lt;/a&gt;. It's a digital &lt;em&gt;goshuin&lt;/em&gt; book: you take photos of the &lt;em&gt;goshuin&lt;/em&gt; (the calligraphy stamps you receive at Japanese shrines), find shrines nearby, and keep a record of your visits. It's free, there's no account, and your records are stored on your device.&lt;/p&gt;

&lt;p&gt;I released v1.0 in October 2026 and v1.1.0 in November 2026. The headline feature of v1.1.0 is a generative-AI feature where &lt;strong&gt;a "companion deity" introduces the shrine to you when you press a button&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is about what I thought through before shipping that feature. It's not a code walkthrough; it's about three decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where the API key should live (and why I went serverless)&lt;/li&gt;
&lt;li&gt;How to keep the app's promise — "your records stay on your device" — even after adding AI&lt;/li&gt;
&lt;li&gt;Why I ended up with Gemini, even though I first looked at Bedrock and the Claude API&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  KamiMeg before the AI feature
&lt;/h2&gt;

&lt;p&gt;Originally, KamiMeg was an app that did almost everything on the device.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;What I use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App&lt;/td&gt;
&lt;td&gt;Expo / React Native (TypeScript)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storing records&lt;/td&gt;
&lt;td&gt;SQLite (on device)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finding nearby shrines&lt;/td&gt;
&lt;td&gt;Google Places API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ads&lt;/td&gt;
&lt;td&gt;Google AdMob&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There was no backend server of my own and no account. "Your &lt;em&gt;goshuin&lt;/em&gt; photos and visit records live only on your own device" was something I really cared about as the person building it.&lt;/p&gt;

&lt;p&gt;When I submitted v1.0, App Store review needed some extra back-and-forth. I responded, it passed, and the app went live — but it taught me that even as an indie developer, once you ship to a store, you should build things you can explain. That became a starting assumption when I designed the AI feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I wanted to build
&lt;/h2&gt;

&lt;p&gt;When you're looking for shrines and logging visits, there are moments where you wonder, "What kind of place is this shrine?" So I thought it would be fun to show a short introduction for each shrine. Just showing a plain description felt a bit flat, though, so in the app it's the "companion deity" who introduces the shrine.&lt;/p&gt;

&lt;p&gt;The introduction is never generated automatically. It's generated &lt;strong&gt;only when the user presses the button&lt;/strong&gt;. I'll explain why in the privacy section.&lt;/p&gt;

&lt;p&gt;In v1.1.0 I also made it so that when you search for a shrine by name, only that shrine is shown, and I unified the app's display name as "カミメグ".&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 1: Don't put the API key in the app (API Gateway → Lambda → Gemini)
&lt;/h2&gt;

&lt;p&gt;The simplest way to use a generative-AI API from an app is to call it directly from the app. But that means shipping the API key inside the app. A key inside an app binary can be extracted, and since I manage the spending cap myself as an indie developer, I wanted to avoid that.&lt;/p&gt;

&lt;p&gt;So I put a thin AWS serverless layer between the app and the AI.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[iOS app&amp;lt;br/&amp;gt;KamiMeg] --&amp;gt;|Shrine name &amp;amp; address&amp;lt;br/&amp;gt;after consent only| B[Amazon API Gateway]
    B --&amp;gt; C[AWS Lambda]
    C --&amp;gt; D[Google Gemini API]
    D --&amp;gt; C
    C --&amp;gt; B
    B --&amp;gt;|Introduction text| A&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The flow is simple: app → Amazon API Gateway → AWS Lambda → Google Gemini API. The Gemini API key lives only on the server side; the app never has it.&lt;/p&gt;

&lt;p&gt;Why serverless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I didn't want an always-on server.&lt;/strong&gt; Introductions are generated only when the button is pressed, so it's enough for something to run only when a request comes in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I can swap what's behind it without changing the app.&lt;/strong&gt; The app only talks to API Gateway, so which AI the Lambda calls can change without an app update. This turned out to matter a lot when I switched to Gemini (more on that below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I can enforce usage limits on the server.&lt;/strong&gt; A daily usage limit can live on the server side (see below).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Decision 2: Don't break the "records stay on your device" promise
&lt;/h2&gt;

&lt;p&gt;Adding an AI feature means sending &lt;em&gt;something&lt;/em&gt; off the device. KamiMeg's selling point is "no account, records on device," so this is where I was most careful.&lt;/p&gt;

&lt;p&gt;Here are the rules I settled on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only the shrine name and address are sent.&lt;/strong&gt; That's the minimum needed to write an introduction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They're sent only after the user consents.&lt;/strong&gt; Without consent, the AI feature isn't used.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Goshuin photos, visit records, and location are not sent.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No account.&lt;/strong&gt; I don't ask users to log in for the AI feature either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There's a daily usage limit.&lt;/strong&gt; It's tracked with a random per-device ID, which isn't information that identifies a person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generated introductions may be stored on the developer's server for up to 90 days.&lt;/strong&gt; This is stated explicitly in the privacy policy (PRIVACY.md).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Generate only when the button is pressed" is part of these rules too. I didn't want a behavior where just opening a screen silently sends data somewhere. Data that the user agreed to share is sent only when they press the button themselves. I think that's easier to explain, and it's more reassuring for the people using the app.&lt;/p&gt;

&lt;p&gt;The usage limit also protects my costs. In indie development, if an API keeps getting called unexpectedly, that bill is on me. Being able to hold the limit on the server is another reason I'm glad the app doesn't call the API directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 3: Why Gemini
&lt;/h2&gt;

&lt;p&gt;To be honest, I didn't decide on Gemini from the start. At first I also considered &lt;strong&gt;Amazon Bedrock&lt;/strong&gt; and the &lt;strong&gt;Claude API&lt;/strong&gt; as candidates.&lt;/p&gt;

&lt;p&gt;But once I actually moved forward, I found that usage limits and payment conditions didn't fit my situation as an indie developer, and I couldn't take it to production as-is. So I switched to the &lt;strong&gt;Gemini API&lt;/strong&gt;, finished the feature, and shipped it to production.&lt;/p&gt;

&lt;p&gt;This is where Decision 1 saved me. Because the app only knows about API Gateway, changing which AI gets called was a matter inside the Lambda. Leaving room to change "which AI to use" later is, I think, the reason the switch didn't stall the release.&lt;/p&gt;

&lt;p&gt;The lesson I took from this: &lt;strong&gt;before comparing model quality, check early whether you can actually use the service in production from your own position.&lt;/strong&gt; Limits and payment conditions are hard to see when you just call an API a few times to try it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what you won't publish
&lt;/h2&gt;

&lt;p&gt;When releasing the app and writing about it, I also decided what &lt;em&gt;not&lt;/em&gt; to publish: the API endpoint URL, the app's key, the AWS account and region, and specific numbers for usage limits. None of them appear in this post.&lt;/p&gt;

&lt;p&gt;Showing the flow "API Gateway → Lambda → Gemini" in a diagram is fine for explaining how it works. Concrete endpoints and limit values, on the other hand, could become clues for abuse. As an indie developer you sometimes feel like making everything open, but this is where I draw the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats and what's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AI-generated introductions aren't guaranteed to be accurate. As the builder, I see them as an "entry point," and I think it's best to check the details with the shrine's official information.&lt;/li&gt;
&lt;li&gt;Because introductions are generated through the server, the feature assumes the user has consented and is online.&lt;/li&gt;
&lt;li&gt;Going forward, I want to keep adding features without breaking the app's promises: no account, records stay on the device.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I didn't call the generative-AI API directly from the app. A serverless setup — API Gateway → Lambda → Gemini — keeps the key and the usage limit on the server.&lt;/li&gt;
&lt;li&gt;Only the shrine name and address are sent, and only after consent. Photos, records, and location are not sent, and there's no account.&lt;/li&gt;
&lt;li&gt;I considered Bedrock and the Claude API, but switched to Gemini because of limits and payment conditions. Since the app doesn't know what's behind the API, the switch didn't stop the release.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you put generative AI into an app, I found it pays off to decide "what you send and what you don't" and "whether you can swap it later" before picking a model. I hope this is useful for anyone else building apps on their own.&lt;/p&gt;

&lt;p&gt;KamiMeg is available on the App Store (Japanese). If you like visiting shrines, give it a try:&lt;br&gt;
&lt;a href="https://apps.apple.com/jp/app/%E3%82%AB%E3%83%9F%E3%82%81%E3%81%90/id6821107881" rel="noopener noreferrer"&gt;https://apps.apple.com/jp/app/カミめぐ/id6821107881&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>gemini</category>
      <category>reactnative</category>
      <category>ios</category>
    </item>
    <item>
      <title>Before You Burn GPU Hours, Smoke-Test One Task on a Cheap CPU Runtime</title>
      <dc:creator>kohei matsunobu</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:34:56 +0000</pubDate>
      <link>https://dev.to/koheip/before-you-burn-gpu-hours-smoke-test-one-task-on-a-cheap-cpu-runtime-h8h</link>
      <guid>https://dev.to/koheip/before-you-burn-gpu-hours-smoke-test-one-task-on-a-cheap-cpu-runtime-h8h</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published in Japanese on &lt;a href="https://zenn.dev/koheip/articles/cheap-runtime-smoke-test" rel="noopener noreferrer"&gt;Zenn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm Kohei Matsunobe, a second-year student in the online (correspondence) program at ZEN University. I'm working toward becoming an AI engineer, and on my own I run experiment pipelines that evaluate LLM outputs and code on notebooks and cloud runtimes.&lt;/p&gt;

&lt;p&gt;GPU runtimes are great, but they're expensive per hour, and there's a cap on how many compute units you can use. I've made the same mistakes more than once: "I grabbed a GPU, launched the real job, and it crashed in the first few minutes," or "it crashed, I didn't notice, and the runtime just sat there running."&lt;/p&gt;

&lt;p&gt;Looking back, almost all of those failures could have been caught without a GPU. In this post, I'll walk through the idea of a "smoke test" — a minimal check you run on a cheap CPU runtime before moving up to the expensive one — plus a small script you can use right away.&lt;/p&gt;

&lt;h2&gt;
  
  
  What tends to go wrong
&lt;/h2&gt;

&lt;p&gt;In pipelines that evaluate LLMs or generated code, these problems come up a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Missing test dependencies&lt;/strong&gt;: Your setup grades generated code with pytest, but the production runtime doesn't have pytest, or the libraries the tested code needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache or data not found&lt;/strong&gt;: The path where you thought you'd put model weights or dataset caches is empty on the fresh runtime. So downloads run every time, or you get errors because offline mode is still on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mismatched environment variables or paths&lt;/strong&gt;: It worked locally, but on a different runtime the working directory or cache location has changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An exception on the very first task&lt;/strong&gt;: Simple mistakes, like a missing output directory or a misspelled argument name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these have anything to do with GPU performance. But when they happen on a GPU runtime, you lose all the time spent on setup and model loading too. Retry the job enough times and you might suddenly find you've used up your compute units.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea behind the smoke test
&lt;/h2&gt;

&lt;p&gt;It boils down to three points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keep dependencies identical to production&lt;/strong&gt;: Build the environment from the same definition as production — the same requirements file, lock file, or container image. If you maintain a separate dependency list for CPU, you hide the very differences you want to catch. (The only exception: packages that need a GPU-specific build get handled separately.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run just one task on the cheap runtime&lt;/strong&gt;: Don't run the whole eval set — just one item. Inference may be slow on CPU, but all you need to know is "does it make it to the end?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Promote to the expensive runtime only if it passes&lt;/strong&gt;: Only when the smoke test succeeds do you run the real job on the GPU runtime, with the same code and the same config.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you think of it as a CI pipeline, it's a staged gate: put a light job in front of the heavy one, and don't move on unless the light one passes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The minimum checklist
&lt;/h2&gt;

&lt;p&gt;Here's what I check every time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the key packages be imported? (Including pytest for evaluation)&lt;/li&gt;
&lt;li&gt;Do the cache and data directories exist, and are they non-empty?&lt;/li&gt;
&lt;li&gt;Are the required environment variables set?&lt;/li&gt;
&lt;li&gt;Can I write to the output directory?&lt;/li&gt;
&lt;li&gt;Does at least one pytest test actually pass?&lt;/li&gt;
&lt;li&gt;Can the same entry point as production run to completion as a dry run with just one task?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to do all of these perfectly. Just adding "anything that has bitten me even once" to the list goes a long way.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small script
&lt;/h2&gt;

&lt;p&gt;Here's a script that bundles the checks above. Adjust &lt;code&gt;REQUIRED_MODULES&lt;/code&gt;, &lt;code&gt;REQUIRED_PATHS&lt;/code&gt;, and &lt;code&gt;run_task&lt;/code&gt; to fit your own pipeline.&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;#!/usr/bin/env python3
# smoke_test.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Smoke test to run on a cheap runtime before moving to an expensive one.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&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;importlib&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;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;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="c1"&gt;# Modules to verify with the same dependency stack as production
&lt;/span&gt;&lt;span class="n"&gt;REQUIRED_MODULES&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;numpy&lt;/span&gt;&lt;span class="sh"&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;pytest&lt;/span&gt;&lt;span class="sh"&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;transformers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# Cache/data paths that should exist and have contents
&lt;/span&gt;&lt;span class="n"&gt;REQUIRED_PATHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&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;environ&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;HF_HOME&lt;/span&gt;&lt;span class="sh"&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;~/.cache/huggingface&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;REQUIRED_ENV&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;HF_HOME&lt;/span&gt;&lt;span class="sh"&gt;"&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;check_imports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&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;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;import_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# also catch init errors, not just ImportError
&lt;/span&gt;            &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;import failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&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;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;p&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;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;path missing: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&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;elif&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
            &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;directory is empty: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&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;return&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&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;env var not set: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;environ&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="n"&gt;n&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;check_writable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_dir&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&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;exist_ok&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;probe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;out_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.smoke_write_test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&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="n"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;OSError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;not writable: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&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="k"&gt;return&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;run_one_pytest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# e.g. tests/test_eval.py::test_single_case
&lt;/span&gt;    &lt;span class="n"&gt;result&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="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-m&lt;/span&gt;&lt;span class="sh"&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;pytest&lt;/span&gt;&lt;span class="sh"&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;-q&lt;/span&gt;&lt;span class="sh"&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;-x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;# Exit code 5 means "no tests were collected", so treat it as a failure too
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&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="k"&gt;return&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;pytest failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;test_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (exit=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&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="k"&gt;return&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;load_tasks&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="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;with&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;open&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;return&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="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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;run_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Replace this with the same processing as production (inference, grading)
&lt;/span&gt;    &lt;span class="k"&gt;return&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="n"&gt;task&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;id&lt;/span&gt;&lt;span class="sh"&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;status&lt;/span&gt;&lt;span class="sh"&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;ok&lt;/span&gt;&lt;span class="sh"&gt;"&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;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cheap runtime smoke test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--tasks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JSONL file, one task per line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--pytest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metavar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TEST_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run a single test 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;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--dry-run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;store_true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run only one task&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;check_imports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REQUIRED_MODULES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;check_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REQUIRED_PATHS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;check_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REQUIRED_ENV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;check_writable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;run_one_pytest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&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;[NG] Pre-flight checks found problems:&lt;/span&gt;&lt;span class="sh"&gt;"&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;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;errors&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;  -&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_tasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;1&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;task&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&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;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;result&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="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;[OK] Smoke test passed. Safe to promote to the expensive runtime.&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="mi"&gt;0&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the cheap CPU runtime, run it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python smoke_test.py &lt;span class="nt"&gt;--tasks&lt;/span&gt; data/tasks.jsonl &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pytest&lt;/span&gt; tests/test_eval.py::test_single_case
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only start the real run when the exit code is 0, the same pattern works in notebooks and in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python smoke_test.py &lt;span class="nt"&gt;--tasks&lt;/span&gt; data/tasks.jsonl &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python run_full_eval.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Most expensive GPU job failures come from things that have nothing to do with the GPU: dependencies, caches, and path settings.&lt;/li&gt;
&lt;li&gt;Keep the same dependency stack as production, push just one task through on a cheap CPU runtime, and promote to the expensive runtime only when it passes.&lt;/li&gt;
&lt;li&gt;For the checklist, adding things you've personally been bitten by, little by little, is plenty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few minutes of smoke testing protects both your compute units and your motivation. I hope this helps anyone else running experiments on their own!&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>pytest</category>
      <category>mlops</category>
    </item>
    <item>
      <title>Summarize Kaggle Notebook Status and Logs in ~7 Lines with the Kaggle CLI (Goodbye, .bat + Notepad)</title>
      <dc:creator>kohei matsunobu</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:34:55 +0000</pubDate>
      <link>https://dev.to/koheip/summarize-kaggle-notebook-status-and-logs-in-7-lines-with-the-kaggle-cli-goodbye-bat-notepad-32o2</link>
      <guid>https://dev.to/koheip/summarize-kaggle-notebook-status-and-logs-in-7-lines-with-the-kaggle-cli-goodbye-bat-notepad-32o2</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published in Japanese on &lt;a href="https://qiita.com/koheip/items/2b2ea56bbedd5b648055" rel="noopener noreferrer"&gt;Qiita&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm Kohei Matsunobe. I'm a second-year student in the online (correspondence) program at ZEN University, studying to become an AI engineer.&lt;/p&gt;

&lt;p&gt;Once I started running Kaggle notebooks (kernels) from my local machine with &lt;code&gt;kaggle kernels push&lt;/code&gt;, one small thing kept eating my time: &lt;strong&gt;checking what was going on.&lt;/strong&gt; Is it still running? Did it finish? Did it fail? And if it failed, where? Checking that in the browser or digging through log files every single time got old fast.&lt;/p&gt;

&lt;p&gt;In this post, I'll share a script that calls &lt;code&gt;kaggle kernels status&lt;/code&gt; and &lt;code&gt;kaggle kernels output&lt;/code&gt; from Python and boils the status and log down to &lt;strong&gt;a ~7-line summary plus one CSV row&lt;/strong&gt;. I'll also cover how I fixed garbled text (mojibake) on Windows, and how I made the same script usable both from a &lt;code&gt;.bat&lt;/code&gt; file and from an AI agent.&lt;/p&gt;

&lt;p&gt;Note: this post is not about submissions. It focuses purely on &lt;strong&gt;monitoring run status and summarizing logs&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I used to do it
&lt;/h2&gt;

&lt;p&gt;At first, I got by with a batch file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="kd"&gt;kaggle&lt;/span&gt; &lt;span class="kd"&gt;kernels&lt;/span&gt; &lt;span class="kd"&gt;status&lt;/span&gt; &lt;span class="kd"&gt;koheip&lt;/span&gt;&lt;span class="na"&gt;/my-notebook
&lt;/span&gt;&lt;span class="kd"&gt;kaggle&lt;/span&gt; &lt;span class="kd"&gt;kernels&lt;/span&gt; &lt;span class="kd"&gt;output&lt;/span&gt; &lt;span class="kd"&gt;koheip&lt;/span&gt;&lt;span class="na"&gt;/my-notebook -p &lt;/span&gt;.\out &lt;span class="na"&gt;-o
&lt;/span&gt;&lt;span class="kd"&gt;notepad&lt;/span&gt; .\out\my&lt;span class="na"&gt;-notebook&lt;/span&gt;.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It worked, but it had a few problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The logs are long, and scrolling through Notepad to find the error is painful&lt;/li&gt;
&lt;li&gt;Non-ASCII output (Japanese, in my case) got garbled in the console&lt;/li&gt;
&lt;li&gt;There was no record of "when, which notebook, and what status"&lt;/li&gt;
&lt;li&gt;When I asked an AI agent to check the log, it had to read the entire thing, which is wasteful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point was the big one. If you're handing something to an agent, &lt;strong&gt;a short text with only the information you need&lt;/strong&gt; is much easier to work with.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the CLI gives you
&lt;/h2&gt;

&lt;p&gt;You only need two commands (official docs: &lt;a href="https://github.com/Kaggle/kaggle-api/blob/main/docs/kernels.md" rel="noopener noreferrer"&gt;Kaggle CLI documentation&lt;/a&gt;, &lt;a href="https://www.kaggle.com/docs/api" rel="noopener noreferrer"&gt;kaggle.com/docs/api&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;kaggle kernels status&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Shows the status of the latest run. You pass the kernel as &lt;code&gt;owner/kernel-slug&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;kaggle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kernels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;koheip/my-notebook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a single line (on failure, an extra &lt;code&gt;Failure message: "..."&lt;/code&gt; line is added):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;koheip/my-notebook has status "KernelWorkerStatus.COMPLETE"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the CLI version, you may see lowercase &lt;code&gt;complete&lt;/code&gt; or the &lt;code&gt;KernelWorkerStatus.&lt;/code&gt; prefix, so the script &lt;strong&gt;takes only the last word and lowercases it&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;kaggle kernels output&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Downloads the output files from the latest run. The main options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-p, --path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Destination folder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-o, --force&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overwrite existing files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-q, --quiet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Suppress progress output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--file-pattern&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only download files matching a regex&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key point: along with the output files, it also saves &lt;strong&gt;the execution log as &lt;code&gt;&amp;lt;kernel-slug&amp;gt;.log&lt;/code&gt;&lt;/strong&gt;. In most cases, it's a JSON array of entries with &lt;code&gt;stream_name&lt;/code&gt; (stdout / stderr), &lt;code&gt;time&lt;/code&gt;, and &lt;code&gt;data&lt;/code&gt;. That's what we'll read and summarize.&lt;/p&gt;

&lt;p&gt;If your notebook produces large output files, narrowing things down with &lt;code&gt;--file-pattern&lt;/code&gt; speeds it up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;The Kaggle CLI can authenticate via the &lt;code&gt;KAGGLE_API_TOKEN&lt;/code&gt; environment variable, &lt;code&gt;~/.kaggle/access_token&lt;/code&gt;, or the classic &lt;code&gt;~/.kaggle/kaggle.json&lt;/code&gt;, among others. I went with &lt;strong&gt;reading the token from a dedicated file and passing it as an environment variable only to the child process&lt;/strong&gt;. That way, whether the script is called from a &lt;code&gt;.bat&lt;/code&gt; file or from an agent, the token is loaded the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The summary script
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;kaggle_status_summary.py&lt;/code&gt;. It only uses the standard library.&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;# kaggle_status_summary.py
&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;csv&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;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;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="c1"&gt;# Force stdout to UTF-8, even on Windows (Python 3.7+)
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reconfigure&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconfigure&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="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DEFAULT_TOKEN_FILE&lt;/span&gt; &lt;span class="o"&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;home&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="s"&gt;.config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kaggle_token.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_file&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Read the token from a file and build env vars for the child process.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&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;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PYTHONIOENCODING&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="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="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PYTHONUTF8&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token_file&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;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;KAGGLE_API_TOKEN&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;token_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&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="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;r&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="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env&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="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;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="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&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="si"&gt;{&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&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="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;300&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;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kaggle&lt;/span&gt;&lt;span class="sh"&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;kernels&lt;/span&gt;&lt;span class="sh"&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;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;has status &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="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;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Failure message: &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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&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;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&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;read_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_path&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;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;Return a list of (stream_name, text). Fall back to plain text if it isn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t JSON.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&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="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&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;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&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;stream_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;except &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="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&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;line&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;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&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;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kernel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner/kernel-slug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kaggle_out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kaggle_status_log.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--token-file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TOKEN_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;out_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;complete&lt;/span&gt;&lt;span class="sh"&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;error&lt;/span&gt;&lt;span class="sh"&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;cancelacknowledged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&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;exist_ok&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="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kaggle&lt;/span&gt;&lt;span class="sh"&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;kernels&lt;/span&gt;&lt;span class="sh"&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;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-o&lt;/span&gt;&lt;span class="sh"&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;-q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
        &lt;span class="n"&gt;log_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;out_dir&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;log_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;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;err_pat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(Error|Exception|Traceback)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stderr_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stderr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;err_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err_pat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;last_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&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;now&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;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strftime&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 %H:%M:%S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# The "~7 line" summary
&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;[kernel]  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernel&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="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;[checked] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;now&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="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;[status]  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&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; / &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="k"&gt;else&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[log]     &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; entries (stderr: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stderr_n&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="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;[errors]  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err_lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; lines&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[last_err] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;err_lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err_lines&lt;/span&gt; &lt;span class="k"&gt;else&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="si"&gt;}&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[tail]    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;last_line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  / files: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&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="c1"&gt;# Append one row to a CSV history (with BOM so Excel opens it cleanly)
&lt;/span&gt;    &lt;span class="n"&gt;csv_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;csv_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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;csv_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&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;newline&lt;/span&gt;&lt;span class="o"&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;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-sig&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="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;(&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;if&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerow&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checked_at&lt;/span&gt;&lt;span class="sh"&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;kernel&lt;/span&gt;&lt;span class="sh"&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;status&lt;/span&gt;&lt;span class="sh"&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;log_entries&lt;/span&gt;&lt;span class="sh"&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;stderr&lt;/span&gt;&lt;span class="sh"&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;error_lines&lt;/span&gt;&lt;span class="sh"&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;last_error&lt;/span&gt;&lt;span class="sh"&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;files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerow&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;stderr_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err_lines&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err_lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err_lines&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running it looks like this (values are just an example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[kernel]  koheip/my-notebook
[checked] 2026-10-31 15:42:10
[status]  complete
[log]     214 entries (stderr: 12)
[errors]  0 lines
[last_err] -
[tail]    Done.  / files: 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things I was deliberate about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exit code&lt;/strong&gt;: it returns 1 when the status is &lt;code&gt;error&lt;/code&gt;, so it's easy to branch on with &lt;code&gt;if errorlevel 1&lt;/code&gt; in a &lt;code&gt;.bat&lt;/code&gt; file or on the agent side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status normalization&lt;/strong&gt;: to absorb differences between CLI versions, &lt;code&gt;KernelWorkerStatus.COMPLETE&lt;/code&gt; becomes &lt;code&gt;complete&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No download while running&lt;/strong&gt;: for &lt;code&gt;running&lt;/code&gt; / &lt;code&gt;queued&lt;/code&gt;, it only checks the status and exits, so it stays lightweight even if you call it often&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, the Kaggle API has rate limits. If you monitor in a loop, leave a gap of a few minutes between checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the token file
&lt;/h3&gt;

&lt;p&gt;Generate a token on &lt;a href="https://www.kaggle.com/settings/api" rel="noopener noreferrer"&gt;your Kaggle settings page&lt;/a&gt; and save it as a one-line text file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# PowerShell: create the token file (it should contain only the token, on one line)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;\.config"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;\.config\kaggle_token.txt"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_TOKEN_HERE"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Encoding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ascii&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NoNewline&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm strict about two rules: &lt;strong&gt;never put this file under Git&lt;/strong&gt;, and never paste it into an article or a chat. When an agent uses the script, all it needs is the script's path; it never has to see the token itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing garbled text on Windows
&lt;/h2&gt;

&lt;p&gt;On Windows, the console code page often isn't UTF-8 by default (on Japanese Windows it's CP932 / Shift_JIS). Kaggle logs, on the other hand, are UTF-8. If you do nothing, non-ASCII characters get garbled, or the script dies with a &lt;code&gt;UnicodeEncodeError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix is to switch to UTF-8 on &lt;strong&gt;both the console side and the Python side&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling from a .bat file
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="nb"&gt;chcp&lt;/span&gt; &lt;span class="m"&gt;65001&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="kr"&gt;nul&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="kd"&gt;PYTHONIOENCODING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kd"&gt;utf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="kd"&gt;PYTHONUTF8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="kd"&gt;python&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="vm"&gt;%~dp0&lt;/span&gt;&lt;span class="s2"&gt;kaggle_status_summary.py"&lt;/span&gt; &lt;span class="kd"&gt;koheip&lt;/span&gt;&lt;span class="na"&gt;/my-notebook
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;errorlevel&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;The&lt;/span&gt; &lt;span class="nb"&gt;run&lt;/span&gt; &lt;span class="kd"&gt;ended&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="kd"&gt;an&lt;/span&gt; &lt;span class="kd"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your &lt;code&gt;.bat&lt;/code&gt; file itself contains non-ASCII text, save it as UTF-8 so it displays correctly after &lt;code&gt;chcp 65001&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling from PowerShell
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;chcp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;65001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;OutputEncoding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Text.Encoding&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$OutputEncoding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Text.Encoding&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;PYTHONIOENCODING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"utf-8"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;PYTHONUTF8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\kaggle_status_summary.py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;koheip/my-notebook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$LASTEXITCODE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[!] The run ended with an error"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What the Python side does
&lt;/h3&gt;

&lt;p&gt;Inside the script, I also do three things just to be safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sys.stdout.reconfigure(encoding="utf-8")&lt;/code&gt; to make stdout UTF-8&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;subprocess.run(..., encoding="utf-8", errors="replace")&lt;/code&gt; to read the child process output as UTF-8&lt;/li&gt;
&lt;li&gt;Pass &lt;code&gt;PYTHONIOENCODING=utf-8&lt;/code&gt; to the child process (the Kaggle CLI) as well&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are cases where &lt;code&gt;chcp 65001&lt;/code&gt; alone, or &lt;code&gt;PYTHONIOENCODING&lt;/code&gt; alone, only fixes half of the problem. That tripped me up at first. &lt;strong&gt;Set both&lt;/strong&gt; and you're good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kaggle kernels status&lt;/code&gt; gives you the status; &lt;code&gt;kaggle kernels output&lt;/code&gt; gives you the output files and &lt;code&gt;&amp;lt;slug&amp;gt;.log&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reading the log in Python and condensing it into a ~7-line summary plus one CSV row makes checking much easier&lt;/li&gt;
&lt;li&gt;Loading the token from a file and passing it to the child process means &lt;code&gt;.bat&lt;/code&gt; files and agents use the same path&lt;/li&gt;
&lt;li&gt;On Windows, use both &lt;code&gt;chcp 65001&lt;/code&gt; and &lt;code&gt;PYTHONIOENCODING=utf-8&lt;/code&gt; to avoid garbled text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compared to the days of scrolling through logs in Notepad, I now spend far less time checking runs, and I hand far less text to my agents. I hope this helps anyone else running Kaggle notebooks from their local machine!&lt;/p&gt;

</description>
      <category>kaggle</category>
      <category>python</category>
      <category>cli</category>
      <category>windows</category>
    </item>
    <item>
      <title>Eliminating Accidental Submissions in Kaggle Work: The Kaggle CLI, a Confirmation Wrapper, and Measuring on Production Hardware</title>
      <dc:creator>kohei matsunobu</dc:creator>
      <pubDate>Sat, 17 Oct 2026 10:56:10 +0000</pubDate>
      <link>https://dev.to/koheip/eliminating-accidental-submissions-in-kaggle-work-the-kaggle-cli-a-confirmation-wrapper-and-2a82</link>
      <guid>https://dev.to/koheip/eliminating-accidental-submissions-in-kaggle-work-the-kaggle-cli-a-confirmation-wrapper-and-2a82</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published in Japanese on &lt;a href="https://zenn.dev/koheip/articles/kaggle-safe-submit-workflow" rel="noopener noreferrer"&gt;Zenn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I've been working on Kaggle with help from LLM agents and scripts. Automation makes things easier, but it also nearly caused accidents, like the workflow going all the way to submission without anyone checking.&lt;/p&gt;

&lt;p&gt;In this article, I'll cover how to work with notebooks using the Kaggle CLI, a wrapper that always puts a human confirmation step in the loop, and what I learned from the differences between my validation environment and the production environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Checking notebook status with the Kaggle CLI
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setting up the token
&lt;/h3&gt;

&lt;p&gt;With the newer token method, you put your token in the &lt;code&gt;KAGGLE_API_TOKEN&lt;/code&gt; environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KAGGLE_API_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"(your token issued by Kaggle)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Getting notebook status and output
&lt;/h3&gt;

&lt;p&gt;You can check the status of a notebook running on Kaggle and fetch its output from the CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check whether it's running, finished, or failed&lt;/span&gt;
kaggle kernels status &amp;lt;username&amp;gt;/&amp;lt;notebook-name&amp;gt;

&lt;span class="c"&gt;# Download the output files locally&lt;/span&gt;
kaggle kernels output &amp;lt;username&amp;gt;/&amp;lt;notebook-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since you can see the status without opening a browser, it's much easier to handle from scripts and agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A confirmation wrapper to prevent accidental submissions
&lt;/h2&gt;

&lt;p&gt;I once had an automated batch go all the way to submission without any confirmation. Submissions are often limited in number, and they can't be undone.&lt;/p&gt;

&lt;p&gt;So I built a wrapper that splits the path to submission into these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Show which file will be submitted&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm with y/N&lt;/strong&gt; (pressing just Enter means "No")&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run it&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate the output&lt;/strong&gt; (whether the file exists, whether the format is correct, etc.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Only display the submit command and stop there&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the final submission, a human runs the displayed command themselves. This way, even if something breaks somewhere in the automation, a human always takes a look right before submission.&lt;/p&gt;

&lt;p&gt;The confirmation part can be written in Python in just a few lines, for example:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; [y/N]: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run with this file?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aborted&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;The key is making "No" the default. Even when it's called from an agent or script with no input, it won't proceed on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Measure time-limited workloads on the same hardware as production
&lt;/h2&gt;

&lt;p&gt;When I was doing local validation on a fast GPU, the trends in the results sometimes differed from those on the slower GPU used in production.&lt;/p&gt;

&lt;p&gt;When a workload has a time limit, slower hardware alone can cause it to be cut off partway, or change what you can get done within the limit. Just because something "fits in time" on a fast environment doesn't mean it will in production.&lt;/p&gt;

&lt;p&gt;I learned that for time-limited workloads, it's important to &lt;strong&gt;measure on the same hardware as production&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Once you set &lt;code&gt;KAGGLE_API_TOKEN&lt;/code&gt;, the Kaggle CLI lets you track your notebooks with &lt;code&gt;kernels status&lt;/code&gt; and &lt;code&gt;kernels output&lt;/code&gt;, no browser needed&lt;/li&gt;
&lt;li&gt;Even with automation, always put a human confirmation step right before submission (default to "No", and only display the submit command)&lt;/li&gt;
&lt;li&gt;Measure time-limited workloads on the same hardware as production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The more I automate, the more I feel it's important to decide up front "where a human steps in to stop things."&lt;/p&gt;

</description>
      <category>kaggle</category>
      <category>python</category>
      <category>cli</category>
      <category>automation</category>
    </item>
    <item>
      <title>I Stopped Feeding Whole Jupyter Notebooks to LLMs and Cut Tokens by Over 95% (Claude Code / Codex CLI / PowerShell)</title>
      <dc:creator>kohei matsunobu</dc:creator>
      <pubDate>Sat, 17 Oct 2026 10:56:09 +0000</pubDate>
      <link>https://dev.to/koheip/i-stopped-feeding-whole-jupyter-notebooks-to-llms-and-cut-tokens-by-over-95-claude-code-codex-2ljh</link>
      <guid>https://dev.to/koheip/i-stopped-feeding-whole-jupyter-notebooks-to-llms-and-cut-tokens-by-over-95-claude-code-codex-2ljh</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published in Japanese on &lt;a href="https://qiita.com/koheip/items/9cd92a31e747e019da0b" rel="noopener noreferrer"&gt;Qiita&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When I work on Kaggle, I write code in Jupyter notebooks and ask Claude Code or Codex CLI for reviews and advice along the way. The problem: passing a whole notebook to an LLM came to &lt;strong&gt;about 30k tokens per notebook&lt;/strong&gt;, and after asking a few times, the cost added up fast.&lt;/p&gt;

&lt;p&gt;In this article, I'll share the tricks I used to save tokens, plus the gotchas I hit when calling these CLIs from Windows PowerShell.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Pass only the "code cell diff" of the notebook
&lt;/h2&gt;

&lt;p&gt;An .ipynb file is JSON under the hood, and it's packed with cell outputs (logs and images) and metadata. In most cases, the code is all I actually want the LLM to look at, so I wrote a script that does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drops outputs and metadata, and extracts only the code cells&lt;/li&gt;
&lt;li&gt;Outputs &lt;strong&gt;only the diff&lt;/strong&gt; against the previous version&lt;/li&gt;
&lt;li&gt;Replaces long lines over 300 characters with their "length" and a "hash" (all I need to know is whether the content changed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this, comparing versions fits in &lt;strong&gt;about 1–1.6k tokens (a reduction of over 95%)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Also, long prompt strings inside a notebook make it hard to read, so I create a "reading view" where those strings are split out into separate .txt files. The notebook I actually run stays as a single file, and I pass the reading view to the LLM or for reviews. That's how I keep the two roles separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Calling Claude Code / Codex CLI as commands from another program
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Claude Code
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;claude -p&lt;/code&gt;, you can ask a single question and get an answer back without opening the interactive UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;--max-turns&lt;/span&gt; 1 &lt;span class="nt"&gt;--disallowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Bash,Read,..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is to &lt;strong&gt;disallow tools like file reading and pass only the summary you've prepared yourself&lt;/strong&gt;. Since it won't go off and read files in your repository on its own, token usage stays cheap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codex CLI
&lt;/h3&gt;

&lt;p&gt;I installed it with npm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; @openai/codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When logging in through the browser failed, authenticating with a URL and a code worked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex login &lt;span class="nt"&gt;--device-auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I pass the question via standard input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Your question here"&lt;/span&gt; | codex &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--skip-git-repo-check&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Japanese text gets garbled in Windows PowerShell
&lt;/h2&gt;

&lt;p&gt;When I piped a Japanese prompt from PowerShell into the CLI, the text came out garbled (mojibake). Setting the following three to UTF-8 fixes it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;InputEncoding&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Text.Encoding&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nx"&gt;OutputEncoding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Text.Encoding&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$OutputEncoding&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Text.Encoding&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, PowerShell doesn't let you chain commands with &lt;code&gt;&amp;amp;&lt;/code&gt; the way cmd does; you get an &lt;code&gt;AmpersandNotAllowed&lt;/code&gt; error. If you write commands with a cmd mindset, this will trip you up, so watch out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Instead of passing the whole notebook, passing only the code cell diff cut tokens by over 95%&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;claude -p&lt;/code&gt;, disallowing tools and passing only a summary keeps it cheap&lt;/li&gt;
&lt;li&gt;If Codex CLI login fails, try &lt;code&gt;codex login --device-auth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When passing Japanese text in PowerShell, set the three encodings to UTF-8&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope this helps anyone else who's going back and forth between LLMs and notebooks like I am.&lt;/p&gt;

</description>
      <category>python</category>
      <category>jupyter</category>
      <category>llm</category>
      <category>powershell</category>
    </item>
  </channel>
</rss>
