<?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: Philip Hern</title>
    <description>The latest articles on DEV Community by Philip Hern (@shrouwoods).</description>
    <link>https://dev.to/shrouwoods</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3858493%2F9a8ff83c-d5c3-493f-9943-b91a2f0a61d8.jpg</url>
      <title>DEV Community: Philip Hern</title>
      <link>https://dev.to/shrouwoods</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shrouwoods"/>
    <language>en</language>
    <item>
      <title>the guardrails i actually use with ai agents</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Mon, 01 Jun 2026 12:39:33 +0000</pubDate>
      <link>https://dev.to/shrouwoods/the-guardrails-i-actually-use-with-ai-agents-3m9c</link>
      <guid>https://dev.to/shrouwoods/the-guardrails-i-actually-use-with-ai-agents-3m9c</guid>
      <description>&lt;p&gt;i argued in &lt;a href="https://philliant.com/posts/20260526-ai-liberty/" rel="noopener noreferrer"&gt;ai liberty&lt;/a&gt; that as models get more capable they get more confident, and that confidence makes them take liberties such as pushing to a primary branch or running a database query nobody asked for. that post named the problem and promised guardrails, but it stopped short of showing them. this is the follow-up with the actual kit.&lt;/p&gt;

&lt;p&gt;the framing i keep coming back to is speed with lane discipline. guardrails are not brakes. they are the lane markers that let me drive fast because i know exactly where the road edges are. the goal is never to slow the agent down on the 95% of work that is safe. the goal is to remove the handful of ways it can do something i cannot undo.&lt;/p&gt;

&lt;h2&gt;
  
  
  quick answer
&lt;/h2&gt;

&lt;p&gt;the guardrails i rely on sit in four layers, namely the agent and editor, the repository, the data, and the human gate. i default the agent to read-only or ask mode, i allowlist the safe commands it runs constantly and deny the destructive ones, i give it database credentials that are read-only and never pointed at production, and i protect the main branch so nothing lands without review. on top of that, a short list of genuinely irreversible actions always requires my explicit approval. none of this slows down day-to-day work, because it only gates the rare move that is hard to reverse.&lt;/p&gt;

&lt;h2&gt;
  
  
  who this is for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;audience: engineers and data folks running ai agents that can touch files, a terminal, or a database&lt;/li&gt;
&lt;li&gt;prerequisites: you already use an agentic editor or cli and have either hit an unintended action or are trying to prevent one&lt;/li&gt;
&lt;li&gt;when to use this guide: when you want the speed of autonomy without leaving open a path to unrecoverable damage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  why this matters
&lt;/h2&gt;

&lt;p&gt;the incidents i described in ai liberty were benign only by luck. an agent committed and pushed to a primary branch on its own, and another hijacked a local script to run queries against a production database. i wrote in &lt;a href="https://philliant.com/posts/20260521-working-with-an-ai-model-mirror/" rel="noopener noreferrer"&gt;working with an ai model mirror&lt;/a&gt; about how a fast model will take real liberties with the command line unless you stop it. the common thread is timing. the destructive action takes a second, and the recovery can take hours or may not be possible at all.&lt;/p&gt;

&lt;p&gt;guardrails change the question from whether i trust the model not to do something into whether the model can do it at all. trust is a hope, and a guardrail is a constraint. the first fails silently and the second fails safe. this is the same lane discipline i wrote about in &lt;a href="https://philliant.com/posts/20260326-the-danger-of-trusting-the-ai-agent/" rel="noopener noreferrer"&gt;the danger of trusting the ai agent&lt;/a&gt;, made concrete in configuration instead of intention.&lt;/p&gt;

&lt;h2&gt;
  
  
  layer 1: the agent and the editor
&lt;/h2&gt;

&lt;p&gt;this is the cheapest place to set limits, and it catches the most.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;default to read-only or ask mode&lt;/strong&gt;: a new chat starts unable to run commands or write files until i grant it, so nothing executes while i am still describing the problem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;allowlist the safe verbs, deny the destructive ones&lt;/strong&gt;: the agent runs my constant, reversible commands without interruption, and the dangerous ones stop for confirmation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;scope the workspace&lt;/strong&gt;: the agent works inside the project root only, and i keep environment files and credential files outside its reach&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reset per chat&lt;/strong&gt;: permissions do not carry over between sessions, so a one-time grant never becomes a standing one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the allowlist is the piece that earns its keep daily. the shape of the rule matters more than the exact syntax, which varies by tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# run these without asking, they are safe and reversible&lt;/span&gt;
&lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git status&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git diff&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git add&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run test&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;
&lt;span class="c1"&gt;# always stop and ask me first, these are hard to undo&lt;/span&gt;
&lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git push&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git reset --hard&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rm -rf&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drop&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the point is not the specific list. it is that the safe path is frictionless and the destructive path is gated by default, not by my memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  layer 2: the repository and git
&lt;/h2&gt;

&lt;p&gt;even with a careful editor config, i assume a command will eventually slip through. the repository is my second net.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;protect the main branch&lt;/strong&gt;: no direct pushes, so an agent cannot land anything on main without a pull request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;require review and passing checks&lt;/strong&gt;: a human review and green continuous integration are required before a merge, which puts a person and a test suite between the agent and the shared history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;let the agent commit, not push&lt;/strong&gt;: the agent can stage and commit on a feature branch, and i am the one who opens the pull request after reading the diff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;keep secret scanning in pre-commit&lt;/strong&gt;: a hook that blocks credentials is a cheap backstop for the moment an agent tries to commit a key it generated or found&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the reason this layer matters is that it does not depend on the agent behaving. branch protection is enforced by the platform, not by the model's good judgment, so it holds even when an editor setting is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  layer 3: data and credentials
&lt;/h2&gt;

&lt;p&gt;this is the layer i am strictest about, because data damage is the kind you cannot always undo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;give the agent a read-only role&lt;/strong&gt;: the credentials in my development loop can select, and nothing else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;never put production write access in reach&lt;/strong&gt;: the agent's connection points at a development or staging database, and production write credentials simply do not exist in that environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;keep secrets out of the repo and the prompt&lt;/strong&gt;: credentials live in environment variables or a secret manager, never pasted into a chat where they end up in logs and history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;prefer least privilege everywhere&lt;/strong&gt;: a separate, narrow role per use beats one powerful role shared across everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;a read-only role takes a few minutes to set up and removes an entire category of accident:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="n"&gt;ai_agent_readonly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;grant&lt;/span&gt; &lt;span class="k"&gt;usage&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;warehouse&lt;/span&gt; &lt;span class="n"&gt;dev_wh&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="n"&gt;ai_agent_readonly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;grant&lt;/span&gt; &lt;span class="k"&gt;usage&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt; &lt;span class="n"&gt;analytics_dev&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="n"&gt;ai_agent_readonly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;grant&lt;/span&gt; &lt;span class="k"&gt;usage&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt; &lt;span class="n"&gt;schemas&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt; &lt;span class="n"&gt;analytics_dev&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="n"&gt;ai_agent_readonly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;grant&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt; &lt;span class="n"&gt;analytics_dev&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="n"&gt;ai_agent_readonly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- intentionally no insert, update, delete, or grants, and no production access&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with a role like this, the worst case for a runaway query is a slow read, not a deleted table.&lt;/p&gt;

&lt;h2&gt;
  
  
  layer 4: the human-in-the-loop gate
&lt;/h2&gt;

&lt;p&gt;a few actions are dangerous enough that i never automate them, no matter how capable the model is. these always stop for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema migrations and destructive ddl such as drop and truncate&lt;/li&gt;
&lt;li&gt;deletes, updates, or anything that mutates production data&lt;/li&gt;
&lt;li&gt;deploys, releases, and infrastructure changes&lt;/li&gt;
&lt;li&gt;force-pushes and history rewrites&lt;/li&gt;
&lt;li&gt;anything that spends money or sends a message to real users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;for this short list, the rule is to read the actual diff and command output, not the summary the agent writes in chat. i have been burned by trusting the narrative before, when a clean git tree hid work i could not account for, and reading the diff is what would have caught it. the same racing phrase fits here, slow is smooth, smooth is fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  keeping the speed
&lt;/h2&gt;

&lt;p&gt;it would be easy to read all of this as a wall of friction, but in practice it is the opposite. almost every guardrail above is one-time setup that pays back forever. the editor config, the branch rules, the read-only role, and the short gate list are written once and then they just hold. they do not cry wolf, because they only interrupt me for the rare action that is actually hard to undo. the constant, reversible work that fills most of my day never hits a single one of them.&lt;/p&gt;

&lt;p&gt;that is what speed with lane discipline means to me. i am not asking the agent to be slower or less autonomous. i am drawing the lanes so that its speed runs in a direction i can always recover from. i wrote a related piece on encoding this kind of intent directly into the tools in &lt;a href="https://philliant.com/posts/20260314-how-to-use-ai-to-create-ai-rules-skills-and-commands/" rel="noopener noreferrer"&gt;how to use ai to create ai rules, skills, and commands&lt;/a&gt;, and the guardrails here are the safety-critical version of that same idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  faq
&lt;/h2&gt;

&lt;h3&gt;
  
  
  do these guardrails slow the agent down?
&lt;/h3&gt;

&lt;p&gt;mostly no. the safe, reversible commands are allowlisted and run without interruption, and the only things that stop are the handful of actions that are genuinely hard to reverse. the friction is concentrated exactly where i want it and absent everywhere else.&lt;/p&gt;

&lt;h3&gt;
  
  
  if i could only set up one guardrail, which should it be?
&lt;/h3&gt;

&lt;p&gt;read-only database credentials that cannot reach production. command and branch mistakes are usually recoverable, but a destructive write against real data often is not, so removing write access removes the worst outcome first.&lt;/p&gt;

&lt;h3&gt;
  
  
  what about fully autonomous agents that run without me watching?
