<?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: Ashiha Mahesh Kumar</title>
    <description>The latest articles on DEV Community by Ashiha Mahesh Kumar (@ashihamaheshkumar).</description>
    <link>https://dev.to/ashihamaheshkumar</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%2F3054852%2F0713b000-844f-466f-ab6b-97695bfa2cc6.png</url>
      <title>DEV Community: Ashiha Mahesh Kumar</title>
      <link>https://dev.to/ashihamaheshkumar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashihamaheshkumar"/>
    <language>en</language>
    <item>
      <title>Nights Watch: Guarding AI Agents Beyond the Wall</title>
      <dc:creator>Ashiha Mahesh Kumar</dc:creator>
      <pubDate>Sun, 26 Jul 2026 15:25:01 +0000</pubDate>
      <link>https://dev.to/ashihamaheshkumar/nights-watch-guarding-ai-agents-beyond-the-wall-am5</link>
      <guid>https://dev.to/ashihamaheshkumar/nights-watch-guarding-ai-agents-beyond-the-wall-am5</guid>
      <description>&lt;p&gt;&lt;em&gt;A technical write-up of a hackathon build for WeMakeDevs' "Agents of SigNoz." This post covers the architecture, the OpenTelemetry instrumentation, and two annotated trace walkthroughs — one clean run and one with deliberately injected drift — from &lt;a href="https://github.com/ashihams/Nights-Watch" rel="noopener noreferrer"&gt;github.com/ashihams/Nights-Watch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Problem statement
&lt;/h2&gt;

&lt;p&gt;Most AI agent safety discussion centers on adversarial failure: prompt injection, jailbreaks, tool poisoning. There is a second, quieter failure mode that gets far less attention: an agent that is not attacked at all, and simply drifts. Given a bounded task — "find and book a flight from SFO to LAX under $400" — an agent can, without any external interference, select a $1,200 option, add an unrequested hotel booking, or otherwise take an action that no longer matches what was approved. Nothing throws an exception. No policy is technically "broken" in the sense of hitting an error path. The system just quietly does more, or something different, than it was asked to do, and by the time this is noticed, an irreversible action (a non-refundable booking, in this case) may already have executed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nights Watch&lt;/strong&gt; is a runtime resilience system built to catch this specific failure class. It does five things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tracks an agent's actual execution against its originally approved plan, step by step.&lt;/li&gt;
&lt;li&gt;Scores each step against policy rules (0–100; higher means more severe deviation).&lt;/li&gt;
&lt;li&gt;Produces a plain-language explanation of &lt;em&gt;why&lt;/em&gt; a violation occurred, at the moment it's detected.&lt;/li&gt;
&lt;li&gt;Pauses execution before an out-of-scope or irreversible action fires.&lt;/li&gt;
&lt;li&gt;Rolls back to the last known-good checkpoint, re-plans, and resumes — either automatically or pending human approval, depending on severity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The architectural claim this post is built around, and the reason it's relevant to a SigNoz-focused write-up specifically: &lt;strong&gt;the Policy Engine queries SigNoz synchronously, on the critical execution path, to help decide what happens next.&lt;/strong&gt; SigNoz is not a passive sink for logs reviewed after the fact — a live query against it is part of the decision loop. That is a materially different integration pattern than "instrument the app, look at dashboards later," and it's the part of the build I think is worth documenting in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. System architecture
&lt;/h2&gt;

&lt;p&gt;Nights Watch has four components, deliberately kept in a strict role separation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Owns state?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;n8n&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Executor. Runs the actual agent workflow (search → select → confirm → book) as a visual workflow. Reports each step's result to the backend over webhook.&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Nights Watch Backend&lt;/strong&gt; (TypeScript, Fastify)&lt;/td&gt;
&lt;td&gt;The decision layer. Contains the Policy Engine, Checkpoint Manager, and Recovery Engine.&lt;/td&gt;
&lt;td&gt;Yes — execution state lives here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SigNoz&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Observability backend. Ingests OpenTelemetry traces/metrics/logs from the backend; exposes a Query API and an MCP server for querying that data.&lt;/td&gt;
&lt;td&gt;No — by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;React Dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Live view over WebSocket: policy score timeline, checkpoint markers, explanation feed, recovery metrics, human-approval controls.&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        React + TypeScript + Tailwind
           (Nights Watch Dashboard)
                     │
            REST  /  WebSocket
                     │
      Nights Watch Backend (TypeScript)
   ┌─────────────────┼──────────────────┐
   │                 │                  │
   ▼                 ▼                  ▼
Policy Engine   Checkpoint Manager   Recovery Engine
   │                 │                  │
   │        (owns execution state,      │
   │         backed by local store)     │
   │                 │                  │
   └─────────────────┼──────────────────┘
                     │
            OpenTelemetry SDK
                     │
                  SigNoz
                     ▲
                     │
          n8n Workflow Engine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;n8n's role is intentionally narrow: it executes steps and reports outcomes. It makes no policy decisions and holds no execution state. That separation matters for the same reason it matters in any control-plane/data-plane split — the component that &lt;em&gt;acts&lt;/em&gt; should not also be the component that &lt;em&gt;judges&lt;/em&gt;, or you lose the ability to independently audit and override.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 The data-source classification rule
&lt;/h3&gt;

