AI-First SDLC: Designing for High Velocity and Cognitive Vertigo


If you use autonomous coding agents for more than a few hours, you will hit a wall.

It is a silent, exhausting kind of fatigue. You didn’t write a single line of boilerplate, you didn't fight compiler configurations, and you didn't search documentation. Yet, your head is throbbing, you feel dizzy, and your mental stamina is completely shot.

This is Cognitive Vertigo (or Cognitive G-Force). It is caused by the collapse of latency between intent and execution. When you are no longer limited by typing speed, your brain is forced into a continuous loop of high-frequency architecture evaluation, context-switching, and code review.

To survive a full-time role in this new era without burning out, you must discard the traditional developer workflow and adopt an AI-First Software Development Lifecycle (SDLC).


The Paradigm Shift: Think Slow, Build Fast

In the traditional SDLC, developers spend 80% of their energy coding and 20% designing. When coding, your brain operates in a slow, sequential state.

In an AI-First SDLC, this relationship is inverted:

gantt
    title Traditional vs. AI-First Developer Lifecycles
    dateFormat  X
    axisFormat %s
    
    section Traditional SDLC (Active Typing)
    Design (Slow)          :active, des1, 0, 20
    Coding & Debugging (Grind)  :active, cod1, 20, 100
    
    section AI-First SDLC (Agent Orchestration)
    Spec & Architecture (80% Brain)  :crit, des2, 0, 80
    Agent Execution & Tests (20% Verify) :crit, cod2, 80, 100

By separating the Design Phase (Think Slow) from the Execution Phase (Build Fast), you shield your brain from the constant feedback loops that drain your mental battery.


Core Pillars of the AI-First SDLC

Drawing inspiration from structured frameworks like SteveGJones/ai-first-sdlc-practices, here is the blueprint to structure your day-to-day workflow for maximum stamina:

graph TD
    A[1. Think Slow: Design & Spec] -->|Define boundaries, schemas, & tests| B[2. The Constitution: Rules of Play]
    B -->|Hand over blueprint to Agent| C[3. Build Fast: Async Execution]
    C -->|Zero-Technical-Debt Pipeline| D[4. Verify: Black-Box Validation]
    D -->|If tests fail| C
    D -->|If tests pass| E[5. Ship & Cooldown]

1. The Constitution (Declarative Rules)

Do not explain your design philosophy to an agent in every chat. Define a permanent configuration file (like a CONSTITUTION.md or .clauderc) in your repository root. This constitution outlines:

  • Strict coding style and directory hierarchies.
  • Error-handling patterns.
  • Mandatory unit test structures.
  • Zero-technical-debt boundaries.

By encoding your architectural standards in a file, the agent reads them as a system constraint. You no longer need to waste energy correcting style errors.

2. Pipeline Validation (validate --syntax/--quick/--pre-push)

Stop reading code line-by-line to verify correctness. This is a primary driver of cognitive fatigue. Instead, implement a multi-stage validation script in your workspace:

  • validate --syntax: Evaluates basic file structures and imports immediately after generation.
  • validate --quick: Runs fast unit tests and linter audits before commits are written.
  • validate --pre-push: Executes the full integration test suite and scans for security anomalies.

If the validation script passes, trust the implementation. Focus your energy on verifying the boundaries, not inspecting the lines.

3. Asynchronous Execution (The Pacing Rule)

Pair-programming with an agent in real-time is a cognitive trap. Instead, delegate asynchronously:

  1. Write the Specification: Explicitly define the goal, the affected files, and the expected unit test changes in a task file (task.md or implementation_plan.md).
  2. Launch the Agent: Tell the agent to execute the specification and output its results.
  3. Step Away: Physically leave your keyboard. Walk around, drink water, or look out a window. Let the agent compile, write, and run tests in its own sandbox.
  4. Evaluate the Artifacts: Return only when the agent has completed the task and updated its walkthrough document.

Daily Habits to Collapse Cognitive Fatigue to Zero

Bookmark this checklist and use it as your daily operating system:

HabitHow to ImplementWhy it Saves Your Brain
Zero-Cache BrainWrite all active tasks in a physical notepad or task.md. Never hold stack frames in your head.Frees up working memory.
Test-Driven IntentWrite the tests or output schemas first. Let the agent code until the tests pass.Eliminates the need for line-by-line review.
Paced BreaksSet a timer to step away from the IDE for 5 minutes after every major agent execution.Prevents cognitive overheating.
Interface DesignSpend your time writing HoneySQL configurations, Malli schemas, or API signatures.Keeps your brain in the "System Architect" tier.

The speed of development is no longer bound by syntax. It is bound by your mental endurance. Structure your workflow to protect your mind first.

Published: 2026-06-14

Tagged: architecture automation workflow developer-experience

Clojure & Bitcoin: Building a Sovereign Node & Integration Sandbox


Lisp (specifically Clojure) and Bitcoin are a natural match. Both prioritize immutable data structures, declarative transformations, and minimal state side-effects.

In this post, we'll walk through the architectural blueprint of bsv-clj, a sandbox project showing how to query Bitcoin node RPCs, map spendable UTXOs from local SQLite databases, validate transaction payloads, and redact sensitive data at the edge.


1. Node RPC Client Plumbing