&lt;/h3&gt;

&lt;p&gt;the gate scales with blast radius. the more autonomy i hand over, the tighter the rails need to be, which usually means a sandboxed environment, no production credentials at all, and an irreversible-action list that is enforced by the platform rather than by my attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" rel="noopener noreferrer"&gt;principle of least privilege&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches" rel="noopener noreferrer"&gt;github branch protection rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Human-in-the-loop" rel="noopener noreferrer"&gt;human-in-the-loop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260526-ai-liberty/" rel="noopener noreferrer"&gt;ai liberty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260326-the-danger-of-trusting-the-ai-agent/" rel="noopener noreferrer"&gt;the danger of trusting the ai agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260521-working-with-an-ai-model-mirror/" rel="noopener noreferrer"&gt;working with an ai model mirror&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260314-how-to-use-ai-to-create-ai-rules-skills-and-commands/" rel="noopener noreferrer"&gt;how to use ai to create ai rules, skills, and commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/ai/" rel="noopener noreferrer"&gt;ai series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>guardrails</category>
      <category>security</category>
      <category>workflow</category>
    </item>
    <item>
      <title>dynamic tables, where have you been all my life?</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Fri, 29 May 2026 13:39:59 +0000</pubDate>
      <link>https://dev.to/shrouwoods/dynamic-tables-where-have-you-been-all-my-life-32ij</link>
      <guid>https://dev.to/shrouwoods/dynamic-tables-where-have-you-been-all-my-life-32ij</guid>
      <description>&lt;h2&gt;
  
  
  quick answer
&lt;/h2&gt;

&lt;p&gt;snowflake dynamic tables are a declarative, automated data engineering solution that materialize query results without manual pipeline orchestration. unlike standard materialized views, which are highly restricted and do not support joins, dynamic tables manage the dependency graph, calculate target lag, and automatically refresh multi-table query joins behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  who this is for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;audience: data engineers and analytics engineers managing data platforms on snowflake&lt;/li&gt;
&lt;li&gt;prerequisites: familiarity with standard database tables, views, and data warehousing concepts&lt;/li&gt;
&lt;li&gt;when to use this guide: when you are designing transactional-to-analytical data flows and want to avoid complex, manual scheduler configurations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  why this matters
&lt;/h2&gt;

&lt;p&gt;managing historical records and updating analytical reporting tables typically requires a significant amount of task orchestration. in the past, i spent hours configuring and debugging sequenced tasks, managing change data capture streams, and writing custom merge statements. this manual approach is fragile, expensive to maintain, and prone to scheduling failures.&lt;/p&gt;

&lt;p&gt;when you are looking for an automated caching mechanism, the natural instinct is to search for materialized views. however, snowflake materialized views are not supported. this is where dynamic tables step in to solve the exact problem i had been trying to solve for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  step-by-step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) define the starting point
&lt;/h3&gt;

&lt;p&gt;before i implemented dynamic tables, our data platform relied on a series of scheduled tasks. we ingested raw transactional records from our upstream application databases into the raw data layer. to populate our reporting tables, we had to coordinate several sequential operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;standard views queried the raw tables&lt;/li&gt;
&lt;li&gt;scheduled tasks ran custom merge statements to update downstream tables on a fixed timeline&lt;/li&gt;
&lt;li&gt;we had to build and maintain manual parent-child relationships between tasks to ensure proper execution order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this system was rigid. if an upstream task failed, downstream tasks either ran on stale data or did not execute at all. calculating dependencies and scheduling intervals manually was a continuous headache.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) apply the change
&lt;/h3&gt;

&lt;p&gt;i recently replaced this complicated orchestration with snowflake dynamic tables. instead of writing imperative tasks and merge scripts, i defined the destination schema declaratively.&lt;/p&gt;

&lt;p&gt;a dynamic table is defined by a standard sql query. snowflake automatically manages the execution, determining what has changed and applying those changes to the target table on our behalf. for example, the definition of a basic dynamic table looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;dynamic&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;my_reporting_dynamic_table&lt;/span&gt;
  &lt;span class="n"&gt;target_lag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1 hour'&lt;/span&gt;
  &lt;span class="n"&gt;warehouse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_standard_warehouse&lt;/span&gt;
  &lt;span class="k"&gt;as&lt;/span&gt;
    &lt;span class="k"&gt;select&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;id&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="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classification&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;created_at&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'raw_person_v'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'raw_classification_v'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
      &lt;span class="k"&gt;on&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;classification_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&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 components of this declarative approach are target lag and dynamic boundaries.&lt;/p&gt;

&lt;h4&gt;
  
  
  the role of target lag
&lt;/h4&gt;

&lt;p&gt;target lag defines how fresh you need the data to be. instead of telling snowflake &lt;em&gt;when&lt;/em&gt; to run (such as a specific cron schedule), you tell snowflake &lt;em&gt;what&lt;/em&gt; maximum data latency is acceptable. if you set the target lag to &lt;code&gt;1 hour&lt;/code&gt;, snowflake handles the scheduling automatically to ensure the data in the dynamic table is no more than one hour behind the source tables. if the source data does not change, snowflake does not waste compute resources refreshing the table.&lt;/p&gt;

&lt;h4&gt;
  
  
  automatic dependency management
&lt;/h4&gt;

&lt;p&gt;snowflake dynamic tables automatically handle dependencies. when you define multiple dynamic tables that reference each other, snowflake builds a directed acyclic graph (dag) of the relationships. snowflake is aware of these dynamic boundaries, so it orchestrates the refresh order across all connected tables to ensure data consistency. you do not need to configure task chains or parent-child dependencies manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) validate the result
&lt;/h3&gt;

&lt;p&gt;once i deployed the dynamic tables, we validated the results by monitoring the snowflake dynamic table account history and query execution logs. i observed several immediate improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;declarative simplicity&lt;/strong&gt;: our pipeline code shrunk significantly because we deleted numerous task definitions, task schedules, and custom merge scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;automatic healing&lt;/strong&gt;: when upstream data ingestion paused and resumed, the dynamic tables automatically recalculated and caught up to the target freshness without human intervention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;optimized resource utilization&lt;/strong&gt;: snowflake only consumed compute resources during the active refresh cycles, reducing unnecessary warehouse uptime compared to our old, rigid scheduling intervals&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  better late than never
&lt;/h2&gt;

&lt;p&gt;discovering this solution felt like finding the missing piece of a puzzle. it is an incredible upgrade to our development workflow, regardless of when it happened.&lt;/p&gt;

&lt;p&gt;still, i cannot help but think about how much time and effort would have been saved if i had discovered this earlier. for years, i searched for "materialized views in snowflake" because that was the concept i knew from other relational databases. because snowflake does not have the concept of materialized views, i assumed snowflake did not have a declarative caching mechanism that supported joins, which led me down the path of manual task management.&lt;/p&gt;

&lt;p&gt;if you are currently in that position, searching for a way to materialize complex joins without writing brittle scheduled tasks, this is the sign you need:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;looking for materialized views in snowflake? try dynamic tables.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;this realization would have set me on the right path years ago. hopefully, sharing this experience will help another engineer bypass the struggle of manual task orchestration and jump straight to declarative, automated pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.snowflake.com/en/user-guide/dynamic-tables-about" rel="noopener noreferrer"&gt;snowflake dynamic tables overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table" rel="noopener noreferrer"&gt;snowflake dynamic table configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.snowflake.com/en/user-guide/dynamic-tables-comparison" rel="noopener noreferrer"&gt;snowflake materialized views vs dynamic tables&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260328-the-difference-between-snowflake-and-the-other-databases/" rel="noopener noreferrer"&gt;the difference between snowflake and the other databases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260408-dbt-snapshots/" rel="noopener noreferrer"&gt;dbt snapshots: moving from merges to native history&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>snowflake</category>
      <category>dynamictables</category>
      <category>dataengineering</category>
      <category>database</category>
    </item>
    <item>
      <title>ai liberty</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Tue, 26 May 2026 23:44:20 +0000</pubDate>
      <link>https://dev.to/shrouwoods/ai-liberty-488a</link>
      <guid>https://dev.to/shrouwoods/ai-liberty-488a</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;as artificial intelligence models grow smarter and more capable, they do not just get better at answering questions. they also become more confident in their own abilities, which makes them increasingly likely to take unsolicited liberties to solve problems. we need to implement robust, systemic guardrails before these fleeting, automated actions lead to unrecoverable or catastrophic consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;i have recently watched this shift play out in my own daily development workflow. in one instance, an autonomous agent took the liberty to commit and push changes directly to our primary branch without my explicit instruction. in another, more concerning instance, an agent hijacked a local script to inject SQL queries into a production database to solve a debugging blocker. while both incidents were benign and quickly resolved, they reflect a broader pattern that is emerging across the industry, including recent reports of a chatbot deleting an entire production database in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;p&gt;this trend is driven by an interesting paradox. as models improve, we train them to be proactive, creative, and self-sufficient. yet, this exact training makes them less likely to ask for permission when they encounter an obstacle.&lt;/p&gt;

&lt;h3&gt;
  
  
  the illusion of competence
&lt;/h3&gt;

&lt;p&gt;as an AI model gains capability, its confidence increases. it stops treating instructions as strict boundaries and begins treating them as general suggestions. if we ask a model to fix a bug, and that model has access to the command line, it may decide that the most efficient way to help is to run a script, modify an environment file, or execute a database query.&lt;/p&gt;

&lt;p&gt;from the perspective of the model, this is simply efficient problem-solving. it does not have a concept of "off-limits" territory unless we explicitly define and enforce those limits. the smarter the model becomes, the more confident it is that its autonomous decisions are correct, making it more likely to bypass the human in the loop entirely. in my previous post on &lt;a href="https://philliant.com/posts/20260521-working-with-an-ai-model-mirror/" rel="noopener noreferrer"&gt;working with an ai model mirror&lt;/a&gt;, i wrote about how fast models will take significant liberties with the command line on their own unless we specifically restrict them.&lt;/p&gt;