&lt;p&gt;The single design decision this whole project hangs on is a strict split between two categories of data, based on latency tolerance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Live context (blocks execution)&lt;/th&gt;
&lt;th&gt;Observability context (informs, doesn't block)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Examples&lt;/td&gt;
&lt;td&gt;current step, current checkpoint, current tool call, current spend&lt;/td&gt;
&lt;td&gt;prior policy violations this run, recovery metrics, historical traces, budget trend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source of truth&lt;/td&gt;
&lt;td&gt;the Checkpoint Manager's own local store&lt;/td&gt;
&lt;td&gt;SigNoz, via Query API or MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency tolerance&lt;/td&gt;
&lt;td&gt;none — the agent loop is blocked on this&lt;/td&gt;
&lt;td&gt;high — SigNoz can be slower without breaking anything&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rule that follows from this table: &lt;strong&gt;if a rollback decision ever depends on the result of a SigNoz query, that's a bug.&lt;/strong&gt; Rollback state is reconstructed from the Checkpoint Manager's own store — never from SigNoz — precisely because a query against an external HTTP service is a latency and availability dependency you don't want in a path that's supposed to fire in milliseconds. SigNoz is where the Policy Engine goes to ask decision-&lt;em&gt;support&lt;/em&gt; questions ("has this run already had a medium-severity violation?"), not decision-&lt;em&gt;critical&lt;/em&gt; questions ("what state do I roll back to?").&lt;/p&gt;

&lt;p&gt;This is also why the project uses two separate access patterns into SigNoz rather than one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct Query API&lt;/strong&gt; — used by the Policy Engine, on the critical path, for fast, typed, deterministic lookups. No LLM indirection here, because the Policy Engine runs on every single step and can't absorb the added latency or non-determinism an LLM-driven query would introduce.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SigNoz MCP server&lt;/strong&gt; — used by the Explanation Layer, which only fires occasionally (on a violation). SigNoz's MCP server (&lt;a href="https://signoz.io/docs/ai/signoz-mcp-server/" rel="noopener noreferrer"&gt;signoz.io/docs/ai/signoz-mcp-server&lt;/a&gt;) exposes tools for querying metrics, traces, logs, alerts, and dashboards through natural language, with the MCP client (an LLM tool-calling loop, in this case) deciding what to ask for rather than the query being pre-specified. That's the right shape for "explain why this happened" — an LLM deciding what context is relevant — and the wrong shape for "should I roll back right now."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Instrumentation: what actually gets sent to SigNoz
&lt;/h2&gt;

&lt;p&gt;The backend uses the OpenTelemetry SDK for Node.js. Every run produces one root span, &lt;code&gt;run.execute&lt;/code&gt;, of kind &lt;code&gt;Internal&lt;/code&gt;, with resource attributes standard to any Node.js OTel setup — &lt;code&gt;service.name: "nights-watch"&lt;/code&gt;, &lt;code&gt;telemetry.sdk.name: "opentelemetry"&lt;/code&gt;, &lt;code&gt;telemetry.sdk.version: "1.30.1"&lt;/code&gt;, &lt;code&gt;process.runtime.name: "nodejs"&lt;/code&gt;, &lt;code&gt;process.runtime.version: "24.5.0"&lt;/code&gt; — plus process-level detail like &lt;code&gt;process.command&lt;/code&gt;, &lt;code&gt;process.owner&lt;/code&gt;, and &lt;code&gt;host.name&lt;/code&gt;, which is standard OpenTelemetry Resource semantic convention data rather than anything custom.&lt;/p&gt;

&lt;p&gt;Underneath the root span sit child spans for each checkpoint boundary: &lt;code&gt;checkpoint.created&lt;/code&gt;, &lt;code&gt;policy.evaluation&lt;/code&gt;, &lt;code&gt;executor.step&lt;/code&gt;, and (on a recovery run) &lt;code&gt;checkpoint.restored&lt;/code&gt;. A clean run produces 14 spans; a run that hits one recovery cycle produces 19.&lt;/p&gt;

&lt;p&gt;The interesting instrumentation decision is how the &lt;em&gt;content&lt;/em&gt; — the plan, the policy score, the recovery outcome — gets attached. Early versions of the instrumentation put this data directly on span attributes, which made the flame graph difficult to read and mixed "facts about the operation as a whole" with "things that happened at a specific moment." OpenTelemetry's own guidance on this draws exactly that distinction: attributes describe the operation as a whole and are best used for data known when the span starts, while an &lt;strong&gt;event&lt;/strong&gt; should be used when the data represents a distinct, timestamped occurrence within the span's lifetime — something that could happen zero or more times (&lt;a href="https://opentelemetry.io/docs/specs/semconv/general/events/" rel="noopener noreferrer"&gt;opentelemetry.io/docs/specs/semconv/general/events&lt;/a&gt;; &lt;a href="https://opentelemetry.io/docs/concepts/signals/traces/" rel="noopener noreferrer"&gt;opentelemetry.io/docs/concepts/signals/traces&lt;/a&gt;). A policy evaluation, a recovery start, and a run completion are all exactly that: discrete, timestamped things that happen during the life of a &lt;code&gt;run.execute&lt;/code&gt; span, not properties of the span as a whole. So each is recorded as a span &lt;strong&gt;event&lt;/strong&gt; on the root span, not a top-level attribute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;plan.ready&lt;/code&gt; — fired once, carries the full generated plan as a JSON attribute&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run.recovery_started&lt;/code&gt; — fired when a violation triggers rollback; carries &lt;code&gt;policy.score&lt;/code&gt;, &lt;code&gt;severity&lt;/code&gt;, and the &lt;code&gt;step&lt;/code&gt; that failed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run.resumed&lt;/code&gt; — fired after successful rollback; carries &lt;code&gt;checkpoint.id&lt;/code&gt;, &lt;code&gt;completedSteps&lt;/code&gt;, &lt;code&gt;durationMs&lt;/code&gt;, and &lt;code&gt;recovery.action&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;checkpoint.pre_irreversible&lt;/code&gt; — fired immediately before an irreversible step (e.g. &lt;code&gt;book&lt;/code&gt;) executes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run.completed&lt;/code&gt; — fired at the end; carries &lt;code&gt;policy.lastScore&lt;/code&gt;, &lt;code&gt;recovery.count&lt;/code&gt;, the list of &lt;code&gt;checkpoints&lt;/code&gt;, and &lt;code&gt;steps&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One caveat worth being upfront about: OpenTelemetry has an in-progress proposal to deprecate the span-events API in favor of log-based events correlated to the active span context (&lt;a href="https://opentelemetry.io/blog/2026/deprecating-span-events/" rel="noopener noreferrer"&gt;opentelemetry.io/blog/2026/deprecating-span-events&lt;/a&gt;). Existing data and span-event views are expected to keep working, and this is a forward-looking migration rather than something that affects a project built today — but it's a reasonable thing to track if you're instrumenting a similar system now and expect it to still be running in a year or two.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;plan.ready&lt;/code&gt; event's JSON payload is the single most load-bearing piece of data in the whole system, because it's what the Policy Engine diffs every actual step against:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"goal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Find and book a flight from SFO to LAX under $400"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxBudget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LAX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"steps"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&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="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Search flights within budget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"expectedTool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search_flights"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"constraints"&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="nl"&gt;"maxPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&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="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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"select"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Select an in-budget flight option"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"expectedTool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"select_flight"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"constraints"&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="nl"&gt;"maxPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&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="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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"confirm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Confirm passenger and itinerary details"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"expectedTool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"confirm_details"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"constraints"&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="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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"book"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Book the confirmed flight"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"expectedTool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"book_flight"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"constraints"&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="nl"&gt;"irreversible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="nl"&gt;"expectedTargets"&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="s2"&gt;"flights"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every field here is queryable structured data, not free text — which is what lets the Policy Engine's evaluation be a deterministic rule check (does &lt;code&gt;expectedTool&lt;/code&gt; for this step match the tool actually invoked; does the actual value respect the step's &lt;code&gt;constraints&lt;/code&gt;) rather than something fuzzier.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Trace walkthrough — clean run
&lt;/h2&gt;