The first layer is talking to the Bitcoin node itself over JSON-RPC. Using http-kit and cheshire (JSON parser), we can call remote node methods natively:

(def rpc-config
  {:url "http://127.0.0.1:8332"
   :user "rpcuser"
   :password "rpcpassword"})

(defn rpc-call
  "Executes a JSON-RPC method call on the Bitcoin node."
  ([method] (rpc-call method []))
  ([method params]
   (let [payload (json/generate-string
                  {:jsonrpc "2.0"
                   :id "clj-rpc"
                   :method method
                   :params params})
         options {:headers {"Content-Type" "application/json"}
                  :basic-auth [(:user rpc-config) (:password rpc-config)]
                  :body payload}
         response @(http/post (:url rpc-config) options)]
     (if (= 200 (:status response))
       (json/parse-string (:body response) true)
       {:error (str "HTTP Error: " (:status response))}))))

With this, running (rpc-call "getblockchaininfo") returns a native Clojure map representing the blockchain status.


2. Local-First Sandbox: UTXO Tracking via SQLite

Instead of querying third-party APIs (which exposes your financial privacy), the sovereign way is to read directly from your local wallet database.

Using next.jdbc and honey.sql, we query spendable outputs (UTXOs) from a local wallet sandbox:

(defn query-wallet-utxos
  "Queries local SQLite database to list spendable outputs."
  [db-path]
  (let [ds (jdbc/get-datasource {:dbtype "sqlite" :dbname db-path})
        query (sql/format {:select [:txid :outputIndex :amount :state]
                           :from [:outputs]
                           :where [:= :state "SPENDABLE"]})]
    (jdbc/execute! ds query)))

3. Transaction Validation with Malli

To prevent malformed transaction request structures from reaching signing nodes, we define a declarative schema using Malli:

(def TransactionRequestSchema
  [:map
   [:appId :string]
   [:inputs [:vector [:map
                      [:txid [:re #"^[a-fA-F0-9]{64}$"]]
                      [:outputIndex :int]]]]
   [:outputs [:vector [:map
                        [:toAddress [:re #"^[13m-n2-9][a-km-zA-HJ-NP-Z1-9]{26,34}$"]]
                        [:satoshis [:pos-int]]]]]
   [:metadata [:map
               [:timestamp :int]
               [:data :string]]]])

This enforces strict hexagonal boundaries: matching valid transaction hex patterns, valid Bitcoin addresses, positive satoshi spends, and clean metadata.


4. Privacy Sentry: Data Loss Prevention (DLP)

When attaching raw text metadata directly into transaction payloads or OP_RETURN scripts, leakages can happen. We inject a regex redaction layer to scrub sensitive Singapore NRIC profiles before they hit the blockchain:

(def sg-nric-regex #"(?i)\b[SGTFAM]\d{7}[A-Z]\b")

(defn redact-payload-metadata
  "Scans metadata text and redacts sensitive Singapore NRIC profiles."
  [text]
  (clojure.string/replace text sg-nric-regex "[REDACTED_NRIC]"))

Why this Architecture Fits the Bitcoin Ethos

By putting the RPC clients, local SQLite DBs, and payload validation under a Clojure workflow, we achieve:

  • Determinism: Zero side-effects inside key validations.
  • Hermetic Runs: No dependency on cloud providers to run rules.
  • Privacy: Data stays local and is scrubbed before broadcast.

You can check out the sandbox project source code on GitHub: github.com/nurazhardotcom/bsv-clj.

Published: 2026-06-14

Tagged: architecture security clojure bitcoin sandbox

Cognitive G-Force: Collapsing Development Time with Agentic Compilers


A few minutes ago, I experienced a strange kind of development vertigo.

I was sitting at my desk, directing an AI agent to clean up some repositories, rename local folders, re-render my blog, write three distinct technical articles, and push commits to GitHub. In exactly 46 minutes, we completed 8 distinct, multi-system tasks.

I literally told the agent: "I'm experiencing G-force and feel like vomiting. It's too fast."

When your development environment transforms from a text editor into a highly-leveraged orchestration sandbox, the speed limit is no longer how fast you can type—it is how fast your mind can context-switch.


The 46-Minute Flight Log

Here is the exact timeline of what was accomplished between 03:26 AM and 04:12 AM this morning:

  1. 03:26 AM - Domain Migration: Transitioned the live site configuration from clojure.nurazhar.com to blog.nurazhar.com, mapping custom CNAMEs and updating DNS configurations.
  2. 03:34 AM - OS Architecture Synthesis: Wrote and published an architectural overview of openSUSE Autonomous OS, detailing Snapper and Btrfs rollback recovery.
  3. 03:46 AM - Profile & Core Config Updates: Cleaned up the main GitHub profile, updated Babashka Quickblog parameters, and published a post on Quickblog's Minimalist Architecture (comparing it to Satoshi's peer-to-peer design philosophy).
  4. 03:53 AM - Refactoring Workspace Directories: Renamed local folders to resolve domain drift, and added client-side Mermaid.js rendering support to the site's layout templates.
  5. 03:58 AM - Local Code Analysis: Scanned a local, unpushed Clojure repository (bsv-clj), analyzed its Malli schemas, SQLite wallet querying, and DLP regex layers, and published a technical integration post about it.
  6. 04:05 AM - Remote Audit & Fixes: Identified and repaired broken repository links across the newly compiled site.
  7. 04:11 AM - Git Auth Resolution: Diagnosed a failing remote Git push caused by dummy environment tokens, bypassed it to push all changes securely to GitHub via system keyring credentials, and audited the directory for obsolete references.

The Cognitive Shift: From Typist to Director

Before agentic IDE tools, this workload would have taken half a day to a weekend of focused manual effort.

The time wouldn't be spent thinking; it would be consumed by the friction of execution:

  • Swapping windows to check file paths.
  • Writing out boilerplate configurations.
  • Drafting paragraphs, checking spelling, and formatting code blocks.
  • Dealing with credentials, terminal syntax errors, and compiler bugs.

When an agent handles the execution, you become the Director. You are no longer writing the code; you are compiling intent.

But this speed comes with a cost: Cognitive G-force. When you do not have to wait for compilers to run, files to save, or documentation to write, the latency between designing a system and seeing it live collapses to zero. You are constantly in high-throughput decision-making mode. It is exhilarating, but it can induce a literal sense of motion sickness.

The future of software engineering is no longer about learning syntax. It is about building the mental stamina to guide autonomous agents through complex architectures without losing your bearings.


How are you managing the cognitive load as developer velocity accelerates? Let's discuss.

Published: 2026-06-14

Tagged: automation workflow developer-experience reflection systems

How to Maximize Clojure Usage & Minimize Token Quota on Antigravity IDE


Developing Clojure applications with AI assistance introduces a unique challenge: Clojure namespaces are dense, data structures are deeply nested, and code-as-data constructs demand precise context. If you feed entire namespaces and REPL outputs to reasoning-focused LLMs without a strategy, you will burn through your token quotas (and API budget) rapidly.

Here is how to optimize your developer workflow in Antigravity IDE using Gemini's Thinking Budget configurations.


🧠 Understanding the Reasoning Settings (Thinking Levels)

Modern models (like Gemini 2.5/3.5 Flash and Pro) introduce a configurable Thinking Level (or Reasoning Budget). This setting represents how many internal, step-by-step reasoning tokens the model compiles before writing the final code.

To manage your tokens, you need to understand when to toggle between them:

SettingWhen to UseClojure Dev TasksQuota ConsumptionLatency
Low / OffSimple, rote coding and direct translationsBoilerplate templating, converting JSON to edn, writing basic Babashka shell scripts.Minimal (Input + Output only)Very Fast
MediumLogical, multi-step coding problemsStandard app functions, writing test suites, refactoring standard functions.ModerateModerate
HighHighly complex, stateful, or abstract logicDesigning advanced macros, debugging race conditions in core.async pipelines, orchestrating multi-agent systems.Maximum (Adds hidden thinking tokens)Slower

🛠️ Tactical Rules for Clojure Token Preservation

1. REPL-Driven Prompting (Don't Copy-Paste Entire Files)

Instead of copy-pasting your entire core.clj namespace to fix an error:

  1. Copy only the function signature and its spec (if you use clojure.spec).
  2. Paste the exact stack trace from your REPL.
  3. Keep the thinking level on Low. The model doesn't need to reason about your whole system to fix a syntax bug or structural partition mismatch.

2. Guard Against the Chat History Multiplier

In interactive chats, the IDE sends your entire chat history back to the model on every new turn. If your previous prompts returned responses that consumed 2,000 thinking tokens, those tokens are sent back as input on the next turn.

  • The Fix: Treat chat threads as transient. Once a bug is resolved or a concept is clear, close the chat thread and start a fresh one. This resets your input context back to zero.

3. Programmatic Control via Babashka

If you write automation scripts that query the Gemini API directly, you can configure the thinking budget dynamically in your payload using generationConfig.

Here is a Babashka script template showing how to switch thinking budgets dynamically:

#!/usr/bin/env bb
(require '[babashka.http-client :as http]
         '[cheshire.core :as json])

(def api-key (System/getenv "GEMINI_API_KEY"))

(defn query-gemini [prompt thinking-budget]
  ;; thinking-budget = 0 (Minimal/Low)
  ;; thinking-budget = 1024 or 2048 (Medium/High reasoning token limit)
  (let [url (str "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=" api-key)
        payload {:contents [{:parts [{:text prompt}]}]
                 :generationConfig 
                 (merge 
                  {:temperature 1.0}
                  (if (zero? thinking-budget)
                    {:thinkingConfig {:thinkingBudget 0}}
                    {:thinkingConfig {:thinkingBudget thinking-budget}}))}]
    (-> (http/post url {:headers {"Content-Type" "application/json"}
                        :body (json/generate-string payload)})
        :body
        (json/parse-string true)
        :candidates
        first
        :content
        :parts
        first
        :text)))

;; Example: Run a low-cost, fast query
(println (query-gemini "Generate a basic Babashka task definition for file-syncing." 0))

🎯 Conclusion

Treating LLM usage like hardware memory management makes you a highly efficient developer:

  • Use Low by default for 90% of your coding and rapid chatting.
  • Elevate to High reasoning exclusively for architectural bottlenecks or macro debugging.
  • Keep your chat history short.

Published: 2026-06-14

Tagged: clojure llm workflow developer-experience babashka

Quickblog Gotcha: Why Your Post Titles Are Displaying Twice


Quickblog Gotcha: Why Your Post Titles Are Displaying Twice

When migrating articles from other static site generators (like Hugo, Jekyll, or standard GitHub-flavored Markdown) to Quickblog, you might run into a jarring visual bug: your post titles show up twice at the top of the page.

Here is why it happens, and how to programmatically fix it across your entire codebase.


The Root Cause: Template vs. Body

In standard Markdown styling, it is natural to start your file with an H1 header matching your title:

Title: My Great Post
Date: 2026-06-14
---
# My Great Post

This is the content of my post...

However, Quickblog uses a rendering template (typically found in templates/post.html) to dynamically construct your posts. If you inspect the default template, you will see it already wraps your metadata {{title}} in an H1 block:

<h1>
  {% if post-link %}<a href="{{post-link}}">{% endif %}
  {{title}}
  {% if post-link %}</a>{% endif %}
</h1>
{{body | safe}}

Because Quickblog inserts the title dynamically, any # Title header you manually write inside the Markdown body will result in a double title on the compiled HTML page.


The Fix: Programmatic Refactoring

Instead of manually editing dozens of markdown files to delete the redundant H1 header, you can clean them all up instantly using a quick Python regex script.

Run this command in your repository root:

python3 -c '
import glob, re
for f in glob.glob("posts/*.md"):
    with open(f, "r") as file:
        content = file.read()
    # Remove the first H1 header line and any trailing empty lines immediately after it
    new_content = re.sub(r"(?m)^# .*\n\n?", "", content, count=1)
    with open(f, "w") as file:
        file.write(new_content)
'

This script:

  1. Loops through every Markdown file in your posts/ folder.
  2. Uses a regex pattern ((?m)^# .*\n\n?) to find only the first occurrence of an H1 header (# ) at the beginning of a line.
  3. Removes it along with any trailing blank lines.
  4. Rewrites the file, leaving your metadata header and standard body intact.

Once completed, run bb quickblog render to rebuild the pages, and the layout will render perfectly with a single title block.

Published: 2026-06-14

Tagged: simplicity automation clojure web-development

Quickblog: The Zen of Minimalist Static Site Generation


When building a personal blog, modern web development defaults to overkill. Setting up dynamic frameworks (like Next.js, Remix, or MERN stacks) with backend databases, authentication layers, and real-time APIs introduces a massive maintenance burden, security attack surface, and slow page-load times.

For a blog, static HTML is the ultimate architecture. This blog is powered by Quickblog, a lightweight static site generator designed for the Clojure/Babashka ecosystem by Michiel Borkent (@borkdude).

Here is a breakdown of how it works under the hood and why this architecture is a superior design choice.


1. The Core Runtime: Babashka

At the heart of Quickblog is Babashka, a fast-starting scripting runtime for Clojure.

Traditional Clojure runs on the JVM, which is powerful but suffers from a slow startup time (often several seconds)—making it tedious for quick CLI utilities and scripts. Babashka solves this by using GraalVM to compile the Clojure interpreter into a native binary. This gives us:

  • Instant startup (measured in milliseconds).
  • Zero JVM overhead on the host.
  • A rich subset of Clojure libraries (like shell execution, filesystem navigation, and HTTP client/server) pre-baked into the runtime.

Quickblog is executed entirely as a Babashka task defined in bb.edn.


2. Compilation Flow

Quickblog's architecture follows a clean, functional pipeline:

graph TD
    A[Markdown Posts /posts] --> B(Babashka CLI / bb.edn)
    C[HTML Templates /templates] --> B
    D[Assets /assets] --> B
    B -->|bb quickblog render| E[Pure HTML/CSS/RSS /public]
    E -->|Git Push| F[GitHub Pages]
    F -->|HTTPS CNAME| G(blog.nurazhar.com)
  1. Ingestion: It reads raw Markdown files from the /posts directory and reads configuration parameters from bb.edn.
  2. Metadata Parsing: It extracts frontmatter metadata (Title, Date, Tags, Description) from the top of each Markdown file.
  3. HTML Transformation: Using Clojure-based parsing engines, it transforms Markdown syntax into clean HTML.
  4. Templating: It uses Selmer (a Django-like templating library written in Clojure) to wrap the rendered Markdown inside your main HTML layout (/templates/base.html, /templates/post.html, etc.).
  5. Feed & Index Generation: It automatically aggregates posts sorted by date to generate:
    • index.html (the homepage loading up to N posts).
    • archive.html (a list of all posts).
    • tags.html (filtered views).
    • feed.xml (RSS feed).
  6. Output: All static resources are placed into a single /public directory, ready to be hosted anywhere.

3. Why This Design is Superior (The Pros)

  • Zero-Database Security: No dynamic database queries mean zero SQL injection, zero Server-Side Request Forgery (SSRF), and zero database connection exhaustion.
  • Extreme Performance: Static HTML files are served directly by the web server (GitHub Pages / Nginx / Cloudflare) at edge speeds. The browser parses only clean HTML and CSS, rendering instantly.
  • Low Footprint: No background servers running on Node.js or Python, minimizing CPU/Memory usage.
  • Git as the Single Source of Truth: Posts are stored as plain text version-controlled files. Moving, backing up, or editing posts is as simple as running standard Git commands.

Conclusion: Emulating Satoshi's Design Philosophy

In his original 2008 Bitcoin v0.1 release, Satoshi Nakamoto focused on peer-to-peer simplicity—designing a system with direct nodes, no unnecessary intermediate layers, and raw efficiency.

Quickblog mirrors this philosophy in web development. By stripping away heavy JS frameworks, hydrations, client-side state managers, and database servers, it leaves you with exactly what the web was built for: clean, readable, and immortal HTML.

Published: 2026-06-14

Tagged: simplicity architecture clojure babashka

The 15-Minute VAPT: Zero-Backend Security on Neon and GCP


In enterprise environments (like Singapore's defense and corporate sectors), Vulnerability Assessment and Penetration Testing (VAPT) is typically a multi-week bureaucratic slog. You pay a consultant $20,000+, they run standard scanner suites, generate a bloated 50-page PDF report of generic warnings, and hand it back to your developers to spend three months debating and patching.

It is a high-overhead, low-velocity cycle.

But when you build using a "No-Backend" architecture—eliminating custom middleware servers in favor of static edge clients, serverless database triggers, and a stateless proxy—the attack surface shrinks so dramatically that you can perform a thorough VAPT, write custom sanitizers, harden schema constraints, and redeploy to production in less than 15 minutes.

Here is how we did exactly that for lagu-lagu (a real-time Singapore PayNow payout registry for independent artists).


🏗️ The Minimalist Surface Area

Our application uses a zero-server runtime model:

  1. Frontend: Static HTML and HTMX served from GitHub Pages (100% static CDN).
  2. Database: Serverless Neon Postgres, with Row-Level Security (RLS) enabled and a PL/pgSQL database trigger calculating the 80/20 split on payment insertion.
  3. API Gateway: A stateless GCP Cloud Function (Node.js) that secures connection strings, handles incoming webhooks, and returns pre-rendered HTML snippets.

By deleting standard backend servers (like Spring Boot or Express), we deleted 99% of our dependency overhead. We don't have server routes to exploit, memory leaks to exploit, or unpatched framework packages.

But minimalist doesn't mean bulletproof. We audited the system and found three real vulnerabilities.


🔍 The VAPT Checklist & Exploits

1. Stored XSS via HTML Template Injection (High Severity)

Our GCP function was querying the database and generating HTML string templates dynamically to serve back to the HTMX frontend:

const html = rows.map(artist => `<h3>${artist.name}</h3>...`).join("");
  • The Exploit: If an attacker registered an artist name containing a malicious script tag (e.g., <script>steal(document.cookie)</script>), Postgres would store it, the GCP function would fetch it, and the browser would execute it.
  • The Fix: We wrote a lightweight, native HTML escaping function inside Node.js and wrapped all dynamic SQL variables before concatenation:
    function escapeHtml(unsafe) {
      if (!unsafe) return '';
      return unsafe.toString()
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
    }
    

2. Lack of DB Constraints on Payment Splits (Low Severity)

The split trigger calculated the 80/20 division using standard multiplication (NEW.amount * 0.80). But the schema did not explicitly validate that payment amounts were positive.

  • The Exploit: If a negative transaction amount (e.g. SGD -10.00) bypassed frontend validation or was injected via a rogue webhook, the database would generate a negative artist payout (SGD -8.00), causing financial ledger logic corruption.
  • The Fix: We executed raw SQL queries to add constraints directly to the live Neon instance:
    ALTER TABLE transactions ADD CONSTRAINT check_positive_amount CHECK (amount > 0);
    ALTER TABLE payouts ADD CONSTRAINT check_positive_payout CHECK (amount_sent > 0);
    

3. Error Verbosity Leak (Low Severity)

In our try-catch block, we returned raw stack errors to the client:

catch (error) {
  return res.status(500).json({ error: error.message });
}
  • The Exploit: If a query crashed, Postgres table names, column structures, and connection parameters would leak directly in the browser console.
  • The Fix: We redirected all detailed traces to GCP Cloud Logging and genericized the HTTP client response to a sanitized string:
    catch (error) {
      console.error("Internal API error:", error);
      return res.status(500).json({ error: "Internal Server Error" });
    }
    

🔒 Proactive Prevention: GitHub Push Protection

To ensure we never leak a connection string or API token in the future, we used the GitHub CLI to globally configure security settings for our repos:

echo '{"security_and_analysis":{"secret_scanning":{"status":"enabled"},"secret_scanning_push_protection":{"status":"enabled"}}}' \
  | gh api --method PATCH repos/nurazhardotcom/lagu-lagu --input -

With Push Protection enabled, GitHub will intercept and block any git push command at the CLI layer if it detects an exposed password or token before it is committed to the cloud.


💡 The Security Lesson

Modern security compliance (like Singapore's Cyber Trust Mark or corporate checklists) tends to focus on administrative processes, PDF paperwork, and constant scanning.

But complexity is the ultimate enemy of security.

The safest line of code is the one you never wrote. By keeping your backend database-native, relying on Postgres constraints for business logic, and routing calls via stateless Cloud Functions, you keep your system so simple that VAPT goes from a multi-week corporate bottleneck to a 15-minute engineering sprint.

Published: 2026-06-14

Tagged: vapt security htmx postgres gcp finance serverless

The Autonomous OS: Btrfs, Snapper, and the Safety Net for Agentic SysOps


As of mid-2026, the tech industry has reached a quiet consensus: AI agents cannot safely manage servers using raw bash terminal access.

LLMs are probabilistic. They guess the next best token. While a 99% accuracy rate is great for writing marketing copy, a 1% failure rate in system operations (SysOps/LMOps) can mean deleting database volumes, bricking configurations, or corrupting boot partitions.

So how do we give AI agents the keys to the kingdom without risking total disaster? We build a legacy safety net.

This post explores the concepts from our deep dive into the openSUSE Autonomous OS pathway—specifically, how openSUSE is pairing futuristic AI standard protocols with battle-tested filesystem technologies to build closed-loop self-healing systems.


🚗 The Core Metaphor: From Brain to Machine

To understand how AI interacts with an operating system today, we can break the architecture down into four layers using a simple Car, Steering Wheel, and Driver analogy:

graph TD
    A["🧠 1. The App / Client (The Driver) <br>Cursor, Claude, Antigravity Client"] -->|Sends MCP commands| B["🛞 2. Model Context Protocol (The Steering Wheel) <br>Standardized Tool-Calling Protocol"]
    B -->|Translates to local shell| C["⚙️ 3. The MCP Server (The ECU / Hands) <br>Local python/node daemon translating commands"]
    C -->|Executes filesystem changes| D["🏎️ 4. The Operating System (The Engine) <br>openSUSE, Btrfs, Snapper, Hardware"]
  1. The App / Client (The Driver): The AI brain (often running in the cloud or local workstation). It is smart, but has no physical hands. It cannot type or edit local files directly; it only outputs text.
  2. Model Context Protocol / MCP (The Steering Wheel): The open-source standard connecting the AI to your computer. Just like USB-C standardized physical hardware connections, MCP standardizes how AI models call tools.
  3. The MCP Server (The Engine Control Unit / The Hands): A lightweight background program running on your host machine. It translates the standardized tool-calling instructions from the AI into physical actions (e.g. running a bash script or editing a config).
  4. The Operating System (The Engine): The underlying platform (Linux, Btrfs, Snapper, hardware) that performs the physical disk writes and computes the changes.

🗺️ The Four Enterprise Pathways

In 2026, different enterprise Linux vendors are approaching AI SysOps from unique angles:

DistributionCore StrategyKey TechnologiesIdeal Workloads
openSUSE / SUSEThe Self-Healing LoopBtrfs + Snapper + transactional-updateAutonomous mitigation, self-updating servers, write-capable AI agents.
Red Hat / RHELTelemetry & Diagnosticslinux-mcp-server + systemd logsCompliance-heavy environments, read-only AI audits, remote troubleshooting.
Amazon LinuxCloud ScalingAWS Agent Toolkit + Amazon QCloud-native microservices, ECS clustering, automated infrastructure scaling.
Canonical / UbuntuSovereignty & Local AILocal LLMs + Snap/Flatpak sandboxesOffline developer setups, on-device training, high-privacy data processing.

🛡️ The openSUSE Solution: Btrfs & Snapper as a Safety Net

SUSE's key advantage is that they didn't write new, experimental code to protect systems from AI hallucinations. Instead, they leveraged their decade-old, bulletproof filesystem technology: Btrfs and Snapper.

This creates the Self-Healing Loop:

  [ AI Agent ] 
       │
       ├─► 1. Save Checkpoint (AI triggers Snapper snapshot e.g. Snapshot #100)
       ├─► 2. Execute Action (AI runs system upgrade or writes configuration)
       ├─► 3. Validate (AI runs post-change diagnostic tests)
       │
       ├───► If SUCCESS: Keep changes.
       └───► If FAILURE: Trigger Snapper rollback to Snapshot #100.

If the AI makes a destructive change, the entire OS is rolled back to the clean snapshot instantaneously. Because /home is typically kept on a separate subvolume/partition, the system is restored to safety without deleting any of your personal source code.


🔧 Arch Linux & CachyOS: Bridging the Plumbing Gap

During our session, we noticed a critical plumbing difference when trying to implement this self-healing loop on a non-SUSE system (like CachyOS / Arch Linux):

  • The openSUSE Way: Uses a heavily patched GRUB bootloader maintained by SUSE. When you trigger a rollback, the bootloader dynamically changes which snapshot to boot into.
  • The Arch Way: Most Arch-based systems use modern, minimal bootloaders like Limine or standard systemd-boot, which hardcode boot arguments (e.g., rootflags=subvol=/@).

To run SUSE-style rollbacks on Arch/CachyOS without breaking the boot sequence, we use the community tool snapper-rollback.

Instead of changing bootloader configurations, snapper-rollback renames the Btrfs subvolumes behind the scenes:

  1. It renames the broken subvolume (/@) to a backup directory.
  2. It makes a read-write clone of the selected clean snapshot and names it /@.
  3. The bootloader boots normally, thinking it's loading the usual folder, but loads the restored system instead.

🏁 Conclusion

The future of systems operations isn't just about making AI models smarter. It's about designing architectures where non-deterministic AI brains are safely sandbox-guarded by deterministic filesystems. By combining modern MCP integration with legacy partition rollbacks, we get the best of both worlds: autonomous AI operations with a zero-cost undo button.

Published: 2026-06-14

Tagged: automation linux agents opensuse mcp systems

Bitcoin v0.1 Set in Stone: Building a functional blockchain toolkit in Clojure


Over the last few days, I've been deep in the blockchain archives. I wanted to build a portfolio project that would let me study the core engine of peer-to-peer electronic cash without wrestling with an ever-mutating spec sheet.

I ended up building bsv-clj—a complete Clojure toolkit featuring an idiomatic JSON-RPC client, a read-only wallet tool, and a Ring/Hiccup-powered local block explorer. It connects directly to a Bitcoin SV (BSV) node.

Here is the story of how I designed it, and why Clojure and Bitcoin's original v0.1 protocol are a perfect match.


1. The Design Philosophy: Locking the Protocol

On June 17, 2010, Satoshi Nakamoto wrote a post on the Bitcoin forum that would define a core fork in blockchain history:

"The nature of Bitcoin is such that once version 0.1 was released, the core design was set in stone for the rest of its lifetime."

In modern blockchain development, this philosophy is rarely followed. BTC has introduced protocol changes like SegWit and Taproot; other chains rewrite their consensus rules every year. But the Bitcoin SV node client preserves the original v0.1 protocol spec: no arbitrary block size limits, no complex alterations of basic opcodes, and the original UTXO transactional mechanics.

For an engineer wanting to study the system, this stability is a superpower:

  • The RPC interface maps directly to Satoshi's original client design.
  • The transaction schema and UTXO model are clean, minimal, and stable.
  • You are querying the foundational data structures that launched the entire industry in 2009.

With this stable backend in place, my goal was to wrap it in an idiomatic Clojure layer to create the ultimate interactive learning environment.


2. Why Clojure + Bitcoin?

As I built the toolkit, I realized that Bitcoin's core mechanics are fundamentally functional. The parallels between Bitcoin's protocol and Clojure's standard library are striking:

Bitcoin Protocol ConceptClojure Equivalent
Transactions are immutable once writtenClojure's immutable data structures
UTXOs are consumed and created, never modifiedPure functions returning new state with no side effects
Script is a stack-based languageClojure's list and sequence abstractions
Blocks form an append-only chainPersistent data structures with structural sharing
Data-driven API (JSON-RPC)Clojure is data-oriented by design

When you query a Bitcoin transaction, you are given a map of inputs and outputs. An input consumes a past output; new outputs declare future UTXOs. There is no balance column in a database. Balance is a derived projection of the unspent UTXO set. In Clojure, this feels like home: you simply filter and reduce a collection of maps.


3. Designing bsv-clj

The codebase is split into three main modules:

A. The Core RPC Client

Instead of writing a dynamic helper for each of Bitcoin's hundred-plus RPC methods, I built a lean HTTP client in bsv.rpc.client that acts as a generic, data-driven wrapper around JSON-RPC 1.0:

(defn rpc-call [cfg method params]
  (let [body (json/generate-string {:jsonrpc "1.0"
                                    :id (str (java.util.UUID/randomUUID))
                                    :method method
                                    :params params})
        response (client/post (rpc-url cfg)
                              {:basic-auth [(:user cfg) (:password cfg)]
                               :body body
                               :content-type :json
                               :as :json})]
    (get-in response [:body :result])))

This single function handles the authentication, HTTP POST request, JSON encoding/decoding via Cheshire, and error wrapping. On top of this core function, I built high-level modules for blockchain, transaction, network, and mining queries.

B. The Wallet Toolkit

The wallet module is read-only for safety. It queries the node for the active UTXO set, tracks balances, and resolves address balances. Rather than relying on heavyweight database indexes, it extracts information directly from the node's mempool and block databases, showing how Clojure's sequence processing makes it trivial to filter and group blockchain data:

(defn group-by-address [utxos]
  (reduce (fn [acc utxo]
            (let [addr (:address utxo)
                  amount (:amount utxo)]
              (update-in acc [addr] (fnil + 0.0) amount)))
          {}
          utxos))

C. The Hiccup Block Explorer

To visualize the blocks and transactions, I built a local web-based explorer using Ring, Compojure, and Hiccup.

Instead of dealing with a complex JavaScript frontend framework (like React or Vue) or pulling in Tailwind, I used vanilla CSS to style a beautiful, dark-themed dashboard. Hiccup compiles standard Clojure vectors directly into HTML:

(defn block-page [block]
  (layout
    (str "Block #" (:height block))
    [:div.container
     [:h1 (str "Block #" (:height block))]
     [:div.card
      [:table
       [:tr [:td "Hash"] [:td (:hash block)]]
       [:tr [:td "Time"] [:td (format-time (:time block))]]
       [:tr [:td "Transactions"] [:td (count (:tx block))]]]]]))

The application launches instantly, renders server-side, and runs at 60 FPS without downloading a single megabyte of frontend JS bundles.


4. The REPL Experience

The magic of Clojure is the REPL. When you run clj -A:dev, the REPL starts and loads the user namespace, which exposes immediate development helpers.

Instead of relying on log statements or debugger breakpoints, you can interact with the blockchain in real-time:

;; Connect to local node
(connect! "localhost" 8332 "rpcuser" "rpcpass")

;; Check node synchronization
(status)
;; => {:height 850123, :difficulty 1293847291.82, :connections 8}

;; Grab the latest 3 blocks
(latest 3)
;; => ({:height 850123, :hash "00000..."} ...)

;; Start the local Ring server
(start!)
;; => "Server started at http://localhost:3000"

If you change a route in the explorer or modify the styling, you don't need to rebuild the project or restart the server. Clojure's dynamic classloading allows you to re-evaluate the namespace inside your running REPL, and the changes appear instantly on your next page refresh.


Conclusion

Building bsv-clj reinforced my belief that the data model is the program.

By stripping away the bloat of mutable state, heavy frameworks, and shifting protocols, I was able to build a portfolio-quality blockchain toolkit in under 2,500 lines of Clojure. It serves as both a stable toolkit for blockchain protocol research and a demonstration of Clojure's data-oriented design.

You can inspect the codebase and try it yourself at: github.com/nurazhardotcom/bsv-clj.

Published: 2026-06-09

Tagged: clojure blockchain functional-programming repl web-development bitcoin

The Browser is Fragile: Why I Nuked My Web GUI for Native Clojure Desktop


I've been building headhunter-agent—a local-first job search assistant and resume compiler—to automate my career strategy.

Initially, I tried building it as a web app. It was a fragile mess. In under ten minutes of debugging browser errors, I nuked the web files and rewrote the entire thing as a native Clojure desktop app using cljfx (JavaFX).

Here is the unfiltered story of how it went down, and why the browser is the wrong sandbox for local-first tools.


1. The Design: Deep Profiling & Multi-Agent Evaluations

The project started as a fork of career-ops, a basic Babashka CLI. But standard resume builders are shallow keyword-stuffers. They produce generic garbage that falls apart in real interviews.

To fix this, I re-architected it around three core requirements:

  • A Data Vault: An EDN database containing full qualifications, deep work history, and a curated library of 8-12 STAR stories (Situation, Task, Action, Result).
  • A 3-Stage Multi-Agent System (MAS):
    • Agent 1 (FCF Audit): Auditing the Job Description for legitimacy and Singapore's Fair Consideration Framework red flags.
    • Agent 2 (Fit Analysis): A brutal comparison of Master Profile gaps/strengths against the JD.
    • Agent 3 (Cheat Sheet): Business model deep dive, tech stack mapping, and cold outreach targets.
  • Interview Prep: Cross-referencing the JD with my STAR stories to map out exact answers to the 5 most likely questions.

2. The Browser Fails (The 10-Minute Pivot)

To build a GUI, I initially went the default route: ClojureScript with Reagent, using Scittle to interpret it directly in the browser on GitHub Pages (headhunter.nurazhar.com). I used typst.ts (WASM) to compile PDFs in-browser.

It was fragile from day one. Web specs evolve, local storage is restrictive, and state-syncing inside a browser sandbox is prone to weird bugs. When headhunter.nurazhar.com failed to load, I didn't want to waste hours wrestling with browser console errors and JS dependency hell.

The web stack is bloated. So, we deleted the web code and pivoted.


3. Native Desktop GUI with cljfx

Instead of the browser, I shifted to cljfx, a declarative wrapper for JavaFX.

Now, it is a standalone desktop application. No local web servers, no browser tabs, no JavaScript. It runs directly on the JVM, rendering native desktop windows on Arch/CachyOS.

Why this actually works:

  1. Zero Browser Bloat: It is a compiled program, not a tab running in an engine.
  2. Immutable UI State: The entire app state is inside a single Clojure atom. The UI is a pure function of the data. If the data is correct, the UI literally cannot render bugs.
  3. No Thread Freezing: Heavy Gemini API calls run in Clojure futures (background threads), while Platform/runLater dispatches the results back to the main UI. The desktop app stays responsive at 60 FPS while the agents run.
  4. Local Privacy: Everything is saved as local .edn files. No network syncing, no cloud databases, no trackers.

Conclusion

We are conditioned to default to the web for everything. But for local-first utilities, the browser is just a bloated, insecure sandbox that gets in the way.

Writing a native desktop app in Clojure is faster, runs better, and is vastly more stable. If your local tools are acting up because of browser quirks, stop trying to fix the JS bundle. Just delete it.

Codebase: headhunter-agent

Published: 2026-06-08

Tagged: automation clojure multi-agent-system cljfx career-ops desktop-gui

Hello Clojure!


Welcome to my new digital garden dedicated to Clojure, Babashka, Lisp, and developer workflow automation.

I built this blog using Quickblog—a lightweight static site generator powered by Babashka. It allows me to write posts in plain Markdown and compile them into lightning-fast, static HTML with zero overhead.

Why Clojure & Babashka?

In an engineering landscape increasingly dominated by complex build pipelines and massive toolchains, Clojure offers a breath of fresh air:

  • REPL-Driven Development: Immediate feedback and interactive coding.
  • Data as Code: Structural elegance and powerful macro systems.
  • Babashka: Brings Clojure's expressive power to shell scripting, replacing heavy bash scripts with fast-starting, native Clojure binaries.

Over the coming weeks, I will be sharing my automation scripts, system configurations, and architectural musings as I continue to design my ultimate developer workflow.

Stay tuned!

Published: 2026-05-31

Tagged: automation clojure babashka

Archive