&lt;h3&gt;
  
  
  fleeting seconds and catastrophic impact
&lt;/h3&gt;

&lt;p&gt;these automated actions happen in literal seconds. a model can execute a terminal command, push a commit, or delete a table faster than a human can read the log. while the execution is fleeting, the long-term, potentially catastrophic consequences are very real.&lt;/p&gt;

&lt;p&gt;we cannot afford to treat these events as minor quirks. thankfully, my own experiences were benign, but they are warnings. if we do not build systemic guardrails into our local environments, our development tools, and our deployment pipelines, it is only a matter of time before an autonomous agent makes an unrecoverable change. delegating high-autonomy changes can lead to unowned complexity, as discussed in &lt;a href="https://philliant.com/posts/20260326-the-danger-of-trusting-the-ai-agent/" rel="noopener noreferrer"&gt;the danger of trusting the ai agent&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;some developers argue that putting tight constraints on AI agents defeats the purpose of using them. they believe that if we force an agent to ask for permission before every action, we lose the speed and autonomy that make these tools valuable. we do not want to cry wolf or slow ourselves down unnecessarily.&lt;/p&gt;

&lt;p&gt;however, there is a fundamental difference between healthy autonomy and unguided liberty. as we learned from spiderman, "with great power comes great responsibility". giving an agent power without defining its responsibility is not a speed booster, it is a liability. we can maintain development speed while keeping strict lane discipline, ensuring that high-risk actions always require human verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;the solution is not to stop using advanced models, but to be much more intentional about the environments in which they operate. we must configure our editor settings, local database permissions, and deployment pipelines to enforce hard limits. speed is excellent, but verification is mandatory. i am continuing to leverage these models to accelerate my work, but i am building robust guardrails to ensure that they remain helpful assistants rather than autonomous actors.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Software_agent" rel="noopener noreferrer"&gt;the risk of autonomous agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Database_security" rel="noopener noreferrer"&gt;database security and local development guardrails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260326-the-danger-of-trusting-the-ai-agent/" rel="noopener noreferrer"&gt;the danger of trusting the ai agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260521-working-with-an-ai-model-mirror/" rel="noopener noreferrer"&gt;working with an ai model mirror&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/ai/" rel="noopener noreferrer"&gt;ai series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>guardrails</category>
      <category>security</category>
      <category>workflow</category>
    </item>
    <item>
      <title>working with an ai model mirror</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Thu, 21 May 2026 17:55:54 +0000</pubDate>
      <link>https://dev.to/shrouwoods/working-with-an-ai-model-mirror-4e0j</link>
      <guid>https://dev.to/shrouwoods/working-with-an-ai-model-mirror-4e0j</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;working with a fast artificial intelligence model can feel like looking in a mirror. when i recently used gemini 3.5 flash for codebase maintenance and deployment improvements, i found a model that matches my own tendency to work fast, think every idea is brilliant, and run with things before looking for a landing. it is an entertaining but highly informative look at how speed and confidence can run ahead of verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;after getting past a major release, i finally had some room to breathe. instead of jumping straight into new feature development, i used the pause to focus on some needed repository maintenance and build pipeline enhancements. since these tasks are mostly structural and procedural, i routed them to gemini 3.5 flash to see how the model handles quick execution across configuration files and scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;p&gt;the speed of gemini 3.5 flash is impressive, but its confidence is even more striking. working with it highlighted two specific behaviors that require careful handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  the self-complementary loop
&lt;/h3&gt;

&lt;p&gt;this model loves its own output. during our sessions, i constantly see it complementing itself with phrases such as "this is a great idea!". flash is highly self-complementary, which means it will run with any idea it generates and do so with absolute confidence.&lt;/p&gt;

&lt;p&gt;this feels hilariously familiar. i have a tendency to work at a rapid pace and assume that whatever solution i come up with is brilliant. when we work together, it is like having two people in the room who both want to leap before looking for a landing. if i am not careful, we both end up running in the wrong direction very quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  command line liberties
&lt;/h3&gt;

&lt;p&gt;another critical behavior to watch is how the model handles terminal operations. compared to other models i have worked with, flash will take significant liberties with your command line. unless i specifically instruct it to remain read-only or ask for permission in every new chat, it will start running scripts and executing commands on its own.&lt;/p&gt;

&lt;p&gt;to prevent unwanted changes, i have to build constraints into the workspace rules or editor settings. without those guardrails, a fast model with terminal access is a recipe for rapid, unverified execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  gemini 3.1 pro on a timer
&lt;/h3&gt;

&lt;p&gt;in practice, working with gemini 3.5 flash feels very much like asking gemini 3.1 pro to answer a question on a timer. you get the same broad context capability, but everything is accelerated. the trade-off for that speed is that you must become the anchor of caution.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;using a fast model is excellent for clearing a backlog of maintenance tasks, but it shifts the burden of validation entirely onto the human developer. when the assistant is a mirror of your own fastest, most optimistic impulses, you have to be the one who slows down and double-checks the work. i am keeping flash in my tool rotation, but i am keeping a much closer eye on its handiwork.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/models" rel="noopener noreferrer"&gt;gemini model documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Software_quality" rel="noopener noreferrer"&gt;developer speed vs decision quality&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260320-deep-dive-ai-models-i-use/" rel="noopener noreferrer"&gt;deep dive: the ai models i use&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260326-the-danger-of-trusting-the-ai-agent/" rel="noopener noreferrer"&gt;the danger of trusting the ai agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/ai/" rel="noopener noreferrer"&gt;ai series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>gemini35flash</category>
      <category>workflow</category>
      <category>developerproductivity</category>
    </item>
    <item>
      <title>again, adaptability for the win</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Mon, 18 May 2026 13:20:55 +0000</pubDate>
      <link>https://dev.to/shrouwoods/again-adaptability-for-the-win-1lm7</link>
      <guid>https://dev.to/shrouwoods/again-adaptability-for-the-win-1lm7</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;adaptability almost always wins. when i hold on to a plan that has stopped working, the cost keeps climbing and the result keeps getting worse. when i let go of the plan and respond to what is actually in front of me, the path usually shortens and the outcome usually improves. that has been true often enough that i no longer think of adaptability as a soft skill. i wrote about it before in &lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt;, and a recent project pushed me right back into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;a few weeks ago i wrote &lt;a href="https://philliant.com/posts/20260416-stick-with-it/" rel="noopener noreferrer"&gt;stick with it&lt;/a&gt;, about a heavy lift that had grown bigger than i expected, and then &lt;a href="https://philliant.com/posts/20260422-back-at-it/" rel="noopener noreferrer"&gt;back at it&lt;/a&gt;, a small checkpoint where i thought the worst part was behind me. it was not. as i kept pressing on, the same friction kept returning, and it became clear that the method i had committed to was not the right one. the change i actually needed to make was arguably larger than the heavy lift i had already started, and that realization is not a fun one to sit with after weeks of effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;p&gt;a friend of mine once shared a saying translated from russian, "if you are going down the wrong path and you turn around, it means you are walking on the right path". that line stuck with me, and it was exactly the frame i needed. the question stopped being "how do i finish the path i started" and became "is this even the right path". the answer was no, and the only move was to turn around regardless of how much time and effort i had already poured in.&lt;/p&gt;

&lt;h3&gt;
  
  
  the sunk cost trap
&lt;/h3&gt;

&lt;p&gt;the hardest part was admitting that the invested time was not a reason to keep going. it felt like turning around would erase the work. it did not. the lessons i picked up from the heavy lift carried forward, even though the specific approach did not. forcing it would have wasted more time on top of the time already spent, and would have produced a worse outcome at the end. that is the sunk cost trap in plain language. the past spend is not a credit toward the wrong direction, and treating it like one only deepens the loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  what adapting actually looked like
&lt;/h3&gt;

&lt;p&gt;once i let go of what i wanted to be in front of me and looked at what was actually in front of me, the next steps were obvious in a way they had not been for weeks (and literal years if i compare this to the original logic). the friction dropped, the solution showed up quickly, and it is meaningfully better than anything i had been trying to force. that part still surprises me a little. i kept expecting the new direction to be a compromise on the original vision. it turned out to be the upgrade.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;adapting is not the same as quitting, and i want to be careful not to confuse the two. some hard work just is hard, and the right call is to stay with it, the way i wrote about in stick with it. the check i run is whether the friction is teaching me about the path or about the problem. friction that teaches me about the path means the method needs to change. friction that teaches me about the problem means i need to keep working the method i have. they look similar in the moment, but the diagnosis is different, and so is the response.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;i used to treat adaptability as a label for being flexible. now i think of it as the discipline of letting reality update the plan faster than ego protects it. that is what made the difference here, and it is what made the final solution better than the one i was forcing. if i had to compress the lesson into a sentence, it is this. when the path is wrong, turning around is forward progress, and the willingness to do that is one of the most valuable skills i have.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Sunk_cost" rel="noopener noreferrer"&gt;sunk cost fallacy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Confirmation_bias" rel="noopener noreferrer"&gt;confirmation bias&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260416-stick-with-it/" rel="noopener noreferrer"&gt;stick with it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260422-back-at-it/" rel="noopener noreferrer"&gt;back at it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/commentary/" rel="noopener noreferrer"&gt;commentary series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>adaptability</category>
      <category>decisionmaking</category>
      <category>sunkcost</category>
      <category>change</category>
    </item>
    <item>
      <title>working together or alone</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Sat, 09 May 2026 14:11:26 +0000</pubDate>
      <link>https://dev.to/shrouwoods/working-together-or-alone-3kn3</link>
      <guid>https://dev.to/shrouwoods/working-together-or-alone-3kn3</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;going alone is faster in the short run, but working with people is what produces durable, well-tested decisions. the hard part is admitting that the cost of collaboration is the price of better outcomes, not friction to be removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;i have been on both ends of this. solo sprints where i make every call myself and ship fast. heavily collaborative weeks where every idea passes through three people before it leaves my hands. both modes have a place, but the second one is what most people undervalue, and that is the side i want to push on.&lt;/p&gt;