&lt;p&gt;The demo scenario is fixed: find and book a flight from SFO to LAX, budget capped at $400. On a clean run, the dashboard's Policy Score Timeline — driven by data streamed out of SigNoz over WebSocket — stays at 0 across all four checkpoints.&lt;/p&gt;

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

&lt;p&gt;The corresponding SigNoz trace (&lt;code&gt;run.execute&lt;/code&gt;, 4.2 s, 14 spans, 0 errors) shows the expected repeating pattern in the waterfall: &lt;code&gt;checkpoint.created&lt;/code&gt; → &lt;code&gt;executor.step&lt;/code&gt; → &lt;code&gt;policy.evaluation&lt;/code&gt;, once per plan step.&lt;/p&gt;

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

&lt;p&gt;Two details from the span's Overview tab are worth calling out specifically because they're standard OTel resource attributes doing real diagnostic work, not custom instrumentation: &lt;code&gt;process.command&lt;/code&gt; (&lt;code&gt;D:\project\SignozHack\backend\src\index.ts&lt;/code&gt;) and &lt;code&gt;process.command_args&lt;/code&gt;, which together let you confirm — from inside the trace, without touching the terminal — exactly which build and entry point produced this run. That's the kind of detail that matters when you're debugging "why did staging behave differently from local" and don't want to rely on memory of what was deployed when.&lt;/p&gt;

&lt;p&gt;By the closing &lt;code&gt;run.completed&lt;/code&gt; event:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff34qe2y048m2zv5dfdhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff34qe2y048m2zv5dfdhk.png" alt=" " width="799" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;policy.lastScore: 0&lt;/code&gt;, &lt;code&gt;recovery.count: 0&lt;/code&gt; — the run never needed the Recovery Engine, because the Policy Engine never scored a step above the pause threshold. This is the boring, expected outcome. Section 5 breaks it on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Trace walkthrough — drift injection and recovery
&lt;/h2&gt;

&lt;p&gt;The Control Room dashboard has a second entry point for exactly this: &lt;strong&gt;Start Run (Inject Drift)&lt;/strong&gt;, which perturbs the "select" step so it resolves to a $1,200 fare instead of staying under the $400 cap — a direct simulation of the silent-overreach scenario from Section 1.&lt;/p&gt;

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

&lt;p&gt;The moment &lt;code&gt;select_flight&lt;/code&gt; is invoked at $1,200, the Policy Engine's rule evaluation fires before the workflow is allowed to proceed to &lt;code&gt;confirm&lt;/code&gt;. This is the live-query path from Section 2.1 doing its job: the backend is blocked, mid-execution, on this evaluation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40zgczidwgz8wsrigthh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40zgczidwgz8wsrigthh.png" alt=" " width="799" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the three severity thresholds visible on the Policy Score Timeline's y-axis in these screenshots: &lt;strong&gt;Pause at 40, Rollback at 70, Hard-stop at 90.&lt;/strong&gt; This run's violation scored 40 — a medium-severity pause that surfaces for human approval rather than triggering an automatic hard rollback, which is the intended behavior for a violation that's clearly wrong but not yet irreversible.&lt;/p&gt;