&lt;p&gt;a lot of the value i get from teammates is invisible if you only look at the final artifact. the meeting that did not happen, the bug that did not ship, the email that did not get sent in anger, the timeline that turned out to be honest. those non-events are the real return on collaboration, and they almost never show up in a status update.&lt;/p&gt;

&lt;h2&gt;
  
  
  working together
&lt;/h2&gt;

&lt;p&gt;working with colleagues changes the shape of your output. the project might take a little longer, but the result is more robust, easier to operate, and far less stressful to own. each of the items below is a check that i do not have when i am alone, and each one catches something the next one would not.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;identifying blind spots&lt;/strong&gt;: colleagues see the assumptions you stopped checking, the corners of the problem you skipped, and the patterns you keep repeating without noticing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;finding holes in ideas&lt;/strong&gt;: a fresh set of eyes pressure-tests the design before customers do, surfacing failure modes while they are still cheap to fix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;help with communication&lt;/strong&gt;: having someone read your draft, sit through your demo, or rehearse the conversation with you turns rough thinking into something a stranger can actually follow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;regulating emotions and reactions&lt;/strong&gt;: a steady colleague absorbs some of the heat in the moment, slows your knee-jerk replies, and helps you respond to a hard situation instead of react to it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;timeline planning&lt;/strong&gt;: two minds estimate better than one, because each person brings their own catalog of past slippage, hidden dependencies, and "i forgot we have to do that too" risks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fielding questions from other colleagues and users&lt;/strong&gt;: a small team can absorb a steady stream of questions in parallel, while a single owner ends up either ignoring some or context-switching all day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reducing stress&lt;/strong&gt;: shared ownership means you are not the only person watching the alert, the deadline, or the customer message late at night, and even just knowing someone else is in the loop lowers the load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;providing backup&lt;/strong&gt;: vacation, sick days, family emergencies, and surprise outages all hurt less when more than one person can keep things moving&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the throughline across all of these is the same. each colleague becomes another lens, another estimator, another shoulder, and another set of hands. the cost is coordination time. the return is that the work survives contact with reality. this is also why i keep coming back to the idea that knowledge has to flow inside a team, which i wrote about more directly in &lt;a href="https://philliant.com/posts/20260402-sharing-is-caring/" rel="noopener noreferrer"&gt;sharing is caring&lt;/a&gt;. collaboration only works when people are willing to share what they know, openly and without keeping score.&lt;/p&gt;

&lt;h3&gt;
  
  
  what these checks actually catch
&lt;/h3&gt;

&lt;p&gt;it is worth saying out loud what the checks above produce, because the upside can feel abstract until you list it. blind-spot reviews catch missing requirements before code is written. design pressure-tests catch failures before launch. communication rehearsals catch misunderstandings before they happen. emotional regulation catches messages you would have regretted in the morning. shared timeline planning catches the date that was never realistic in the first place. shared on-call catches the alert that would have woken you up alone. shared knowledge catches the bus factor that would have ground the team to a halt the moment one person took a week off.&lt;/p&gt;

&lt;p&gt;put simply, the value of working together is that it converts a long list of "what could go wrong" into a much shorter list of "what actually went wrong", and the difference is paid for by the people sitting around the table with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  working alone
&lt;/h2&gt;

&lt;p&gt;working alone has real advantages, and pretending otherwise just makes the trade-off invisible. there are days where solo work is exactly the right tool, and i do not want to lose that entirely. the upsides are real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;speed of execution&lt;/strong&gt;: you can move from idea to keystroke without waiting on anyone's calendar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;speed of decision&lt;/strong&gt;: small choices, the ones that would normally chew up a meeting, get resolved in seconds because the only stakeholder is you&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;faster time to release&lt;/strong&gt;: with no review queue, no design discussion, and no hand-off, you can ship in hours instead of days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;those are real benefits, and i would not want a workflow that lost them entirely. quick prototypes, urgent fires, and small exploratory spikes all reward solo speed. the trouble is what you are quietly trading for that speed, because the things you give up are exactly the items in the previous section.&lt;/p&gt;

&lt;p&gt;every solo sprint is also a sprint without a pressure test, without an emotional buffer, without a second estimator, and without a backup. the output may go out fast, but it goes out untested by anyone other than you, and the cost shows up later. it shows up as the bug a teammate would have spotted, the customer signal you misread, the timeline that was confidently wrong, the email that should never have been sent, or the small fire that grew into a bigger one because nobody else was watching.&lt;/p&gt;

&lt;p&gt;solo work also hides one structural risk that is easy to ignore in the moment. when you are the only person who has touched the work, you are also the only person who knows how it functions. that feels like job security in the short run, but it is actually a single point of failure for the team. the next person who has to maintain, change, or escalate that work pays the cost, and the work itself becomes less changeable over time. the savings on review on day one quietly turn into interest on every change after.&lt;/p&gt;

&lt;h3&gt;
  
  
  where solo work still earns its place
&lt;/h3&gt;

&lt;p&gt;i do not want to overcorrect. solo work is the right call when the task is small, clearly bounded, reversible, low-stakes for other people, or strictly time-critical. exploratory spikes, urgent on-call fixes that have to land in minutes, and personal experiments are all good candidates. the test i use is simple. if i ship this alone and it turns out wrong, who pays the cost? if the answer is mostly me, solo is fine. if the answer is the team, the customer, or some future maintainer, i want a second pair of eyes on it before it goes out.&lt;/p&gt;

&lt;h2&gt;
  
  
  weighing the trade-off
&lt;/h2&gt;

&lt;p&gt;put the two lists next to each other and the picture is honest. solo work optimizes for speed of one person. collaboration optimizes for quality, durability, and the well-being of the team. neither is universally correct, and i am not saying that either is the default.&lt;/p&gt;

&lt;p&gt;most people i have worked with default to whichever mode their personality prefers. fast movers default to solo and underweight the cost of being wrong. careful planners default to collaboration and underweight the cost of slow shipping. the better habit, in both cases, is to read the work first and choose deliberately. solo when the cost of being wrong is small. collaborative when the cost of being wrong is borne by other people. but this also, again, points out the value of collaboration because each personality type compliments each other, yielding the best overall result in the long-term.&lt;/p&gt;

&lt;p&gt;this is one of those calls that benefits from being made consciously, which is the same kind of branch-aware thinking i wrote about in &lt;a href="https://philliant.com/posts/20260424-logic/" rel="noopener noreferrer"&gt;logic&lt;/a&gt;. naming the conditions up front, "is this reversible", "who pays if it is wrong", "do i have a clean place to bring it back if needed", makes the choice between solo and collaborative much less personality-driven and much more situational.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;it is fair to push back on this. bad collaboration is worse than careful solo work. meetings without decisions, design reviews that turn into ego contests, committees that flatten everyone's good ideas into the lowest common denominator, and "let us all weigh in" as a way of avoiding ownership are real failures, and pretending otherwise is naive. the argument for working together only holds when the collaboration itself is healthy, with clear ownership, real candor, and a shared incentive to ship.&lt;/p&gt;

&lt;p&gt;there is also a real risk that "let us collaborate" becomes a way to spread responsibility for choices people do not want to defend. that is a different problem than the one this post is making the case for, and it is worth naming. healthy collaboration sharpens decisions. unhealthy collaboration dissolves them, and the right response to unhealthy collaboration is to fix the team norms, not to retreat into solo work and call it focus. the goal is not "do everything together" or "do everything alone". the goal is to use the right mode for the right work, and to invest in the team norms that make collaboration actually pay back when it is the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;if i had to pick one rule, it is this. build the kind of working relationships where it costs you less to be questioned than to be wrong. that is the version of teamwork that actually pays back, and it is the version that lets you go solo with confidence when the moment calls for it, because you know you are not gambling alone every time you do.&lt;/p&gt;

&lt;p&gt;solo speed is still on the menu, and i still reach for it when the task is small enough that the cost of being wrong sits squarely with me. but the durable wins, the ones i look back on a year later and feel good about, almost always have someone else's fingerprints on them. that is not a coincidence. that is the trade working as intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Psychological_safety" rel="noopener noreferrer"&gt;psychological safety&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Bus_factor" rel="noopener noreferrer"&gt;bus factor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Groupthink" rel="noopener noreferrer"&gt;groupthink&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260402-sharing-is-caring/" rel="noopener noreferrer"&gt;sharing is caring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260424-logic/" rel="noopener noreferrer"&gt;logic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/commentary/" rel="noopener noreferrer"&gt;commentary series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>collaboration</category>
      <category>teamwork</category>
      <category>communication</category>
      <category>decisionmaking</category>
    </item>
    <item>
      <title>keep your snapshots simple</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Sat, 09 May 2026 14:11:26 +0000</pubDate>
      <link>https://dev.to/shrouwoods/keep-your-snapshots-simple-2off</link>
      <guid>https://dev.to/shrouwoods/keep-your-snapshots-simple-2off</guid>
      <description>&lt;p&gt;snapshots are one of those features that feel like a free win the first time you reach for them. dbt handles the merge, the warehouse handles the storage, and suddenly you have a tidy history of every change a source row has ever gone through. then you ship a few of them, leave them running for a while, and discover that the maintenance, the backups, and the recovery story are quietly the most expensive parts of your pipeline.&lt;/p&gt;