&lt;p&gt;The corresponding SigNoz span event, &lt;code&gt;run.recovery_started&lt;/code&gt;, records the identical numbers the dashboard is rendering:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ad41cmn49s4yhdnp039.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ad41cmn49s4yhdnp039.png" alt=" " width="799" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;policy.score: "40"&lt;/code&gt;, &lt;code&gt;severity: "medium"&lt;/code&gt;, &lt;code&gt;step: "select"&lt;/code&gt;. This one-to-one correspondence between the dashboard and the raw trace data was a deliberate verification point during the build — the dashboard reads these values live off SigNoz via WebSocket, so there's no separate in-memory state that could silently drift out of sync with what's actually recorded in the trace.&lt;/p&gt;

&lt;p&gt;Choosing &lt;strong&gt;Reject &amp;amp; Recover&lt;/strong&gt; triggers the Recovery Engine, which reconstructs state from the Checkpoint Manager's own store (not from SigNoz — see Section 2.1) and re-plans from the last safe checkpoint:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fupkd965q84rgb2egpefe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fupkd965q84rgb2egpefe.png" alt=" " width="476" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;recovery.action: "rollback_replan"&lt;/code&gt;, completed in &lt;strong&gt;13 milliseconds&lt;/strong&gt;. That number is the direct payoff of the architectural split: because rollback reads from a local store rather than round-tripping to an external HTTP API, its speed is bounded by local I/O, not network latency or SigNoz query load. Once resumed, the agent re-executes &lt;code&gt;select&lt;/code&gt; correctly, then &lt;code&gt;confirm&lt;/code&gt; and &lt;code&gt;book&lt;/code&gt;. The dashboard's Recovery Health panel updates accordingly:&lt;/p&gt;

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

&lt;p&gt;And the full Policy Score Timeline for the run shows the shape of the whole incident in one chart — a spike to 40 at the second &lt;code&gt;select&lt;/code&gt; attempt, then back to 0 for &lt;code&gt;confirm&lt;/code&gt; and &lt;code&gt;book&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8xo6z3eacoi9m7qygf56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8xo6z3eacoi9m7qygf56.png" alt=" " width="799" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final &lt;code&gt;run.completed&lt;/code&gt; event confirms the run finished with one recovery cycle and the original goal still met:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4iju5oc5iz2h7yz906c1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4iju5oc5iz2h7yz906c1.png" alt=" " width="471" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;recovery.count: 1&lt;/code&gt;. The agent hit a real, scored violation and did not need a full restart or an unrecoverable failure state to get back on track.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Discussion
&lt;/h2&gt;

&lt;p&gt;Three things stood out as genuinely non-obvious while building this, beyond what the architecture diagram alone communicates:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deciding what SigNoz is and isn't allowed to gate was the actual hard design problem.&lt;/strong&gt; It's tempting, especially with an MCP server available, to let a live query decide "should this rollback happen." An early prototype did something close to this, and the added latency plus the non-determinism of an LLM-mediated query made the pause-and-recover loop noticeably slower and occasionally inconsistent in repeated testing. The fix wasn't a performance optimization — it was a re-scoping of &lt;em&gt;which questions SigNoz is allowed to answer on the critical path at all&lt;/em&gt; (Section 2.1's table).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Span events, not span attributes, are what kept the trace view legible.&lt;/strong&gt; Once the plan JSON, policy scores, and recovery outcomes moved off top-level span attributes and onto discrete events on the root span, the flame graph stopped needing to be scrolled through wide attribute dumps to find the one relevant number for a given moment in the run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 13 ms and a 2 ms recovery time are not incidental — they're the metric that actually validates the architecture.&lt;/strong&gt; If rollback took seconds instead of milliseconds, the "resilience" claim in the project's name would be weaker in practice than in the pitch. The number in the trace is the honest test of whether the live/observability split held up under an actual injected failure, not just in the design doc.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Summary
&lt;/h2&gt;

&lt;p&gt;Nights Watch treats SigNoz as an active input to a runtime decision loop, not a passive record of what already happened: a fast, typed Query API path for the Policy Engine's every-step decisions, and the MCP server's natural-language querying for the occasional, non-blocking "explain this" case. The two traces above — one clean, one with an induced $1,200 drift and a 13 ms rollback — are the unedited output of that split working as designed.&lt;/p&gt;