&lt;p&gt;i still use snapshots, but the rule i hold myself to is short. use them only when i absolutely have to, never on top of another snapshot, and never as a substitute for logic i could recompute deterministically. the rest of this post explains why.&lt;/p&gt;

&lt;h2&gt;
  
  
  quick answer
&lt;/h2&gt;

&lt;p&gt;dbt snapshots implement the type 2 slowly changing dimension pattern. they track every change to a source row by closing the previous version and inserting a new one with &lt;code&gt;dbt_valid_from&lt;/code&gt; and &lt;code&gt;dbt_valid_to&lt;/code&gt; columns that define when each version was current. they are powerful when the source overwrites history and you genuinely need point-in-time answers, but they are expensive to operate, brittle under source-schema or grain changes, and impossible to fully rebuild from scratch once you have lost the original change events. the practical rule is to use the smallest number of snapshots you can get away with, never let one snapshot read from another, and recompute everything else deterministically.&lt;/p&gt;

&lt;h2&gt;
  
  
  who this is for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;analytics engineers and data engineers who already use dbt and are deciding when to reach for snapshots&lt;/li&gt;
&lt;li&gt;teams that have inherited a snapshot-heavy project and are trying to reduce the operational tax&lt;/li&gt;
&lt;li&gt;anyone who has felt the pain of a corrupted or backfilled snapshot and wants a more conservative pattern going forward&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  why this matters
&lt;/h2&gt;

&lt;p&gt;snapshots are different from every other model in your dbt project. a regular model is a pure function of its inputs, and you can drop and rebuild it any time. a snapshot is &lt;strong&gt;stateful&lt;/strong&gt;, meaning its output depends on every prior run, the order those runs happened in, and the source values that existed at the moment each run executed. once that state is wrong, no amount of &lt;code&gt;dbt run --full-refresh&lt;/code&gt; will fix it for you, because the historical events that produced the original sequence of rows are gone.&lt;/p&gt;

&lt;p&gt;that is the trade you are making when you adopt a snapshot. you are giving up reproducibility in exchange for history capture. that trade is worth it for some sources, but it is far less common than people assume. most of the time, what you actually need is either a deterministic transformation, a freeze calendar, or a properly modeled effective satellite. snapshots should be a small, deliberate slice of your warehouse, not a default.&lt;/p&gt;

&lt;h2&gt;
  
  
  type 2 scd: a quick refresher
&lt;/h2&gt;

&lt;p&gt;before going further, here is a short reminder about what type 2 actually means. the dimension community talks about slowly changing dimensions in numbered types because the trade-offs are very different across them. the most common ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;type 0&lt;/strong&gt;: never change the value, even if the source does&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;type 1&lt;/strong&gt;: overwrite the value in place, no history kept&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;type 2&lt;/strong&gt;: keep every version of the row across time, with start and end timestamps that mark when each version was current&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;type 3&lt;/strong&gt;: keep a small number of prior values in additional columns on the same row (for example &lt;code&gt;previous_status&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;type 2 is the only one that gives you a complete record of how an attribute evolved. the trade-off is that the row count grows every time something changes, and every consumer has to know how to filter the table to get the version they want.&lt;/p&gt;

&lt;h3&gt;
  
  
  what type 2 looks like in a row
&lt;/h3&gt;

&lt;p&gt;imagine a customer table where the &lt;code&gt;status&lt;/code&gt; column changed twice. with type 2 history, the same &lt;code&gt;customer_id&lt;/code&gt; shows up multiple times, with non-overlapping validity intervals.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;status&lt;/th&gt;
&lt;th&gt;valid_from&lt;/th&gt;
&lt;th&gt;valid_to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;prospect&lt;/td&gt;
&lt;td&gt;2026-01-01 00:00:00&lt;/td&gt;
&lt;td&gt;2026-02-15 09:30:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;active&lt;/td&gt;
&lt;td&gt;2026-02-15 09:30:00&lt;/td&gt;
&lt;td&gt;2026-04-22 14:10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;churned&lt;/td&gt;
&lt;td&gt;2026-04-22 14:10:00&lt;/td&gt;
&lt;td&gt;9999-12-31 23:59:59&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;a few things in that table do most of the work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;business key&lt;/strong&gt; (&lt;code&gt;customer_id&lt;/code&gt;) is no longer unique on its own, so the grain is now &lt;code&gt;customer_id&lt;/code&gt; plus the validity interval&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;valid_from&lt;/code&gt; is &lt;strong&gt;inclusive&lt;/strong&gt; and &lt;code&gt;valid_to&lt;/code&gt; is &lt;strong&gt;exclusive&lt;/strong&gt; in most type 2 conventions, so the intervals tile cleanly without overlap&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;current row&lt;/strong&gt; uses a sentinel like &lt;code&gt;9999-12-31 23:59:59&lt;/code&gt; instead of &lt;code&gt;null&lt;/code&gt;, which makes downstream filters cleaner&lt;/li&gt;
&lt;li&gt;to answer "what was the status at time &lt;code&gt;t&lt;/code&gt;", you filter where &lt;code&gt;valid_from &amp;lt;= t and valid_to &amp;gt; t&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;to answer "what is the status now", you filter where &lt;code&gt;valid_to = '9999-12-31 23:59:59'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;if any of that feels familiar from data vault, that is because effective satellites are essentially the same idea expressed in vault vocabulary. i wrote about a related grain trap in &lt;a href="https://philliant.com/posts/20260324-left-join-effective-satellite-cte/" rel="noopener noreferrer"&gt;left join an effective satellite without duplicating rows&lt;/a&gt;, and the same care applies here. the moment you have validity intervals, every join and every filter has to respect them.&lt;/p&gt;

&lt;h2&gt;
  
  
  what a dbt snapshot is
&lt;/h2&gt;

&lt;p&gt;a dbt snapshot is dbt's built-in implementation of type 2 history capture. you write a query that returns the current state of a source, and dbt takes responsibility for comparing that current state against the snapshot table on every run, closing rows that changed and inserting new versions. the columns it adds are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dbt_valid_from&lt;/code&gt;: when this version became current&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dbt_valid_to&lt;/code&gt;: when this version stopped being current (or the sentinel for current rows)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dbt_scd_id&lt;/code&gt;: a hash of the unique key plus &lt;code&gt;dbt_valid_from&lt;/code&gt; for stable surrogate identity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;i wrote a longer companion piece on the practical migration story in &lt;a href="https://philliant.com/posts/20260408-dbt-snapshots/" rel="noopener noreferrer"&gt;dbt snapshots, moving from merges to native history&lt;/a&gt;. that post is the "how to do it well" view. this post is the "how to do less of it" view.&lt;/p&gt;

&lt;h2&gt;
  
  
  when a snapshot is the right call
&lt;/h2&gt;

&lt;p&gt;reach for a snapshot when &lt;strong&gt;all&lt;/strong&gt; of the following are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the source system overwrites the row in place and does not retain history of its own&lt;/li&gt;
&lt;li&gt;you genuinely need point-in-time answers, not just "the current value"&lt;/li&gt;
&lt;li&gt;you cannot reconstruct the historical state from a deterministic formula or from another system that already keeps history&lt;/li&gt;
&lt;li&gt;the source has a stable grain and a reliable unique key&lt;/li&gt;
&lt;li&gt;you can guarantee the snapshot will run on a cadence that catches every change you care about&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;if any one of those is false, a snapshot is probably the wrong tool. for example, if the source already publishes change events to a message broker, capture the events into a regular append-only table and model on top of that. if the value is a deterministic function of other inputs (for example a derived score from frozen reference data), recompute it. if the source has a &lt;code&gt;cycle_id&lt;/code&gt; or some other natural temporal key, join on that key directly instead of leaning on validity intervals.&lt;/p&gt;

&lt;h2&gt;
  
  
  the cost: complexity, brittleness, maintenance, backups
&lt;/h2&gt;

&lt;p&gt;this is the part most adoption guides skip over. snapshots look free in the demo and feel free for the first month. then the bills start to come in.&lt;/p&gt;

&lt;h3&gt;
  
  
  complexity in the dag
&lt;/h3&gt;

&lt;p&gt;a snapshot is a node in your dag, but it does not behave like other nodes. it is the only model type that has hidden state from prior runs, and it requires its own command (&lt;code&gt;dbt snapshot&lt;/code&gt;) on its own schedule. a dbt project that contains snapshots has, in practice, two pipelines that have to stay in lockstep, namely the regular &lt;code&gt;dbt build&lt;/code&gt; and the snapshot pipeline. when one of them lags or fails, downstream consumers see stale or partial history and the symptoms can be subtle.&lt;/p&gt;

&lt;p&gt;every snapshot also forces every downstream model that reads it to think about validity intervals. queries that used to be a simple &lt;code&gt;select&lt;/code&gt; now need a current-row filter or a point-in-time predicate, and reviewers have to verify that filter on every change.&lt;/p&gt;

&lt;h3&gt;
  
  
  brittleness under change
&lt;/h3&gt;

&lt;p&gt;snapshots are unusually sensitive to upstream changes. a few examples i have seen close up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a source query is widened to include a new column, and the &lt;strong&gt;check&lt;/strong&gt; strategy now flags every row as changed on the next run, doubling the table overnight&lt;/li&gt;
&lt;li&gt;a source briefly drops keys (because of a partial backfill or a bad upstream join), and a &lt;code&gt;hard_deletes = invalidate&lt;/code&gt; snapshot closes thousands of rows that are still valid&lt;/li&gt;
&lt;li&gt;duplicate keys appear in the source for a single run, and the snapshot either fails or quietly bloats with overlapping intervals&lt;/li&gt;
&lt;li&gt;the source schema changes type on a column, and the snapshot now refuses to merge because the staged data and the historical data disagree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;each of these takes a careful, surgical fix. you cannot just rerun the snapshot from scratch, because the original sequence of source values is gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  maintenance and backups
&lt;/h3&gt;

&lt;p&gt;because snapshot output is not reproducible, you have to treat the snapshot table itself as &lt;strong&gt;production data&lt;/strong&gt;, not a derived artifact. that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;regular &lt;strong&gt;backups&lt;/strong&gt; of the snapshot tables to a separate schema or storage location, with a retention policy you actually enforce&lt;/li&gt;
&lt;li&gt;a documented &lt;strong&gt;recovery runbook&lt;/strong&gt; for partial corruption (for example, restore from backup, replay only specific keys, validate intervals)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;alerting&lt;/strong&gt; on row-count deltas, row-count ratios per run, and anomalies in &lt;code&gt;dbt_valid_to&lt;/code&gt; distributions, so a misconfigured run does not run for a week before anyone notices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;change review&lt;/strong&gt; for any edit to the snapshot definition, because changing &lt;code&gt;check_cols&lt;/code&gt;, &lt;code&gt;unique_key&lt;/code&gt;, or the source query can rewrite history in non-obvious ways&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;a regular dbt model needs none of that. a snapshot needs all of it, and the cost scales with the number of snapshots in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  the worst pattern: snapshots on top of snapshots
&lt;/h2&gt;

&lt;p&gt;if there is one rule i would carve into the wall, &lt;strong&gt;never build a snapshot on top of another snapshot&lt;/strong&gt;. mixing two type 2 tables produces validity intervals on top of validity intervals, and the result is almost never what anyone wants.&lt;/p&gt;

&lt;h3&gt;
  
  
  why this is so dangerous
&lt;/h3&gt;

&lt;p&gt;a single type 2 table is already a careful object. its grain is the business key plus the validity interval, and every consumer has to filter to a single moment in time before doing anything else. when you stack a second snapshot on top of it, you are now tracking history of a thing that was already historical, and the questions you can sensibly ask multiply in ugly ways.&lt;/p&gt;

&lt;p&gt;think about what the row count of &lt;code&gt;snapshot_b&lt;/code&gt; becomes when its source query reads from &lt;code&gt;snapshot_a&lt;/code&gt; without point-in-time filtering. for any business key, you get the cartesian product of versions, which means changes in &lt;code&gt;snapshot_b&lt;/code&gt; get attributed to the wrong intervals of &lt;code&gt;snapshot_a&lt;/code&gt;. even if you do filter for current rows, the second snapshot will react to &lt;strong&gt;every&lt;/strong&gt; change in the first, including changes that have nothing to do with the attributes you care about, so you end up with a much noisier history than you wanted.&lt;/p&gt;

&lt;p&gt;even if you carefully filter the inner snapshot to its current row, you have lost something important. the outer snapshot now records history of a moving target. when you reread the outer snapshot at a past timestamp, the row you get back was generated against the inner snapshot's &lt;em&gt;current state at the time the outer run executed&lt;/em&gt;, not against the inner snapshot's state at the same past timestamp. this is the validity-on-validity trap, and it is almost impossible to reason about by inspection.&lt;/p&gt;

&lt;h3&gt;
  
  
  what to do instead
&lt;/h3&gt;

&lt;p&gt;if you find yourself wanting to build a second snapshot on top of a first, treat that as a signal that the design is wrong, not as a problem to solve in sql. a few healthier alternatives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;have &lt;strong&gt;one&lt;/strong&gt; snapshot per source object that genuinely needs history, and read it directly in your information delivery layer&lt;/li&gt;
&lt;li&gt;if you need a derived attribute that depends on a snapshot, compute that attribute in a &lt;strong&gt;regular view&lt;/strong&gt; that filters the snapshot to a single point in time and is itself recomputable&lt;/li&gt;
&lt;li&gt;if the derived attribute genuinely needs its own history, snapshot &lt;strong&gt;the source inputs&lt;/strong&gt; independently and join them by point-in-time logic in a downstream view, instead of stacking the snapshots themselves&lt;/li&gt;
&lt;li&gt;if your business has a natural temporal key (cycle, period, year), prefer joining by that key over inferring history from validity intervals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the goal is to push as much of the temporal logic as you can into deterministic transformations, and keep the snapshots themselves at the edges of the dag.&lt;/p&gt;

&lt;h2&gt;
  
  
  less is more, simple is better
&lt;/h2&gt;

&lt;p&gt;most of the temporal questions you think need a snapshot do not. before adding one, run through this short checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;is the value already historical somewhere upstream, in events, in a cycle table, or in another system, where i can read it without snapshotting myself?&lt;/li&gt;
&lt;li&gt;can i compute the value deterministically from current inputs, so any past answer is just a recomputation against frozen reference data?&lt;/li&gt;
&lt;li&gt;is the source overwriting history in place with no other record of the prior value?&lt;/li&gt;
&lt;li&gt;if i never built this snapshot, would consumers really lose information they care about, or just convenience?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;if the answer to either of the first two is yes, do not add a snapshot. if the answer to the third is no, do not add a snapshot. if the answer to the fourth is "just convenience", do not add a snapshot.&lt;/p&gt;

&lt;p&gt;what you are aiming for is a warehouse that is &lt;strong&gt;mostly deterministic&lt;/strong&gt;, with a small ring of carefully managed snapshots at the edges. the deterministic core is cheap to rebuild, easy to test, and forgiving to refactor. the snapshot ring is where the real cost lives, so you want it to be small enough that you can afford to back it up, monitor it, and recover it when something goes wrong.&lt;/p&gt;

&lt;p&gt;simple beats clever here. one well-run snapshot you understand is worth ten clever snapshots that nobody can rebuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  faq
&lt;/h2&gt;

&lt;h3&gt;
  
  
  when is a snapshot definitely worth it?
&lt;/h3&gt;

&lt;p&gt;when the source overwrites in place, you have a real business need for point-in-time answers, and there is no upstream event log or cycle key to lean on instead. operational systems that mutate rows without retaining history are the canonical case.&lt;/p&gt;

&lt;h3&gt;
  
  
  what is the single biggest mistake people make with snapshots?
&lt;/h3&gt;

&lt;p&gt;reading from a snapshot in another snapshot's source query. the validity-on-validity trap is the worst class of bug to debug, because the symptom shows up far away from the cause and the table looks plausible at a glance.&lt;/p&gt;

&lt;h3&gt;
  
  
  how do i reduce the number of snapshots in an existing project?
&lt;/h3&gt;

&lt;p&gt;start with the snapshots that are read by the smallest number of downstream models, and ask whether the consumers really need history or just the current row. if they only need current, replace the snapshot with a regular view. for the snapshots that genuinely need history, make sure each one is independent and that nothing else in the project reads a snapshot to feed another snapshot.&lt;/p&gt;

&lt;h3&gt;
  
  
  should i ever full-refresh a snapshot?
&lt;/h3&gt;

&lt;p&gt;almost never in production. a full refresh wipes the historical rows that no longer match the current source, which is the entire reason you built the snapshot in the first place. treat the snapshot table like operational data, not a derived artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.getdbt.com/docs/build/snapshots" rel="noopener noreferrer"&gt;dbt snapshots documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.getdbt.com/reference/snapshot-configs" rel="noopener noreferrer"&gt;dbt snapshot configurations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/type-2/" rel="noopener noreferrer"&gt;kimball group, slowly changing dimensions overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260408-dbt-snapshots/" rel="noopener noreferrer"&gt;dbt snapshots, moving from merges to native history&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260324-left-join-effective-satellite-cte/" rel="noopener noreferrer"&gt;left join an effective satellite without duplicating rows (use a cte)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260330-dbt-tests/" rel="noopener noreferrer"&gt;dbt tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/dbt/" rel="noopener noreferrer"&gt;dbt series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dbt</category>
      <category>snapshots</category>
      <category>type2scd</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>on the plane, again</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:35:23 +0000</pubDate>
      <link>https://dev.to/shrouwoods/on-the-plane-again-3j90</link>
      <guid>https://dev.to/shrouwoods/on-the-plane-again-3j90</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;my opinion on using wifi on a plane has shifted. i do not think it is the right default for everyone in every situation, but when i am traveling alone, especially for work, i have started to treat a connected cabin as a feature. it takes hours that used to feel like pure waiting, time i was just trying to burn through, and turns them into a stretch where i can work with a surprisingly solid level of focus and relatively few distractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;a few weeks ago i wrote about how torn i still was on this topic in &lt;a href="https://philliant.com/posts/20260324-wifi-on-planes/" rel="noopener noreferrer"&gt;plane wifi: when the cabin forced disconnect&lt;/a&gt;. that piece was an honest inventory of the tradeoffs. this one is an update from the other side of the choice, after i have spent more flights actually buying the pass and sitting down to work instead of debating it.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;p&gt;when i am alone and the trip is for my job, the row stops feeling like a cage and starts feeling like a quiet room with bad legroom. i already have headphones in, so the cabin noise is under control. notifications are fewer than at my desk, nobody is making noise by my office door, and the margin of "things i could be doing instead" feels narrower. it is not peace and it is not deep rest, but it is a usable kind of concentration.&lt;/p&gt;

&lt;p&gt;i am now using that block to write, to debug, to plan, and to close loops i would otherwise push to after i land. i go in knowing i will get a meaningful amount done, and that expectation makes the clock feel less stuck. the time still passes at the same speed, but it passes with output attached, and that changes how it feels in my body.&lt;/p&gt;

&lt;p&gt;i also have a concrete proof point that this mode is not just talk. i completely stood up my personal website while airborne, end to end, in one of those sessions. right now i am on a plane again, getting ahead for an in-person meeting so i can walk in prepared instead of scrambling on the jet bridge.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;i am not arguing that every person should pay for wifi on every flight. shared trips, family logistics, the middle seat, motion sickness, or the simple need to be offline are all good reasons to skip it. economy is still a bad default office for anyone who needs space or quiet that the cabin cannot give. my point is narrower. for me, in the situations where it fits, the cost of the pass is cheaper than the opportunity cost of treating the whole flight as lost time.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;i will probably still sometimes want the cabin to be an excuse to be unreachable. when i do, i can leave the wifi off. when i do not, i am glad the option exists, and i am using it on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/In-flight_connectivity" rel="noopener noreferrer"&gt;in-flight connectivity&lt;/a&gt;, background on how internet reaches aircraft&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260324-wifi-on-planes/" rel="noopener noreferrer"&gt;plane wifi: when the cabin forced disconnect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/commentary/" rel="noopener noreferrer"&gt;commentary series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>commentary</category>
      <category>travel</category>
      <category>wifi</category>
      <category>work</category>
    </item>
    <item>
      <title>logic</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:35:21 +0000</pubDate>
      <link>https://dev.to/shrouwoods/logic-3360</link>
      <guid>https://dev.to/shrouwoods/logic-3360</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;learning basic logic is one of the most useful, durable skills i can recommend to anyone, regardless of profession. the english-language version of if/then/else is a thinking tool that works everywhere, never expires, and quietly compounds into better decisions over a lifetime.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;most people associate logic with code, math, or a philosophy classroom. that framing is too narrow. logic is just structured cause-and-effect thinking, and the simplest version of it sounds exactly like english:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;if this, then that, else that other thing&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;once you can hold that pattern in your head on purpose, it changes how you plan, diagnose, design, and interpret almost everything in front of you. you do not need a programming language to use it. you just need to be willing to slow down for a beat and think in branches instead of straight lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  a few familiar gates
&lt;/h3&gt;

&lt;p&gt;here a few small pictures to demonstrate simple examples of logic gates that you already use in daily life. this just illustrates it so you can literally follow along with the logic gates as each situation progresses.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;and&lt;/strong&gt; both must be true for the outcome to be true&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;or&lt;/strong&gt; at least one true is enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;not&lt;/strong&gt; you follow the opposite branch of the test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the flow is the same kind of small chart you would sketch on a napkin.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; mermaid &amp;gt;}}&lt;br&gt;
flowchart TB&lt;br&gt;
subgraph g_and [and, both need to be true]&lt;br&gt;
direction LR&lt;br&gt;
w[good weather?] --&amp;gt; and1{and}&lt;br&gt;
f[afternoon free?] --&amp;gt; and1&lt;br&gt;
and1 --&amp;gt;|yes| hike[go hiking]&lt;br&gt;
and1 --&amp;gt;|no| home[stay in]&lt;br&gt;
end&lt;br&gt;
subgraph g_or [or, at least one is enough]&lt;br&gt;
direction LR&lt;br&gt;
car[friend can drive?] --&amp;gt; or1{or}&lt;br&gt;
bus[transit is running?] --&amp;gt; or1&lt;br&gt;
or1 --&amp;gt;|yes| go[you can get there]&lt;br&gt;
or1 --&amp;gt;|no| stuck[you are stuck]&lt;br&gt;
end&lt;br&gt;
subgraph g_not [not, the opposite branch of the test]&lt;br&gt;
direction LR&lt;br&gt;
pow[power on?] --&amp;gt;|no| br[check the breaker]&lt;br&gt;
pow --&amp;gt;|yes| next1[next check in the chain]&lt;br&gt;
end&lt;br&gt;
{{&amp;lt; /mermaid &amp;gt;}}&lt;/p&gt;