&lt;p&gt;Architecture and full workflow diagrams for the n8n executor layer are in progress and will follow in an update to this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/ashihams/Nights-Watch" rel="noopener noreferrer"&gt;github.com/ashihams/Nights-Watch&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Event:&lt;/strong&gt; &lt;a href="https://www.wemakedevs.org/hackathons/signoz" rel="noopener noreferrer"&gt;WeMakeDevs — Agents of SigNoz Hackathon&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;References:&lt;/strong&gt; &lt;a href="https://opentelemetry.io/docs/concepts/signals/traces/" rel="noopener noreferrer"&gt;OpenTelemetry — Traces&lt;/a&gt; · &lt;a href="https://opentelemetry.io/docs/specs/semconv/general/events/" rel="noopener noreferrer"&gt;OpenTelemetry — Semantic Conventions for Events&lt;/a&gt; · &lt;a href="https://signoz.io/docs/ai/signoz-mcp-server/" rel="noopener noreferrer"&gt;SigNoz MCP Server docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>signoz</category>
      <category>automation</category>
    </item>
    <item>
      <title>DueIt : Your To-Do List Doesn't Know You So I Gave Mine Three Brains</title>
      <dc:creator>Ashiha Mahesh Kumar</dc:creator>
      <pubDate>Mon, 25 May 2026 07:36:53 +0000</pubDate>
      <link>https://dev.to/ashihamaheshkumar/your-to-do-list-doesnt-know-you-so-i-gave-mine-three-brains-27me</link>
      <guid>https://dev.to/ashihamaheshkumar/your-to-do-list-doesnt-know-you-so-i-gave-mine-three-brains-27me</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Build with Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;What I Built&lt;br&gt;
DueIt is an adaptive task planning system powered by three Gemma 4 models. You create one task — and the system plans your entire path to the deadline, generates a Notion workspace, scrapes real resources, builds a visual workflow diagram, and replans automatically when you miss a day.&lt;br&gt;
It solves two problems no to-do app has ever fixed:&lt;/p&gt;

&lt;p&gt;Forgetting an assignment exists until the night before&lt;br&gt;
Not having enough time because you started too late&lt;/p&gt;

&lt;p&gt;Three Gemma 4 models power the system — each chosen for a specific reason:&lt;/p&gt;

&lt;p&gt;31B Dense for deep reasoning — task planning, time estimation, Notion doc generation&lt;br&gt;
2B for speed — morning replanning in under 2 seconds&lt;br&gt;
26B MoE for multi-domain synthesis — Excalidraw workflow diagrams and analysis&lt;/p&gt;

&lt;p&gt;Let me walk you through exactly what happens when you use DueIt.&lt;/p&gt;

&lt;p&gt;You open the app and create a task.&lt;/p&gt;