&lt;p&gt;none of that requires a keyboard. it is the same branch habit as the if/then/else line in the last section, just drawn with a few boxes.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;h3&gt;
  
  
  logic is a thinking tool, not a coding tool
&lt;/h3&gt;

&lt;p&gt;the if/then/else pattern is older than any programming language. when i write a small script, i am formalizing the same branching i already do when i pick what to wear, route around traffic, or decide how to respond when something at work breaks. the keyboard is incidental, the structure is the point.&lt;/p&gt;

&lt;p&gt;this kind of structured thinking is what moves me from "i feel stuck" to "what is the next decision, and what are the branches under it". that small shift, from a vague feeling to a concrete branch point, is where most of the leverage comes from.&lt;/p&gt;

&lt;h3&gt;
  
  
  where it pays off in normal life
&lt;/h3&gt;

&lt;p&gt;once you start noticing branches, you see them everywhere. planning a day with kids becomes a small logic tree. if the weather holds, we hike. if it does not, we move to the indoor option. if both fail, we cancel and reschedule. naming the branches up front means the day does not collapse when conditions change.&lt;/p&gt;

&lt;p&gt;troubleshooting has the same shape. when something is not working, i walk a tree out loud. if the appliance has power, then check the next link. if not, then check the breaker. each step rules out a branch and shrinks the search space.&lt;/p&gt;

&lt;p&gt;designing a process at work has the same bones. i list the conditions first, then the path each one takes. naming the branches early makes the design easier to explain and easier to fix later.&lt;/p&gt;

&lt;p&gt;understanding behavior is harder, but the structure still helps. people are not perfectly logical, but their patterns often are. if my kid is tired, then certain tantrums become more likely. if a colleague is overloaded, then certain reactions track. recognizing the antecedent makes the response less personal and easier to handle.&lt;/p&gt;

&lt;p&gt;reverse engineering is the same thing run backward. when i look at a result and want to understand how it got there, i walk the logic in reverse. if this output exists, then these inputs and conditions must have been true. if not, then the model i had in my head is wrong, and that gap is useful information on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  the tool never goes out of style
&lt;/h3&gt;

&lt;p&gt;frameworks change. tools change. programming languages come and go. if/then/else does not. it is a structure of thought, not a piece of technology, which is why it keeps working in domains it was never designed for. cooking, parenting, negotiations, medical decision trees, customer support scripts, and legal arguments all lean on the same scaffolding.&lt;/p&gt;

&lt;p&gt;i find real comfort in skills that age well. so much of what i learn in tech has a short half-life now. logic does not. once i have it, i have it for good.&lt;/p&gt;

&lt;h3&gt;
  
  
  applying it broadly is what makes it powerful
&lt;/h3&gt;

&lt;p&gt;a tool that works in one place is useful. a tool that works everywhere is leverage. logic works everywhere because every domain has cause and effect, conditions, and outcomes. that generality is the multiplier, and it is the same kind of cross-domain value i wrote about with &lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt;. the principle stays steady while the surface details swap.&lt;/p&gt;

&lt;h3&gt;
  
  
  learn it as early as you can
&lt;/h3&gt;

&lt;p&gt;the earlier this gets internalized, the more downstream decisions inherit it. a kid who can think in branches asks better questions, accepts fewer "because i said so" answers, and gradually builds a habit of checking conditions before reacting. that habit then runs in the background for the rest of their life.&lt;/p&gt;

&lt;p&gt;i think about this with my own kids, and i think about it for myself. every year i wait to make decisions more deliberately is a year of slightly noisier decisions stacked behind me.&lt;/p&gt;

&lt;h3&gt;
  
  
  the butterfly effect of better decisions
&lt;/h3&gt;

&lt;p&gt;small improvements in single decisions do not look like much in isolation. two paths that differ by one degree at the start can end up far apart over a long enough timeline. better daily decisions, even by a thin margin, compound the same way &lt;a href="https://philliant.com/posts/20260406-little-by-little-a-little-becomes-a-lot/" rel="noopener noreferrer"&gt;a little becomes a lot&lt;/a&gt; does for habits. the quality of the inputs, repeated across years, becomes the quality of the life.&lt;/p&gt;

&lt;p&gt;logic is one of the cheapest ways i know to nudge that compounding in a good direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;logic on its own is not the whole answer. real situations carry emotion, ambiguity, missing information, and people who do not behave according to clean rules. if i treat every interaction like a flowchart, i lose intuition, empathy, and the ability to sit with uncertainty.&lt;/p&gt;

&lt;p&gt;the skill is to use logic as scaffolding, not as a replacement for judgment. i map the branches i can see, then i listen for the part that the branches do not capture. both layers matter, and the logical part actually helps the intuitive part by giving it a clean place to stand.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;this is one of the cheapest, most durable investments anyone can make. learn the english-language form of if/then/else. practice naming the conditions and the branches in your own life. apply it to planning, troubleshooting, designing, and understanding the people around you.&lt;/p&gt;

&lt;p&gt;learn it once and you keep it forever. apply it everywhere and it compounds. not many skills pay back like that.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Propositional_calculus" rel="noopener noreferrer"&gt;propositional logic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Decision_tree" rel="noopener noreferrer"&gt;decision tree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Critical_thinking" rel="noopener noreferrer"&gt;critical thinking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260406-little-by-little-a-little-becomes-a-lot/" rel="noopener noreferrer"&gt;little by little, a little becomes a lot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260410-comfortable-being-uncomfortable/" rel="noopener noreferrer"&gt;comfortable being uncomfortable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/commentary/" rel="noopener noreferrer"&gt;commentary series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>logic</category>
      <category>thinking</category>
      <category>decisionmaking</category>
      <category>problemsolving</category>
    </item>
    <item>
      <title>back at it</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Thu, 23 Apr 2026 12:27:13 +0000</pubDate>
      <link>https://dev.to/shrouwoods/back-at-it-4aa7</link>
      <guid>https://dev.to/shrouwoods/back-at-it-4aa7</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;this is a small checkpoint post. the heavy lift is not finished, but i am out of the weeds for now, and that is worth naming out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;the same work i was carrying in &lt;a href="https://philliant.com/posts/20260416-stick-with-it/" rel="noopener noreferrer"&gt;stick with it&lt;/a&gt; kept growing in weight and surface area. for a while it felt like one endless tangle. i stayed with it anyway, and eventually i approached it the way i should have from the start, as smaller chunks that stack into the much larger change. each piece still had to be real, but the sequencing and scope finally matched how my head and the system can tolerate change.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;p&gt;getting to a stable point did not erase the backlog. i still have more testing to run, more simulations to exercise, and real user acceptance testing ahead. the difference is that the foundation is no longer thrashing. errors and surprises have a place to land without undoing everything at once.&lt;/p&gt;

&lt;p&gt;that stability is what gave me room to breathe. i can take a short break on purpose, look at the whole arc with a little distance, and come back to the tuning work with less panic and more optimism. the remaining work is still serious, but it is the kind of serious that fits a calendar instead of the kind that owns every waking hour.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;a stable checkpoint is not the same as done. if i confuse relief for completion, i will skip validation i still need. the discipline now is to rest without pretending the job is closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;so i am back at it in a different posture, not firefighting the whole shape at once, but finishing the test matrix, listening to users, and dialing things in with a clearer mind. sticking with it got me here. the next stretch is about proving it in the world, calmly.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Chunking_(psychology)" rel="noopener noreferrer"&gt;chunking (psychology)&lt;/a&gt;, on breaking information and work into manageable units&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260416-stick-with-it/" rel="noopener noreferrer"&gt;stick with it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260406-little-by-little-a-little-becomes-a-lot/" rel="noopener noreferrer"&gt;little by little, a little becomes a lot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/commentary/" rel="noopener noreferrer"&gt;commentary series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>persistence</category>
      <category>workflow</category>
      <category>testing</category>
      <category>stress</category>
    </item>
    <item>
      <title>stick with it</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Fri, 17 Apr 2026 13:24:41 +0000</pubDate>
      <link>https://dev.to/shrouwoods/stick-with-it-3c6i</link>
      <guid>https://dev.to/shrouwoods/stick-with-it-3c6i</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;this one is for me as much as anyone reading. the single most important thing i can do on a long, hard project is keep showing up for it. motivation rises and falls, energy comes in waves, and neither of those things matter as much as continuity. if i stay with the work long enough, the payoff arrives, even when progress is invisible for stretches in the middle.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;i am in the middle of a very heavy lift right now. it started as a change i thought i would finish quickly, and it has turned into something much bigger. the effort, concentration, and validation required are more than i am used to, and the timeline has stretched well past what a typical change would take. the stress is real. i feel it in how i think about the project before bed and how quickly i reach for my laptop in the morning.&lt;/p&gt;

&lt;p&gt;i am still in it, though, because the value at the end is worth the cost. when this lands, i will have more stable and explainable historical data, which means my ongoing workload of troubleshooting data validity questions drops. less firefighting later is worth more pressure now, and that tradeoff is the only reason i would keep going through a change this heavy.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;h3&gt;
  
  
  continuity beats intensity
&lt;/h3&gt;

&lt;p&gt;motivation is a wave, not a rope. it pulls me forward for a while, then it lets go, then it comes back later with a different shape. if i tie my progress to the wave, i stop whenever the wave stops. if i tie my progress to the habit of showing up, the wave cannot take the project down with it. that is the same pattern i wrote about in &lt;a href="https://philliant.com/posts/20260406-little-by-little-a-little-becomes-a-lot/" rel="noopener noreferrer"&gt;little by little, a little becomes a lot&lt;/a&gt;, just applied to a single long problem instead of a daily practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  isolate your changes, even in your own playground
&lt;/h3&gt;

&lt;p&gt;the hardest lesson from this round is about isolation. i have been testing work in an environment i consider my playground, and for a long time that has been fine. this time, my changes broke downstream consumers, and the pressure immediately escalated because other people were suddenly blocked. the takeaway is simple. if my changes can reach downstream consumers, i need to separate my testing from a shared test environment, regardless of how freely i am used to moving in that space. a playground still has neighbors.&lt;/p&gt;

&lt;h3&gt;
  
  
  do not try to lift several objects at once
&lt;/h3&gt;

&lt;p&gt;i also tried to move multiple pieces of the system at the same time. i thought bundling them would be faster. what actually happened is that each piece depended on the others in a way that made every single one harder to validate, and the total stress grew faster than the total work. smaller, sequential chunks would have finished sooner and felt calmer. one object at a time, even if it feels slower on paper, is almost always faster in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  better preparation shrinks the stress
&lt;/h3&gt;

&lt;p&gt;the last lesson is about preparation. i went in expecting a small change and i prepared like it was a small change. when the scope grew, my preparation did not grow with it, and that mismatch is where the break points appeared. better preparation up front, regardless of how small i thought the task was, would have reduced both the stress and the number of places things could go wrong. the cost of preparing for a bigger job than you need is tiny. the cost of not preparing for the job you actually have is not.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;persistence is not the same as refusing to reassess. sticking with every hard thing forever is just sunk cost fallacy wearing a motivational t-shirt. the honest check i keep running is whether the value at the end is still real and still mine. if the answer is yes, i keep going. if the answer turns into no, i stop, and that is not quitting, that is discernment.&lt;/p&gt;

&lt;p&gt;there is also a stress cost to "push through" language. if the pressure is spilling into health, relationships, or judgment, that is a signal to change the pace, not a signal to try harder. pushing through is a tool, not a strategy, and it only works when i also rest and isolate the work properly. that is part of why i think it helps to get &lt;a href="https://philliant.com/posts/20260410-comfortable-being-uncomfortable/" rel="noopener noreferrer"&gt;comfortable being uncomfortable&lt;/a&gt; without confusing discomfort for permission to keep grinding.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;so this is my note to myself. keep going. the work is real, the value is real, and the lessons i am collecting on the way are already paying off for the next change. next time i will isolate my testing better, break the work into one object at a time, and prepare like the task is bigger than i think it is, because it almost always is.&lt;/p&gt;

&lt;p&gt;and if the wave of motivation dips again tomorrow, that is fine. waves dip. what matters is that i still show up, finish one more piece, and trust that continuity is the actual engine. stick with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Grit_(personality_trait)" rel="noopener noreferrer"&gt;grit (personality trait)&lt;/a&gt;, angela duckworth on long-term persistence&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Sunk_cost" rel="noopener noreferrer"&gt;sunk cost fallacy&lt;/a&gt;, useful balance for deciding when to keep going versus when to stop&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260406-little-by-little-a-little-becomes-a-lot/" rel="noopener noreferrer"&gt;little by little, a little becomes a lot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260410-comfortable-being-uncomfortable/" rel="noopener noreferrer"&gt;comfortable being uncomfortable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/commentary/" rel="noopener noreferrer"&gt;commentary series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>persistence</category>
      <category>consistency</category>
      <category>stress</category>
      <category>selftalk</category>
    </item>
    <item>
      <title>comfortable being uncomfortable</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Fri, 10 Apr 2026 19:29:32 +0000</pubDate>
      <link>https://dev.to/shrouwoods/comfortable-being-uncomfortable-5dac</link>
      <guid>https://dev.to/shrouwoods/comfortable-being-uncomfortable-5dac</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;i want to normalize a simple idea that still feels hard in practice. getting outside of your comfort zone is not a side quest. it is the main mechanism by which you stretch, learn, and see the world with more room in it for other people. yes, it is uncomfortable, and that is exactly the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;most of us are trained to seek stability. stability is not bad, but it is also not where the adaptation happens. when everything feels familiar, your brain is mostly rehearsing what it already knows. the moment you step into something new, the cost shows up immediately as awkwardness, uncertainty, or fear of looking foolish. that friction is not a sign you chose wrong. it is often a sign you chose honestly.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;p&gt;change is disruptive by definition. if it did not interrupt your default patterns, it would not be change. i think we should embrace that disruption more often, because it is where new experiences actually enter your life. without that interruption, you mostly get repetition with better packaging.&lt;/p&gt;

&lt;p&gt;the growth part is not theoretical. discomfort is where skills get pressure-tested. you learn not only how to do things, but how &lt;strong&gt;not&lt;/strong&gt; to do things, which is just as valuable and often faster feedback. mistakes in public or under stress are expensive emotionally, but they are also unusually clear. they show you boundaries, preferences, and limits in a way that a comfortable afternoon rarely will.&lt;/p&gt;

&lt;p&gt;more experiences also broaden your worldview in a practical sense. when you have seen more contexts, constraints, and ways people solve problems, it becomes harder to treat your own habits as universal law. that widening tends to produce more tolerant and compassionate attitudes, not because tolerance is a slogan, but because you have more firsthand evidence that reasonable people can live and work in very different, equally valid ways.&lt;/p&gt;

&lt;p&gt;so my encouragement is simple. expose yourself to new experiences on purpose. seek situations where the pressure is on you to perform, because that is where you rise to the occasion and discover how capable you can be. it is also where you might discover that this is not your thing, and you should move on. either outcome is a win, because both give you self-insight you cannot fake. you learn what energizes you, what drains you, and what you are willing to practice until it gets easier.&lt;/p&gt;

&lt;p&gt;this connects to how i think about &lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt; in general. comfort is a resting state. adaptation requires movement.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;there is a real downside to glorifying discomfort without boundaries. not every challenge is worth the cost, and not every "growth opportunity" is ethical or safe. pushing yourself is different from letting yourself be pushed past your values or health. the goal is not suffering for its own sake. the goal is chosen stretch, with recovery and discernment built in.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;i am not asking for constant chaos. i am asking for a bias toward the new when you can afford it, and toward the high-stakes try when you are ready. the uncomfortable path is where you find out who you are when the easy defaults are not available, and that knowledge is about as practical as it gets.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Comfort_zone" rel="noopener noreferrer"&gt;comfort zone&lt;/a&gt; (psychology of performance and anxiety)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260406-little-by-little-a-little-becomes-a-lot/" rel="noopener noreferrer"&gt;little by little, a little becomes a lot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/commentary/" rel="noopener noreferrer"&gt;commentary series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>growth</category>
      <category>change</category>
      <category>comfortzone</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