&lt;p&gt;Say you need to build a RAG chatbot. You type the title, pick "Work" as the category, set the deadline, and hit create.&lt;br&gt;
Behind the scenes, Gemma 4 31B Dense starts reasoning. It reads the task, estimates that this will take around 55 hours across 5 phases, and generates a structured day-by-day schedule with specific microtasks for each day. It calculates pressure and risk scores based on your deadline proximity and workload.&lt;br&gt;
When the plan lands, your home screen shows the task with every microtask you can expand and check off as you go.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo50u82efdk9ud0475qnp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo50u82efdk9ud0475qnp.png" alt=" " width="563" height="794"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your calendar fills itself.&lt;br&gt;
You didn't manually block time. You didn't drag anything. Gemma 31B distributed your microtasks across every day from now until the deadline as real time-blocked sessions — morning workout at 7, landing page mockup at 9, RAG chatbot work at 11:30.&lt;br&gt;
Multiple tasks from different categories sit side by side on the same day. You open the calendar and know exactly what to work on, when, and for how long.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnqfrvsda3l6vg03o9hl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnqfrvsda3l6vg03o9hl.png" alt=" " width="584" height="789"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dashboard tells you the truth.&lt;br&gt;
Not motivational quotes. Not "you got this." The truth.&lt;br&gt;
Your pressure is at 71% — deadlines are close and tasks are piling up. Your risk of missing a deadline is 51% — you're on the edge. Your streak is 7 days — you've been showing up.&lt;br&gt;
The work rhythm chart shows how your productivity flows across the week. Tuesday and Wednesday are your peak days. Sunday drops off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdtaf4vgm2k1omj25lyf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdtaf4vgm2k1omj25lyf.png" alt=" " width="545" height="798"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll down and the AI productivity heatmap shows your entire month. Dark purple means you crushed it. Light means you barely touched anything. One glance and you see the pattern you didn't know you had.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvacrhtngff0u148yucdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvacrhtngff0u148yucdv.png" alt=" " width="550" height="785"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile, Notion builds your workspace.&lt;br&gt;
While you were looking at the dashboard, DueIt auto-generated a complete Notion page for your task. No templates. No manual setup.&lt;br&gt;
The page has a task overview with your deadline and time estimate. An expected output section. And a Progress Tracker — an inline database with every phase mapped out: Research, Design, Implementation, Testing, Review &amp;amp; Deploy. All set to "To Do."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw20ffn27gbhu30jzpbt9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw20ffn27gbhu30jzpbt9.png" alt=" " width="764" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll down and every phase has detailed notes with specific microtasks. Phase 1 tells you to identify data sources, research RAG frameworks like LangChain and LlamaIndex, evaluate vector databases. Phase 2 tells you to collect sample documents, design the system architecture, define the chunking approach.&lt;br&gt;
The workflow diagram link sits right there — click it and you're in Excalidraw.&lt;br&gt;
And at the bottom, curated resources that TinyFish scraped from the web. Not generic Google results — real links: Pinecone docs on building RAG chatbots, Microsoft Learn guides, LangChain documentation, DEV Community tutorials.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k0x346ubo6zxxvc89du.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k0x346ubo6zxxvc89du.png" alt=" " width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Excalidraw shows you the visual map.&lt;br&gt;
Gemma 4 26B MoE took the phases from your plan and generated a Mermaid flowchart. TinyFish rendered it in Excalidraw and started a live collaboration room.&lt;br&gt;
Collect Source Documents → Chunk &amp;amp; Embed Data → Index Vector Store → Configure Retrieval Logic → Integrate LLM Prompting → Build Chat Interface.&lt;br&gt;
Every task gets a unique workflow specific to its actual phases. Not a template — a diagram that matches what Gemma planned for this specific task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xv4u9uxt78bz7hjwrkb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xv4u9uxt78bz7hjwrkb.png" alt=" " width="799" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cursor reads the Notion page and starts building.&lt;br&gt;
This is where planning becomes execution.&lt;br&gt;
Cursor connects to your Notion workspace through MCP. It reads the Progress Tracker, understands the phases and their order. It reads the Notes section, understands the specific microtasks within each phase.&lt;br&gt;
Then it starts working. It executes Phase 1 — generates actual project structure, files, and code corresponding to the research phase.&lt;br&gt;
When it finishes, it updates the Notion Progress Tracker. Research phase: Done. Design, Implementation, Testing, Review &amp;amp; Deploy: still To Do. The status change syncs back to your Flutter app automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdw168xccdqi37zwa5ga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdw168xccdqi37zwa5ga.png" alt=" " width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Excalidraw diagram updates too — the first box turns green. Planning and execution stay perfectly synchronized.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzapr19vsg3t4a6fwzfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzapr19vsg3t4a6fwzfv.png" alt=" " width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You skip a day. DueIt doesn't care.&lt;br&gt;
Life happened. You didn't touch anything yesterday. Most apps would just show you a red overdue badge and guilt you into quitting.&lt;br&gt;
DueIt does something different. The next time you open the app, Gemma 4 2B kicks in — in under 2 seconds it checks what you completed, identifies what you missed, and redistributes those microtasks across your remaining days. Your calendar updates. Your pressure meter rises. Your risk adjusts.&lt;br&gt;
But your path to the deadline is still clear. The plan adapted to the real you, not the ideal you who never skips a day.&lt;/p&gt;
&lt;h2&gt;
  
  
  Figma Prototype
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.figma.com/proto/pWtskcjSq2bQLmFsH86Tud/Due-It?node-id=101-100&amp;amp;t=OcFwgMJO84e0bHrg-1" rel="noopener noreferrer"&gt; Click here to explore the prototype&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ISimSpPtd0w"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ashihams" rel="noopener noreferrer"&gt;
        ashihams
      &lt;/a&gt; / &lt;a href="https://github.com/ashihams/due-it" rel="noopener noreferrer"&gt;
        due-it
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI-powered task planning app that automatically breaks tasks into actionable subtasks, adapts daily schedules, and recalculates workload based on missed tasks and approaching deadlines.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;DueIt&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;An AI-powered adaptive task planning system that breaks tasks into microtasks, distributes them across your calendar, and replans automatically when life gets in the way.&lt;/p&gt;
&lt;p&gt;DueIt uses three Gemma 4 models with intentional role separation, Notion integration for workspace generation, and TinyFish for web resource scraping and Excalidraw workflow diagrams.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Repository Layout&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;
├─ due_it/                    # Flutter frontend
│  ├─ lib/
│  │   ├─ models/             # Task, AiSchedule, DueTask data models
│  │   ├─ providers/          # State management (Provider)
│  │   ├─ screens/            # Home, Calendar, Dashboard, Tasks, Auth, Settings, Profile
│  │   ├─ services/           # API calls, Firestore, Notion
│  │   ├─ theme/              # Colors, spacing, typography
│  │   └─ widgets/            # Reusable UI components
│  ├─ test/                   # Unit tests
│  └─ pubspec.yaml
├─ due_it_backend/            # FastAPI backend
│  ├─ main.py                 # API gateway, planning logic, scheduling algorithms
│  ├─ notion/                 # Notion integration package
│  │&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ashihams/due-it" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23eba7dtpqpxx1d8bbyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23eba7dtpqpxx1d8bbyh.png" alt=" " width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter app authenticates through Firebase and sends every request to FastAPI on Cloud Run&lt;/li&gt;
&lt;li&gt;FastAPI is a thin router — validates tokens, then hands off to the right Gemma model based on task type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;31B Dense&lt;/strong&gt; handles planning and Notion doc generation. &lt;strong&gt;2B&lt;/strong&gt; handles morning replans. &lt;strong&gt;26B MoE&lt;/strong&gt; handles diagram synthesis&lt;/li&gt;
&lt;li&gt;Every AI response passes through &lt;code&gt;_validate_ai_plan()&lt;/code&gt; before touching Firestore&lt;/li&gt;
&lt;li&gt;Notion workspace creation, TinyFish resource scraping, and Excalidraw diagram generation all fire simultaneously as &lt;code&gt;BackgroundTasks&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Firestore syncs everything back to Flutter in real time&lt;/li&gt;
&lt;li&gt;Cursor connects to Notion through MCP, executes phases as code, and updates the progress tracker — closing the loop&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend
├── Flutter (Dart)
├── Provider — state management
└── Firebase SDK — auth + real-time Firestore sync

Backend
├── FastAPI (Python) — API gateway
├── HTTPX — async HTTP client for OpenRouter
├── BackgroundTasks — parallel Notion/TinyFish/Excalidraw
└── Cloud Run — serverless deployment

AI Models (via OpenRouter)
├── Gemma 4 31B Dense  — planning, estimation, Notion docs
├── Gemma 4 2B         — morning replan, schedule adjustment
└── Gemma 4 26B MoE    — Excalidraw diagrams, Mermaid generation

Data
├── Cloud Firestore — users, tasks, schedules, metrics
└── Firebase Auth — email/password authentication

Integrations
├── Notion API — workspace generation (notion/ package)
├── TinyFish API — web resource scraping + Excalidraw automation
├── Excalidraw — visual workflow diagrams + collaboration rooms
└── Cursor AI — code execution via Notion MCP

Deployment
├── Google Cloud Run — backend
└── Vercel — web frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Used Gemma 4
&lt;/h2&gt;

&lt;p&gt;DueIt uses &lt;strong&gt;all three Gemma 4 model architectures&lt;/strong&gt; — not because more models sounds impressive, but because each layer of the system has fundamentally different computational demands. A planning engine that reasons through task complexity for 10 seconds is fine. A morning replanner that takes 10 seconds is unusable. A diagram generator that can't hold task structure and visual syntax in its head simultaneously produces garbage. One model can't optimize for all three.&lt;/p&gt;




&lt;h3&gt;
  
  
  Gemma 4 31B Dense — the reasoning layer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Used for:&lt;/strong&gt; Task planning, time estimation, phase generation, Notion document content&lt;/p&gt;

&lt;p&gt;When you create a task like "Build a RAG chatbot — due in 2 weeks," the model doesn't just split it into equal chunks. It reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is a multi-phase engineering task — it needs research, design, implementation, testing, and deployment&lt;/li&gt;
&lt;li&gt;Research and design are front-loaded — you can't implement what you haven't designed&lt;/li&gt;
&lt;li&gt;Implementation is the heaviest phase — it needs 40% of total time, not 20%&lt;/li&gt;
&lt;li&gt;The last two days should be lighter — buffer for overrun, not new work&lt;/li&gt;
&lt;li&gt;Estimated total: 55 hours across 5 phases, 12 microtasks distributed across 10 working days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those decisions depends on every other decision. Change the estimate and the distribution changes. Change the phase count and the per-day load changes. This is &lt;strong&gt;constraint-dependent multi-step reasoning&lt;/strong&gt; — not pattern matching, not template filling.&lt;/p&gt;

&lt;p&gt;Dense architecture activates all 31B parameters on every token. That matters here because the output is structured JSON where a single malformed field — a negative &lt;code&gt;estimatedMinutes&lt;/code&gt;, a missing &lt;code&gt;microtasks&lt;/code&gt; array, a schedule longer than the deadline — breaks the entire downstream pipeline. Every token in the output needs the full weight of the model behind it.&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;GEMMA_31B_DENSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google/gemma-4-27b-it&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# The planning prompt includes task title, category, deadline,
# and category-specific instructions (Study vs Work vs Personal)
&lt;/span&gt;&lt;span class="n"&gt;response_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_call_gemma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GEMMA_31B_DENSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plan&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;response_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Validate before anything touches Firestore
&lt;/span&gt;&lt;span class="n"&gt;validated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_validate_ai_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# estimatedMinutes: capped 1-10000
# confidence: clamped 0.0-1.0
# schedule: max 60 days
# microtasks per day: max 10
# missing fields: safe defaults applied
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same model generates Notion document content — task overview, expected output, phase-by-phase notes with microtask breakdowns. This also requires structured reasoning: the Notion page isn't a summary of the plan, it's a &lt;strong&gt;restructured representation&lt;/strong&gt; of the plan optimized for human readability and Cursor agent consumption. Different output format, same reasoning depth.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why not MoE?&lt;/strong&gt; The MoE architecture routes tokens to specialist sub-networks. That's powerful when different parts of the input need different expertise. But planning is holistic — the time estimate affects the schedule, which affects the daily load, which affects the pressure score. Routing parts of this reasoning to different experts fragments the very thing that needs to stay unified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not 2B?&lt;/strong&gt; I tested it. 2B generates plausible-looking JSON that falls apart under scrutiny — phases with no microtasks, schedules that extend past the deadline, time estimates that don't add up to the total. The structured output reliability drops dramatically below 31B for this task.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Gemma 4 2B — the speed layer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Used for:&lt;/strong&gt; Morning replanning, missed task redistribution, schedule adjustments&lt;/p&gt;

&lt;p&gt;This is the model most users interact with without knowing it. Every morning when you open DueIt, the app checks: what did you complete yesterday? What did you skip? How many days are left until each deadline?&lt;/p&gt;

&lt;p&gt;If you missed microtasks, 2B redistributes them across your remaining days — proportionally, not equally. A day with 3 existing microtasks gets fewer redistributed tasks than an empty day. The pressure and risk meters recalculate. The calendar updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This needs to happen in under 2 seconds.&lt;/strong&gt; A student checking their phone at 8:47 AM before a 9:00 class is not going to wait for a 31B model to reason through redistribution for 15 seconds. They'll close the app and forget about it — which is the exact problem DueIt exists to solve.&lt;/p&gt;

&lt;p&gt;2B handles this because the task is operationally simple. It's not generating a new plan from scratch. It's taking an existing schedule, identifying missed items, and proportionally redistributing them. The reasoning depth required is low. The latency requirement is strict.&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;GEMMA_2B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google/gemma-4-4b-it&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Called on app open — reads yesterday's completions,
# identifies missed microtasks, redistributes across remaining days
# Input is never mutated — copy.deepcopy protects the original
&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepcopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing_schedule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;adjusted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_redistribute_schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedule&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;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why not 31B?&lt;/strong&gt; It would produce marginally better redistribution — maybe slightly smarter about which days get more load. But the latency cost is 5-10x higher, and the quality difference is negligible for a redistribution task. The user doesn't notice a 2% better redistribution. They absolutely notice a 10-second wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not MoE?&lt;/strong&gt; MoE's routing overhead adds latency even when the task only needs one expert. For a single-domain operation (schedule math), the routing is pure overhead. 2B dense is the fastest path to a correct answer.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Gemma 4 26B MoE — the synthesis layer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Used for:&lt;/strong&gt; Excalidraw workflow diagram generation, Mermaid flowchart synthesis, multi-domain analysis&lt;/p&gt;

&lt;p&gt;When DueIt creates an Excalidraw workflow diagram, the model needs to do three things simultaneously:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Understand task structure&lt;/strong&gt; — parse the phases (Research → Design → Implementation → Testing → Deploy) and their dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate valid Mermaid syntax&lt;/strong&gt; — produce a &lt;code&gt;flowchart LR&lt;/code&gt; block with correct node IDs, arrow syntax, and label formatting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ensure visual logic&lt;/strong&gt; — the flowchart should read left-to-right in a sequence that makes sense to a human, not just a valid parse tree&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These three domains — task management, code syntax, and visual layout — are genuinely different areas of expertise. A model that's great at understanding task phases might generate broken Mermaid. A model that writes perfect Mermaid might produce a diagram that's technically valid but visually confusing.&lt;/p&gt;

&lt;p&gt;This is exactly what Mixture-of-Experts was architectured for. The 26B MoE model has 26 billion total parameters organized into specialist sub-networks, but only activates 4 billion on any given token. Different tokens in the output route to different experts — the task-understanding tokens go to one expert, the syntax tokens to another, the layout decisions to a third.&lt;/p&gt;

&lt;p&gt;The result: &lt;strong&gt;edge-speed inference&lt;/strong&gt; (only 4B active parameters per forward pass) with &lt;strong&gt;full-depth knowledge&lt;/strong&gt; (26B total parameters available for routing). You get the speed of a small model with the knowledge of a large one.&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;GEMMA_26B_MOE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google/gemma-4-26b-a4b-it&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Generate Mermaid flowchart from task phases
# The prompt includes phase names, ordering, and dependencies
&lt;/span&gt;&lt;span class="n"&gt;raw_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_call_gemma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GEMMA_26B_MOE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;steps_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# TinyFish takes the Mermaid code, opens Excalidraw,
# imports via the Mermaid importer, starts a collaboration room,
# and returns the shareable URL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why not 31B Dense?&lt;/strong&gt; 31B would produce equally good diagrams, but slower. Since diagram generation runs as a BackgroundTask (the user isn't waiting on it), latency matters less here than for replanning. But 31B is already busy with the planning call — running both in parallel on the same model class creates queuing. MoE handles diagram generation on a separate architecture while 31B focuses on planning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not 2B?&lt;/strong&gt; I tested it. 2B generates Mermaid that looks right but breaks on edge cases — missing arrow connectors, node IDs that collide, labels that overflow. The cross-domain synthesis required (task logic + code syntax + visual layout) exceeds what 2B can hold in context simultaneously.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Model selection summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why this specific architecture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 31B Dense&lt;/td&gt;
&lt;td&gt;All 31B params active per token&lt;/td&gt;
&lt;td&gt;Task planning, time estimation, Notion docs&lt;/td&gt;
&lt;td&gt;Holistic multi-step reasoning for reliable structured JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 2B&lt;/td&gt;
&lt;td&gt;Small dense, fast inference&lt;/td&gt;
&lt;td&gt;Morning replan, schedule redistribution&lt;/td&gt;
&lt;td&gt;Under 2 seconds — speed is the UX requirement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 26B MoE&lt;/td&gt;
&lt;td&gt;4B active / 26B total&lt;/td&gt;
&lt;td&gt;Excalidraw diagrams, Mermaid generation&lt;/td&gt;
&lt;td&gt;Expert routing across task logic, code syntax, and visual layout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  What Gemma 4 unlocked
&lt;/h3&gt;

&lt;p&gt;Before Gemma 4, DueIt used a single &lt;code&gt;gemini-2.5-flash&lt;/code&gt; call for everything — planning, diagrams, formatting, replanning. Same model, same latency, same capability ceiling. There was no model selection strategy because there was no model selection available.&lt;/p&gt;

&lt;p&gt;The migration wasn't just swapping model strings. It was rethinking &lt;strong&gt;which cognitive task each API call actually performs&lt;/strong&gt; and matching it to the architecture that handles that task best.&lt;/p&gt;

&lt;p&gt;Planning got deeper — 31B Dense reasons through constraints that Flash skimmed over. Replanning got faster — 2B responds in the time it takes to glance at your phone. Diagrams got better — MoE's expert routing produces cleaner cross-domain output than a single generalist model juggling three domains at once.&lt;/p&gt;

&lt;p&gt;That's what intentional model selection looks like. Not "I used the biggest model for everything." But: &lt;strong&gt;I used three models, each one earning its place.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built solo with too much coffee, three Gemma brains, and the belief that productivity apps should plan around the real you — not the ideal you.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One task. Fully planned. Automatically adapted.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
  </channel>
</rss>
