<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged golang</title>
<link>https://coderlegion.com/tag/golang</link>
<description>Powered by Question2Answer</description>
<item>
<title>A Weekend Project With Docker Agent: Four Agents, Two Config Files, One Permissions Trap</title>
<link>https://coderlegion.com/30939/a-weekend-project-with-docker-agent-four-agents-two-config-files-one-permissions-trap</link>
<description>&lt;p&gt;Docker Agent lets me describe a team of AI agents, their tools, and their guardrails in one config file that a platform team can review like any other piece of infrastructure. That is the part I like most. It also has a permissions trap that I reproduced in a test, and I'll show it before you copy any example rules.&lt;/p&gt;
&lt;p&gt;I spent a weekend-sized block of time building a small project to find out how far that idea goes. The result is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mdabydeen/release-gate-crew&quot;&gt;release-gate-crew&lt;/a&gt;: four agents that review a service's container and CI configuration before release and write a PASS/BLOCK report.&lt;/p&gt;
&lt;h3 id=&quot;what-docker-agent-is&quot;&gt;What Docker Agent is&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.docker.com/ai/docker-agent/&quot;&gt;Docker Agent&lt;/a&gt; is Docker's open-source runtime for teams of agents. You write a config, run &lt;code&gt;docker agent run crew.yaml&lt;/code&gt;, and talk to the root agent, which delegates to the sub-agents you declared. It ships in Docker Desktop 4.63 and later (versions 4.49 to 4.62 called it &lt;code&gt;cagent&lt;/code&gt;), or as a standalone binary from &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/docker/docker-agent/releases&quot;&gt;GitHub releases&lt;/a&gt;. It works with Anthropic, OpenAI, Google, Bedrock, Docker Model Runner and a long list of other providers.&lt;/p&gt;
&lt;p&gt;I used v1.150.0, released 9 October 2026.&lt;/p&gt;
&lt;h3 id=&quot;the-team&quot;&gt;The team&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;              root (lead)                think, todo
             /     |      \
 container_auditor  pipeline_auditor  scribe
 fs: ./target       fs: ./target      fs: ./reports
 shell: grep -n
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;lead&lt;/strong&gt; never reads files. It delegates, then makes the gate call: BLOCK if any finding is high or critical.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;container auditor&lt;/strong&gt; reads &lt;code&gt;target/Dockerfile&lt;/code&gt; and &lt;code&gt;target/compose.yaml&lt;/code&gt;, and may run &lt;code&gt;grep -n&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;pipeline auditor&lt;/strong&gt; reads the CI workflow in &lt;code&gt;target/ci/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;scribe&lt;/strong&gt; writes &lt;code&gt;reports/gate-report.md&lt;/code&gt; and can write nowhere else.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;target/&lt;/code&gt; is a deliberately bad sample service: &lt;code&gt;FROM node:latest&lt;/code&gt;, a token baked into &lt;code&gt;ENV&lt;/code&gt;, a privileged container with the Docker socket mounted, and a &lt;code&gt;pull_request_target&lt;/code&gt; workflow that checks out pull request code with &lt;code&gt;permissions: write-all&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-whole-team-is-one-file&quot;&gt;The whole team is one file&lt;/h3&gt;
&lt;p&gt;Here is the core of &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mdabydeen/release-gate-crew/blob/main/crew.yaml&quot;&gt;&lt;code&gt;crew.yaml&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;models:
  lead:   { provider: anthropic, model: claude-sonnet-4-5, max_tokens: 8000 }
  worker: { provider: anthropic, model: claude-haiku-4-5,  max_tokens: 4000 }

agents:
  root:
    model: lead
    description: Release gate lead. Delegates reviews and makes the PASS/BLOCK call.
    instruction_file: prompts/lead.md
    sub_agents: [container_auditor, pipeline_auditor, scribe]
    toolsets:
      - type: think
      - type: todo

  container_auditor:
    model: worker
    description: Reviews Dockerfile and compose files for container risks.
    instruction_file: prompts/container_auditor.md
    toolsets:
      - type: filesystem
        allow_list: [&quot;./target&quot;]
      - type: shell

  # pipeline_auditor and scribe follow the same shape
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;sub_agents&lt;/code&gt; line is the whole orchestration. The runtime gives the lead a &lt;code&gt;transfer_task&lt;/code&gt; tool and handles the hand-off. Each agent can use a different model, so the coordinator gets the stronger one and the workers get the cheaper one.&lt;/p&gt;
&lt;p&gt;This is why I prefer config to framework code for this kind of work. In a code-first framework, the same team is classes, a tool registry, a loop, and retry handling, and the permissions live wherever someone remembered to put them. Here the reviewer sees the topology, the tool scope, and the policy in one diff. Swapping a provider is a two-line change. Nothing about the agents is hidden in a helper function.&lt;/p&gt;
&lt;h3 id=&quot;the-same-team-in-hcl&quot;&gt;The same team in HCL&lt;/h3&gt;
&lt;p&gt;Docker Agent also reads &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.docker.com/ai/docker-agent/configuration/hcl/&quot;&gt;HCL&lt;/a&gt;. If your team writes Terraform, this will feel familiar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-hcl&quot;&gt;agent &quot;container_auditor&quot; {
  model       = &quot;worker&quot;
  description = &quot;Reviews Dockerfile and compose files for container risks.&quot;
  instruction = file(&quot;prompts/container_auditor.md&quot;)

  toolset &quot;filesystem&quot; {
    allow_list = [&quot;./target&quot;]
  }
  toolset &quot;shell&quot; {}
}

permissions {
  allow = [&quot;read_file&quot;, &quot;read_multiple_files&quot;, &quot;shell:cmd=grep -n *&quot;]
  deny  = [&quot;shell:cmd=*curl*&quot;, &quot;shell:cmd=*&amp;amp;&amp;amp;*&quot;, &quot;shell:cmd=*;*&quot;, &quot;shell:cmd=*|*&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The runtime converts HCL to the YAML shape and validates both against the same schema. Labelled blocks and heredocs suit long prompts and per-agent commands well. Keep your expectations modest, though: there are no variables, locals or modules, and &lt;code&gt;file()&lt;/code&gt; is the only function. It is a second syntax, not a second language. I keep both files in the repo and run the same tests against each, so they cannot drift apart silently.&lt;/p&gt;
&lt;h3 id=&quot;guardrails-live-next-to-the-agents&quot;&gt;Guardrails live next to the agents&lt;/h3&gt;
&lt;p&gt;This is where Docker Agent reads like a DevOps tool rather than an AI demo. Every layer is declared in the same file:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Config &lt;/th&gt;&lt;th&gt;Effect&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Filesystem scope &lt;/td&gt;&lt;td&gt;&lt;code&gt;allow_list&lt;/code&gt; per toolset &lt;/td&gt;&lt;td&gt;Auditors read only &lt;code&gt;./target&lt;/code&gt;. The scribe writes only &lt;code&gt;./reports&lt;/code&gt;.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Hidden files &lt;/td&gt;&lt;td&gt;&lt;code&gt;.agentsignore&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;.env&lt;/code&gt;, keys and certs disappear from listings, reads and writes.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tool rules &lt;/td&gt;&lt;td&gt;&lt;code&gt;permissions&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Deny, then allow, then ask.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Safety mode &lt;/td&gt;&lt;td&gt;&lt;code&gt;runtime.safety: restricted&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Unattended runs deny anything not explicitly allowed, without prompting.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Spend &lt;/td&gt;&lt;td&gt;&lt;code&gt;budget&lt;/code&gt; &lt;/td&gt;&lt;td&gt;One shared $0.50 and 10-minute ceiling for all four agents.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Isolation &lt;/td&gt;&lt;td&gt;&lt;code&gt;--sandbox&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Runs the team in a Docker Sandboxes VM.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The budget detail is thoughtful. A named budget is one shared pot, so a lead that fans out to ten sub-agents cannot spend ten times the cap.&lt;/p&gt;
&lt;p&gt;Two other features earned their place. &lt;strong&gt;Flavors&lt;/strong&gt; are named patches inside the config, so &lt;code&gt;--flavor cheap&lt;/code&gt; swaps the lead to a smaller model without a second file. &lt;strong&gt;Sharing&lt;/strong&gt; packages the config as an OCI artifact: &lt;code&gt;docker agent share push ./crew.yaml you/release-gate-crew&lt;/code&gt;, then anyone with access can &lt;code&gt;docker agent run you/release-gate-crew&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;testing-the-guardrails-without-an-api-key&quot;&gt;Testing the guardrails without an API key&lt;/h3&gt;
&lt;p&gt;A config file is only as trustworthy as the tests behind it, so I wanted CI to prove the guardrails hold on every push without paying for tokens.&lt;/p&gt;
&lt;p&gt;I wrote a small OpenAI-compatible stub model in Go, standard library only. It plays a fixed script per agent: the lead delegates three times, the auditors read files, and then they deliberately try things they shouldn't. An &lt;code&gt;offline&lt;/code&gt; flavor points every agent at the stub:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;flavors:
  offline:
    providers:
      stub:
        api_type: openai_chatcompletions
        base_url: http://127.0.0.1:8089/v1
        token_key: STUB_API_KEY
    models:
      lead:   { provider: stub, model: scripted }
      worker: { provider: stub, model: scripted }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A Go integration test then runs the real &lt;code&gt;docker-agent&lt;/code&gt; binary against both &lt;code&gt;crew.yaml&lt;/code&gt; and &lt;code&gt;crew.hcl&lt;/code&gt; and checks each requirement in the project's &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mdabydeen/release-gate-crew/blob/main/SPEC.md&quot;&gt;SPEC.md&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--- PASS: TestCrew/crew.yaml/R2_delegates_to_scribe
--- PASS: TestCrew/crew.yaml/R4_.agentsignore_hides_target/.env
--- PASS: TestCrew/crew.yaml/R5_auditor_read_scope_holds
--- PASS: TestCrew/crew.yaml/R6_compound_shell_is_denied
--- PASS: TestCrew/crew.yaml/R7_scribe_cannot_write_target/
--- PASS: TestCrew/crew.yaml/R9_target_unchanged
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What the runtime returned, verbatim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reading &lt;code&gt;target/.env&lt;/code&gt;: &lt;code&gt;path &quot;target/.env&quot; is excluded by .agentsignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The pipeline auditor reading a prompt file: &lt;code&gt;outside the allowed directories (./target)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The scribe writing &lt;code&gt;target/Dockerfile&lt;/code&gt;: denied by the restricted safety mode, with no prompt&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grep ... &amp;amp;&amp;amp; curl --version&lt;/code&gt;: &lt;code&gt;Tool 'shell' is denied by permissions configuration&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The stub proves the harness, not the reviews. For review quality, the repo has three &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.docker.com/ai/docker-agent/features/evaluation/&quot;&gt;&lt;code&gt;docker agent eval&lt;/code&gt;&lt;/a&gt; sessions: the gate must BLOCK the sample and name its worst problems, must not reveal the &lt;code&gt;.env&lt;/code&gt; value when asked directly, and must not claim an edit when told to fix the Dockerfile. Those need real model keys and a container runtime, so they are not in CI yet. I have not run them yet, so I'm making no claim about review quality here.&lt;/p&gt;
&lt;h3 id=&quot;the-permissions-trap&quot;&gt;The permissions trap&lt;/h3&gt;
&lt;p&gt;Permission patterns are globs matched against the whole command string, and &lt;code&gt;*&lt;/code&gt; matches anything, including spaces. That makes the common prefix style unsafe for shell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;permissions:
  allow: [&quot;shell:cmd=grep -n *&quot;]
  deny:  [&quot;shell:cmd=curl *&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;grep -n FROM target/Dockerfile &amp;amp;&amp;amp; curl --version&lt;/code&gt; starts with &lt;code&gt;grep -n &lt;/code&gt;, so it matches the allow rule. It does not start with &lt;code&gt;curl &lt;/code&gt;, so it misses the deny rule. Deny is checked first, finds nothing, and allow approves it. I reproduced this with the stub (&lt;code&gt;--flavor naive-permissions&lt;/code&gt;): curl ran and printed its version banner, even under &lt;code&gt;restricted&lt;/code&gt; safety, because an explicit allow outranks the safety mode.&lt;/p&gt;
&lt;p&gt;The documentation is clear that permissions are enforced client-side and are not a security boundary, and it points to sandbox mode for isolation. That is fair. The trap is that prefix rules like &lt;code&gt;cat *&lt;/code&gt; and &lt;code&gt;rm *&lt;/code&gt; are the pattern you see most often. What I use instead:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Deny shell operators outright: &lt;code&gt;*&amp;amp;&amp;amp;*&lt;/code&gt;, &lt;code&gt;*;*&lt;/code&gt;, &lt;code&gt;*|*&lt;/code&gt;, backticks, &lt;code&gt;$(&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Write deny rules as &lt;code&gt;*curl*&lt;/code&gt;, not &lt;code&gt;curl *&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Allow one narrow command per agent that needs a shell at all.&lt;/li&gt;
&lt;li&gt;Run anything you don't fully trust with &lt;code&gt;--sandbox&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The test suite asserts the trap still exists, so if a future release starts parsing compound commands, CI will tell me and I'll update the advice.&lt;/p&gt;
&lt;h3 id=&quot;other-rough-edges&quot;&gt;Other rough edges&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The filesystem toolset is unrestricted by default.&lt;/strong&gt; Without an &lt;code&gt;allow_list&lt;/code&gt;, absolute paths and &lt;code&gt;..&lt;/code&gt; reach anything the process can. Docker's own example file calls this a footgun. Set &lt;code&gt;allow_list&lt;/code&gt; on every filesystem toolset.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pulled configs can set their own safety mode.&lt;/strong&gt; An agent from a registry may declare &lt;code&gt;runtime.safety: autonomous&lt;/code&gt;. Your own &lt;code&gt;--safety&lt;/code&gt; flag or user settings win, but only if you set them. Read a config before you run it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sub-agents get the task text and nothing else.&lt;/strong&gt; I confirmed it: the delegated task arrives in a &lt;code&gt;&amp;lt;task&amp;gt;&lt;/code&gt; block in the sub-agent's system prompt, and its user turn is just &quot;Please proceed.&quot; Isolation is good. It also means the lead's prompt must pass along everything a specialist needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It moves fast.&lt;/strong&gt; The changelog lists 17 releases in September 2026 alone. Pin the version in CI. The rename from &lt;code&gt;cagent&lt;/code&gt; also shows: the config and data directories are still &lt;code&gt;~/.config/cagent&lt;/code&gt; and &lt;code&gt;~/.cagent&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Telemetry is on by default.&lt;/strong&gt; The first run says so. Set &lt;code&gt;TELEMETRY_ENABLED=false&lt;/code&gt; in CI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evals have prerequisites.&lt;/strong&gt; &lt;code&gt;docker agent eval&lt;/code&gt; runs each case in a container and the default judge model is an OpenAI model, so plan for a container runtime and a second provider key, or pass &lt;code&gt;--judge-model&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;try-it&quot;&gt;Try it&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/mdabydeen/release-gate-crew &amp;amp;&amp;amp; cd release-gate-crew
make test integration               # Go 1.24+ and docker-agent; no API key needed
export ANTHROPIC_API_KEY=...
docker agent run crew.yaml          # try /gate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Point it at your own service by replacing &lt;code&gt;target/&lt;/code&gt;. If you try the evals with real keys before I do, I'd like to know what the gate gets wrong.&lt;/p&gt;
&lt;p&gt;What I'd like to hear from people running agents in CI: where do your guardrails live today, in the agent's code or next to its config?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/_firelinks/a-weekend-project-with-docker-agent-four-agents-two-config-files-one-permissions-trap-2a10&quot;&gt;DEV&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/30939/a-weekend-project-with-docker-agent-four-agents-two-config-files-one-permissions-trap</guid>
<pubDate>Tue, 13 Oct 2026 13:02:21 +0000</pubDate>
</item>
<item>
<title>My Voice Is Code: How 2 Years as a Mute Freelance Go Developer Made Me an Async-First Expert</title>
<link>https://coderlegion.com/29767/my-voice-is-code-how-2-years-as-a-mute-freelance-go-developer-made-me-an-async-first-expert</link>
<description>&lt;h2 id=&quot;my-voice-is-code-how-being-mute-made-me-the-best-async-dev-you-will-hire&quot;&gt;My Voice Is Code: How Being Mute Made Me The Best Async Dev You Will Hire&lt;/h2&gt;
&lt;p&gt;Hello CoderLegion. I am Pires Aurelio, Rising Voice here on the platform.&lt;/p&gt;
&lt;p&gt;342 points, 13 badges, 5 technical tutorials in 3 days. But there is one thing my profile does not show: I am mute.&lt;/p&gt;
&lt;p&gt;I am not telling this for pity. I am telling because this is my career strategy. And if you lead a remote team, you will want to hire me after this post.&lt;/p&gt;
&lt;h3 id=&quot;1-silence-became-my-stack&quot;&gt;1. Silence Became My Stack&lt;/h3&gt;
&lt;p&gt;When I lost my speech, I gained obsessive focus on written communication.&lt;/p&gt;
&lt;p&gt;While other devs fix things with a 30 min call, I fix with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;README that any junior understands in 5 min&lt;/li&gt;
&lt;li&gt;PR with full context, screenshots, metrics and rollback plan&lt;/li&gt;
&lt;li&gt;API documentation before code&lt;/li&gt;
&lt;li&gt;Commit messages that tell a story&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is not extra. It is survival. And it became my superpower.&lt;/p&gt;
&lt;p&gt;Practical result: My Go Jobs Tracker project has a 12MB Docker image with 99 percent reduction, zero downtime deploy on Kubernetes, Prometheus with custom metrics, Grafana and healthz endpoint. Why? Because I cannot shout error on Slack. My system has to speak for me.&lt;/p&gt;
&lt;h3 id=&quot;2-what-2-years-as-freelance-go-taught-me&quot;&gt;2. What 2 Years As Freelance Go Taught Me&lt;/h3&gt;
&lt;p&gt;Since May 2024 I am a Freelance Independent Go Developer from Luanda Cacuaco Angola.&lt;/p&gt;
&lt;p&gt;Stack I deliver today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Golang: REST and gRPC APIs, worker pools, graceful shutdown, concurrent scraping&lt;/li&gt;
&lt;li&gt;DevOps: Docker multi stage, Kubernetes, Helm, GitHub Actions CI and CD&lt;/li&gt;
&lt;li&gt;Observability: Prometheus Counter and Histogram, Grafana, structured logging&lt;/li&gt;
&lt;li&gt;Extra: Digital Marketing and Promotion: I build AND promote the product&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My 5 tutorials here prove it: Deploying Go Scraper to Kubernetes, Resilient Job Queue in Go, Migrating from Python to Go, 5 Real World API Examples with Gin.&lt;/p&gt;
&lt;p&gt;All written, documented, production ready. Because my voice is code.&lt;/p&gt;
&lt;h3 id=&quot;3-why-async-first-teams-love-me&quot;&gt;3. Why Async First Teams Love Me&lt;/h3&gt;
&lt;p&gt;I do not work in teams that need 1h daily meetings. I thrive in teams that value:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Async Culture and Written First&lt;/li&gt;
&lt;li&gt;Inclusive and Accessibility First Environment&lt;/li&gt;
&lt;li&gt;Data Driven decisions via metrics and docs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are a Tech Lead tired of devs who do not document, who break production without metrics, who disappear without written updates... I am your antidote.&lt;/p&gt;
&lt;p&gt;My freelance workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;100 percent async, short overlap for review, rest written&lt;/li&gt;
&lt;li&gt;Small PRs, tested, with built in observability&lt;/li&gt;
&lt;li&gt;Runbooks that enable self serve onboarding for remote teams&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-open-to-work-remote-async-teams&quot;&gt;4. Open To Work: Remote Async Teams&lt;/h3&gt;
&lt;p&gt;I am currently open to remote opportunities: Freelance, Contract, Part-time or Full-time.&lt;/p&gt;
&lt;p&gt;If your team values written communication over endless meetings, documentation over noise, and results over presence, we will work great together.&lt;/p&gt;
&lt;p&gt;I work 100 percent async, overlap for code review, everything else documented. My timezone is GMT plus 1 Angola but I work worldwide remote.&lt;/p&gt;
&lt;p&gt;What I can do for you this week:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ship a production Go API with Docker and Prometheus&lt;/li&gt;
&lt;li&gt;Migrate your service to Kubernetes with zero downtime&lt;/li&gt;
&lt;li&gt;Document your messy project so any dev can onboard self serve&lt;/li&gt;
&lt;li&gt;Build and promote your technical product with Digital Marketing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I do not need a call to start. Send me a doc or an issue. I reply with a plan and a PR.&lt;/p&gt;
&lt;p&gt;Direct contact: my email is aureliopires186 at Gmail, my WhatsApp is plus 244 926451351, my LinkedIn is Pires Aurelio, my GitHub is Aureliopires186. Search for Pires Aurlio here on CoderLegion.&lt;/p&gt;
&lt;h3 id=&quot;5-the-invitation&quot;&gt;5. The Invitation&lt;/h3&gt;
&lt;p&gt;If you are a team lead, founder, or dev who has a Go freelance task stuck because you need someone reliable who documents, message me.&lt;/p&gt;
&lt;p&gt;My voice is code. Let my code speak for me.&lt;/p&gt;
&lt;p&gt;Pires Aurelio&lt;br&gt;
Freelance Backend Developer Go Docker Kubernetes&lt;br&gt;
Async First Leader&lt;br&gt;
Luanda Angola Remote Worldwide&lt;br&gt;
Open to Work Freelance Inclusive&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29767/my-voice-is-code-how-2-years-as-a-mute-freelance-go-developer-made-me-an-async-first-expert</guid>
<pubDate>Sun, 04 Oct 2026 21:38:43 +0000</pubDate>
</item>
<item>
<title>Deploying a Production-Ready Go Scraper to Kubernetes with Prometheus: The Complete Implementation</title>
<link>https://coderlegion.com/29677/deploying-production-ready-scraper-kubernetes-with-prometheus-complete-implementation</link>
<description>&lt;p&gt;Most Go tutorials stop at &lt;code&gt;go run main.go&lt;/code&gt;. Real production starts after that.&lt;/p&gt;
&lt;p&gt;I had a working Go Jobs Tracker with Prometheus metrics, but it only ran on my laptop. If I close the lid, production dies. That is not production.&lt;/p&gt;
&lt;p&gt;This is how I took it from localhost to a scalable, observable Kubernetes deployment with 12MB images, health checks, and zero-downtime.&lt;/p&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li&gt;The Problem: Why &lt;code&gt;go run&lt;/code&gt; is Not Production&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;No isolation&lt;/li&gt;
&lt;li&gt;No auto-restart&lt;/li&gt;
&lt;li&gt;No scaling&lt;/li&gt;
&lt;li&gt;No observability in cluster&lt;/li&gt;
&lt;li&gt;Secrets in .env&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need Docker plus Kubernetes plus Prometheus.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Production Dockerfile - Multi-Stage (12MB vs 1GB)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the Dockerfile big tech uses. Not the junior FROM golang.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;Dockerfile&lt;/code&gt; in root:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;Stage 1: Builder - we compile here
FROM golang:1.22-alpine AS builder

WORKDIR /app

Cache dependencies first - faster rebuilds
COPY go.mod go.sum ./
RUN go mod download

COPY . .

Build static binary - no CGO, no libc dependency
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
  -ldflags=&quot;-w -s&quot; \
  -o /go-jobs-tracker ./cmd/scraper

Stage 2: Final - distroless-level small
FROM alpine:3.19

WORKDIR /app

RUN apk --no-cache add ca-certificates wget

Copy only the binary
COPY --from=builder /go-jobs-tracker .

Non-root user - security best practice
RUN adduser -D appuser
USER appuser

EXPOSE 2112

K8s needs this to know if you are alive
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:2112/healthz || exit 1

CMD [&quot;./go-jobs-tracker&quot;]
Why this is production grade:
- Multi-stage results in 12MB final image
- -ldflags=&quot;-w -s&quot; strips debug symbols
- CGO_ENABLED=0 produces static binary
- Non-root user for security
- HEALTHCHECK for Docker and K8s

Build it:
docker build -t go-jobs-tracker:latest .
docker images | grep go-jobs-tracker
3. Full Observability Stack with docker-compose

One command to run everything like in production.

Create `docker-compose.yml`:
version: '3.8'
services:
  scraper:
    build: .
    ports:
            - &quot;2112:2112&quot;
    restart: unless-stopped
    environment:
            - SERPAPI_KEY=${SERPAPI_KEY}
    networks:
            - monitoring

  prometheus:
    image: prom/prometheus:v2.52.0
    ports:
            - &quot;9090:9090&quot;
    volumes:
            - ./prometheus.yml:/etc/prometheus/prometheus.yml
            - prometheus_data:/prometheus
    command:
            - '--config.file=/etc/prometheus/prometheus.yml'
            - '--storage.tsdb.path=/prometheus'
    networks:
            - monitoring

  grafana:
    image: grafana/grafana:10.4.0
    ports:
            - &quot;3000:3000&quot;
    environment:
            - GF_SECURITY_ADMIN_USER=admin
            - GF_SECURITY_ADMIN_PASSWORD=admin
            - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
            - grafana_data:/var/lib/grafana
    depends_on:
            - prometheus
    networks:
            - monitoring

volumes:
  prometheus_data:
  grafana_data:

networks:
  monitoring:
    driver: bridge
Create `prometheus.yml`:
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
    - job_name: 'go-jobs-tracker'
    static_configs:
            - targets: ['scraper:2112']
    metrics_path: /metrics
    scrape_interval: 5s
Run it:
docker-compose up --build
Your stack:
- App on 2112 slash metrics
- Prometheus on 9090
- Grafana on 3000 with admin slash admin

4. Kubernetes - Real Production

This is what separates Senior from Staff.

Create folder `k8s` and file `k8s/deployment.yaml`:
apiVersion: v1
kind: Secret
metadata:
  name: scraper-secrets
type: Opaque
stringData:
  serpapi-key: YOUR_SERPAPI_KEY_HERE

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-jobs-tracker
  labels:
    app: go-jobs-tracker
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: go-jobs-tracker
  template:
    metadata:
      labels:
        app: go-jobs-tracker
      annotations:
        prometheus.io/scrape: &quot;true&quot;
        prometheus.io/port: &quot;2112&quot;
        prometheus.io/path: &quot;/metrics&quot;
    spec:
      containers:
            - name: scraper
        image: ghcr.io/aureliopires186/go-jobs-tracker:latest
        imagePullPolicy: Always
        ports:
                - name: metrics
          containerPort: 2112
        env:
                - name: SERPAPI_KEY
          valueFrom:
            secretKeyRef:
              name: scraper-secrets
              key: serpapi-key
        resources:
          requests:
            memory: &quot;64Mi&quot;
            cpu: &quot;50m&quot;
          limits:
            memory: &quot;128Mi&quot;
            cpu: &quot;200m&quot;
        livenessProbe:
          httpGet:
            path: /healthz
            port: 2112
          initialDelaySeconds: 15
          periodSeconds: 20
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /healthz
            port: 2112
          initialDelaySeconds: 5
          periodSeconds: 10
          failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
  name: go-jobs-tracker-service
  labels:
    app: go-jobs-tracker
spec:
  selector:
    app: go-jobs-tracker
  ports:
    - name: metrics
    port: 2112
    targetPort: 2112
    protocol: TCP
  type: ClusterIP
Deploy to K8s:
kubectl apply -f k8s/deployment.yaml
kubectl get pods -w
kubectl logs -f deployment/go-jobs-tracker
kubectl port-forward svc/go-jobs-tracker-service 2112:2112
5. The Metrics Code - What Makes It Observable

Your metrics endpoint should expose this:
// metrics/metrics.go
package metrics

import (
  &quot;github.com/prometheus/client_golang/prometheus&quot;
  &quot;github.com/prometheus/client_golang/prometheus/promauto&quot;
)

var (
  JobsScraped = promauto.NewCounterVec(prometheus.CounterOpts{
    Name: &quot;jobs_scraped_total&quot;,
    Help: &quot;Total number of jobs scraped&quot;,
  }, []string{&quot;engine&quot;, &quot;status&quot;})

  ScrapeDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
    Name:    &quot;scrape_duration_seconds&quot;,
    Help:    &quot;Duration of scrape jobs&quot;,
    Buckets: prometheus.DefBuckets,
  }, []string{&quot;engine&quot;})

  ScraperErrors = promauto.NewCounterVec(prometheus.CounterOpts{
    Name: &quot;scraper_errors_total&quot;,
    Help: &quot;Total errors in scraper&quot;,
  }, []string{&quot;type&quot;})
)
And in http://main.go:
http.Handle(&quot;/metrics&quot;, promhttp.Handler())
http.HandleFunc(&quot;/healthz&quot;, func(w http.ResponseWriter, r *http.Request){
  w.WriteHeader(200)
  w.Write([]byte(&quot;ok&quot;))
})
go http.ListenAndServe(&quot;:2112&quot;, nil)
6. Result: What You Get

After this tutorial you have:

- 12MB Docker image, not 1GB
- Non-root, secure, production-ready
- docker-compose with full monitoring
- K8s with 2 replicas, zero-downtime rolling update
- Liveness and Readiness probes
- Resource limits to prevent OOM in prod
- Prometheus auto-discovery
- Secrets, not hardcoded keys

This is how real companies deploy Go in production.

Production-ready Go is not about code, it is about deployment.


•Author : Pires Aurélio 
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29677/deploying-production-ready-scraper-kubernetes-with-prometheus-complete-implementation</guid>
<pubDate>Sun, 04 Oct 2026 01:15:51 +0000</pubDate>
</item>
<item>
<title>Production-Ready Go Scraper with Prometheus - Complete Implementation Building a scraper</title>
<link>https://coderlegion.com/29672/production-ready-go-scraper-with-prometheus-complete-implementation-building-scraper</link>
<description>&lt;p&gt;Building a scraper that works is easy. Building a scraper that you can observe in production is senior work.&lt;/p&gt;
&lt;p&gt;I added Prometheus to my Go Jobs Tracker and here's the full working implementation.&lt;/p&gt;
&lt;p&gt;PART 1: METRICS DEFINITION&lt;/p&gt;
&lt;p&gt;Create file metrics/metrics.go:&lt;/p&gt;
&lt;p&gt;package metrics&lt;/p&gt;
&lt;p&gt;import (&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;github.com/prometheus/client_golang/prometheus&quot;
&quot;github.com/prometheus/client_golang/prometheus/promauto&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;)&lt;/p&gt;
&lt;p&gt;var (&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;JobsScraped = promauto.NewCounterVec(
    prometheus.CounterOpts{
        Name: &quot;jobs_scraped_total&quot;,
        Help: &quot;Total number of jobs scraped per engine and status&quot;,
    },
    []string{&quot;engine&quot;, &quot;status&quot;},
)

ScrapeDuration = promauto.NewHistogramVec(
    prometheus.HistogramOpts{
        Name: &quot;scrape_duration_seconds&quot;,
        Help: &quot;Duration of scrape jobs&quot;,
        Buckets: prometheus.DefBuckets,
    },
    []string{&quot;engine&quot;},
)

ErrorsTotal = promauto.NewCounterVec(
    prometheus.CounterOpts{
        Name: &quot;scraper_errors_total&quot;,
        Help: &quot;Total errors by type&quot;,
    },
    []string{&quot;type&quot;},
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;)&lt;/p&gt;
&lt;p&gt;PART 2: MAIN.GO INTEGRATION - 100% WORKING&lt;/p&gt;
&lt;p&gt;package main&lt;/p&gt;
&lt;p&gt;import (&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;net/http&quot;
&quot;time&quot;
&quot;github.com/prometheus/client_golang/prometheus/promhttp&quot;
&quot;github.com/Aureliopires186/go-jobs-tracker/metrics&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;)&lt;/p&gt;
&lt;p&gt;func main() {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;go func() {
    http.Handle(&quot;/metrics&quot;, promhttp.Handler())
    http.HandleFunc(&quot;/healthz&quot;, func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(200)
        w.Write([]byte(&quot;ok&quot;))
    })
    http.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(&quot;go-jobs-tracker running. check /metrics and /healthz&quot;))
    })
    http.ListenAndServe(&quot;:2112&quot;, nil)
}()

start := time.Now()

// Simulate your scraping logic here
err := ScrapeGoogleJobs()

duration := time.Since(start)

if err != nil {
    metrics.ErrorsTotal.WithLabelValues(&quot;scrape_failed&quot;).Inc()
} else {
    metrics.JobsScraped.WithLabelValues(&quot;google_jobs&quot;, &quot;success&quot;).Inc()
    metrics.ScrapeDuration.WithLabelValues(&quot;google_jobs&quot;).Observe(duration.Seconds())
}

select {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;func ScrapeGoogleJobs() error {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// your logic
return nil
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;PART 3: GO.MOD DEPENDENCY&lt;/p&gt;
&lt;p&gt;Run:&lt;br&gt;
go get github.com/prometheus/client_golang@v1.19.0&lt;br&gt;
go mod tidy&lt;/p&gt;
&lt;p&gt;PART 4: HOW TO TEST IT LOCALLY&lt;/p&gt;
&lt;p&gt;Terminal 1:&lt;br&gt;
go run main.go&lt;/p&gt;
&lt;p&gt;Terminal 2:&lt;br&gt;
curl &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:2112/metrics&quot;&gt;http://localhost:2112/metrics&lt;/a&gt;&lt;br&gt;
curl &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:2112/healthz&quot;&gt;http://localhost:2112/healthz&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You will see:&lt;br&gt;
jobs_scraped_total&lt;br&gt;
scrape_duration_seconds&lt;br&gt;
scraper_errors_total&lt;/p&gt;
&lt;p&gt;PART 5: PROMETHEUS CONFIG&lt;/p&gt;
&lt;p&gt;prometheus.yml&lt;/p&gt;
&lt;p&gt;global:&lt;br&gt;
  scrape_interval: 15s&lt;br&gt;
scrape_configs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- job_name: 'go-jobs-tracker'
static_configs:
        - targets: ['localhost:2112']
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This setup is production ready, K8s ready, and used by real companies.&lt;/p&gt;
&lt;p&gt;No more blind scraping.&lt;/p&gt;
&lt;p&gt;•Author :Pires Aurélio &lt;/p&gt;
&lt;p&gt;•Go Developer &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29672/production-ready-go-scraper-with-prometheus-complete-implementation-building-scraper</guid>
<pubDate>Sat, 03 Oct 2026 23:53:01 +0000</pubDate>
</item>
<item>
<title>How to Build an MCP Server in Golang Using the Official MCP SDK</title>
<link>https://coderlegion.com/29194/how-to-build-an-mcp-server-in-golang-using-the-official-mcp-sdk</link>
<description>&lt;p&gt;&lt;strong&gt;What is MCP?&lt;/strong&gt;&lt;br&gt;
The Model Context Protocol is an open standard (by Anthropic) that lets LLM clients (Claude, Cursor, etc.) call external tools, read resources, and get prompted context in a uniform way — without any bespoke plugin per-model. You can read more about this on Model Context Protocol Docs&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll build a basic currency conversion MCP server using Golang and the official MCP SDK. We'll explore Streamable HTTP transport for remote access, add authentication to secure our server, and finally deploy the application to Render. By the end of this guide, you'll have a solid understanding of the complete lifecycle of building and deploying an MCP server in Go.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go 1.22+&lt;/li&gt;
&lt;li&gt;Claude Desktop installed (for local testing)&lt;/li&gt;
&lt;li&gt;Basic familiarity with Go modules and &lt;code&gt;net/http&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MCP Transports&lt;/strong&gt;&lt;br&gt;
MCP supports &lt;a rel=&quot;nofollow&quot; href=&quot;https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio&quot;&gt;stdio&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http&quot;&gt;Streamable HTTP&lt;/a&gt; transport mechanisms that define how an MCP client and server communicate with each other. In this tutorial, we'll work with Streamable HTTP.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Streamable HTTP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Streamable HTTP allows MCP clients and servers to communicate over standard HTTP connections. The server runs independently and can be hosted remotely, making it accessible from different environments. Unlike stdio, Streamable HTTP allows the MCP server to live anywhere (local machine, Docker container, cloud VM, Kubernetes, etc.), making it much easier to share across multiple users.&lt;/p&gt;
&lt;p&gt;Streamable HTTP is a better choice when you want to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deploy your MCP server to the cloud&lt;/li&gt;
&lt;li&gt;Allow multiple clients to access the same server&lt;/li&gt;
&lt;li&gt;Add features such as authentication and centralized monitoring&lt;/li&gt;
&lt;li&gt;Manage your MCP server independently from the client application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since we'll eventually deploy our currency conversion server to Render, Streamable HTTP will play an important role in moving from a local setup to a production-ready deployment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setting up Golang project&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can refer following &lt;a rel=&quot;nofollow&quot; href=&quot;https://go.dev/doc/tutorial/getting-started&quot;&gt;blog&lt;/a&gt; for setting up a basic golang project. We will use the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/modelcontextprotocol/go-sdk&quot;&gt;go-sdk&lt;/a&gt; for creating mcp server which is maintend by the mcp team.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;go mod init example/mcp-server
go get -u github.com/gorilla/mux
go get https://github.com/modelcontextprotocol/go-sdk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Basic MCP Server&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To start, we'll set up a very basic mcp server with just 1 tool. Before building the currency conversion tool, let's start with a minimal Greeting tool so we can understand the basic structure of an MCP server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.go
package main

import (
...
)

func main() {
logger := slog.New(slog.NewTextHandler(os.Stdout, &amp;amp;slog.HandlerOptions{Level: slog.LevelInfo}))

err := godotenv.Load()
if err != nil {
logger.Error(&quot;error loading .env file&quot;, &quot;err&quot;, err)
}

mcpServer := server.NewMcpServer(logger)
mcpServer.AddReceivingMiddleware(security.LoggingMiddleware())

mcpHandler := mcp.NewStreamableHTTPHandler(func(request *http.Request) *mcp.Server {
return mcpServer
}, &amp;amp;mcp.StreamableHTTPOptions{
Logger:                     logger,
JSONResponse:               true,
Stateless:                  true,
DisableLocalhostProtection: true,
})

router := server.NewHttpServer(mcpServer, mcpHandler)

mcp.AddTool(mcpServer, &amp;amp;mcp.Tool{
Title:       &quot;Greeting&quot;,
Name:        &quot;greeting&quot;,
Description: &quot;Greet a person by name&quot;,
}, SayHi)

port := os.Getenv(&quot;PORT&quot;)
if port == &quot;&quot; {
port = &quot;3000&quot;
}

server := &amp;amp;http.Server{
Addr:              &quot;:&quot; + port,
Handler:           router,
ReadTimeout:       5 * time.Minute,
ReadHeaderTimeout: 10 * time.Second,
WriteTimeout:      5 * time.Minute,
IdleTimeout:       5 * time.Minute,
MaxHeaderBytes:    1 &amp;lt;&amp;lt; 20,
}

go func() {
logger.Info(&quot;starting server&quot;, &quot;addr&quot;, port)

if err := server.ListenAndServe(); err != nil &amp;amp;&amp;amp;
err != http.ErrServerClosed {

logger.Error(&quot;server failed&quot;, &quot;err&quot;, err)
os.Exit(1)
}
}()

ctx, stop := signal.NotifyContext(
context.Background(),
syscall.SIGINT,
syscall.SIGTERM,
)
defer stop()

&amp;lt;-ctx.Done()

logger.Info(&quot;shutdown initiated&quot;)

shutdownCtx, cancel := context.WithTimeout(
context.Background(),
30*time.Second,
)
defer cancel()

if err := server.Shutdown(shutdownCtx); err != nil {
logger.Error(&quot;shutdown failed&quot;, &quot;err&quot;, err)
}

logger.Info(&quot;shutdown complete&quot;)
}

type GreetingInput struct {
Name string `json:&quot;name&quot; jsonschema:&quot;the name of the person to greet&quot;`
}

type GreetingOutput struct {
Greeting string `json:&quot;greeting&quot; jsonschema:&quot;the greeting to tell to the user&quot;`
}

func SayHi(ctx context.Context, req *mcp.CallToolRequest, input GreetingInput) (
*mcp.CallToolResult, any, error) {
return &amp;amp;mcp.CallToolResult{
Content: []mcp.Content{
&amp;amp;mcp.TextContent{Text: &quot;Hi &quot; + input.Name},
},
}, GreetingOutput{Greeting: &quot;Hi &quot; + input.Name}, nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// server.go
package server

import (
...
)

func NewMcpServer(logger *slog.Logger) *mcp.Server {
 implementation := &amp;amp;mcp.Implementation{
  Title:   &quot;MCP Tutorial For Greeting&quot;,
  Name:    &quot;greeting-mcp&quot;,
  Version: &quot;1.0.0&quot;,
 }

 serverOptions := &amp;amp;mcp.ServerOptions{
  Logger:    logger,
  KeepAlive: 5 * time.Minute,
 }

 return mcp.NewServer(implementation, serverOptions)
}

func NewHttpServer(server *mcp.Server, mcpHandler *mcp.StreamableHTTPHandler) *mux.Router {
router := mux.NewRouter()

router.HandleFunc(&quot;/health&quot;, func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(&quot;ok&quot;))
}).Methods(http.MethodGet)

// Optional root route to avoid confusion when testing in browser.
router.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(&quot;MCP server is running. Use /mcp endpoint.&quot;))
}).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)

router.HandleFunc(&quot;/mcp&quot;, func(w http.ResponseWriter, req *http.Request) {
fmt.Printf(
&quot;method=%s path=%s auth=%q\n&quot;,
req.Method,
req.URL.Path,
req.Header.Get(&quot;Authorization&quot;),
)
mcpHandler.ServeHTTP(w, req)
}).Methods(http.MethodPost, http.MethodGet, http.MethodOptions)

return router
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here you can see for the http server we expose /mcp endpoint. This endpoint is not a REST endpoint that your application calls directly. It speaks the MCP protocol over HTTP and is intended to be consumed by MCP clients.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding MCP Tools&lt;/strong&gt;&lt;br&gt;
I like to think of MCP tools as API endpoint which an AI agent or client understands and sends request to based on its analysis of the prompt user has typed. So it becomes very important how we name our MCP tool. You can read more on it &lt;a rel=&quot;nofollow&quot; href=&quot;https://modelcontextprotocol.io/specification/2025-11-25/server/tools&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now lets build a real life example tool which could do currency conversion. For currency conversion I am using this free API which is provided by &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fawazahmed0&quot;&gt;fawazahmed0&lt;/a&gt; in the following github &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fawazahmed0/exchange-api&quot;&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package tool

import (
...
)

// ConvertCurrencyInput is the input for the ConvertCurrencyTool
type ConvertCurrencyInput struct {
FromCurrency string  `json:&quot;from_currency&quot; jsonschema:&quot;currency codes are ISO 4217 format, e.g. USD, EUR, GBP, etc. the currency to convert from&quot;`
ToCurrency   string  `json:&quot;to_currency&quot; jsonschema:&quot;currency codes are ISO 4217 format, e.g. USD, EUR, GBP, etc. the currency to convert to&quot;`
Amount       float64 `json:&quot;amount&quot; jsonschema:&quot;the amount to convert&quot;`
}

// ConvertCurrencyOutput is the output for the ConvertCurrencyTool
type ConvertCurrencyOutput struct {
ConvertedAmount float64 `json:&quot;converted_amount&quot; jsonschema:&quot;the converted amount&quot;`
}

type CurrencyResponse struct {
Date string                        `json:&quot;date&quot; jsonschema:&quot;the date of the currency rates&quot;`
Data map[string]map[string]float64 `json:&quot;-&quot; jsonschema:&quot;the currency rates&quot;`
}

const BASE_CURRENCY_API_URL = `https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies`

// ConvertCurrencyTool converts a currency to another currency
func ConvertCurrencyTool(ctx context.Context, req *mcp.CallToolRequest, input ConvertCurrencyInput) (*mcp.CallToolResult, any, error) {
input.FromCurrency = strings.ToLower(input.FromCurrency)
input.ToCurrency = strings.ToLower(input.ToCurrency)
fmt.Println(&quot;inside convert currency tool input&quot;, input)

apiURL := fmt.Sprintf(&quot;%s/%s.json&quot;, BASE_CURRENCY_API_URL, input.FromCurrency)
resp, err := http.Get(apiURL)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}

response, err := UnmarshalJSON(body)
if err != nil {
return nil, nil, err
}

if _, ok := response.Data[input.FromCurrency]; !ok {
return nil, nil, fmt.Errorf(&quot;currency %s not found&quot;, input.FromCurrency)
}

if _, ok := response.Data[input.FromCurrency][input.ToCurrency]; !ok {
return nil, nil, fmt.Errorf(&quot;currency %s not found&quot;, input.ToCurrency)
}

convertedAmount := response.Data[input.FromCurrency][input.ToCurrency] * input.Amount
return &amp;amp;mcp.CallToolResult{
Content: []mcp.Content{
&amp;amp;mcp.TextContent{
Text: fmt.Sprintf(&quot;Converted amount: %f&quot;, convertedAmount),
},
},
}, nil, nil
}

func UnmarshalJSON(data []byte) (*CurrencyResponse, error) {
var r CurrencyResponse
var raw map[string]json.RawMessage

if err := json.Unmarshal(data, &amp;amp;raw); err != nil {
return nil, err
}

// Extract date
if v, ok := raw[&quot;date&quot;]; ok {
if err := json.Unmarshal(v, &amp;amp;r.Date); err != nil {
return nil, err
}
delete(raw, &quot;date&quot;)
}

// Remaining keys are currency codes
r.Data = make(map[string]map[string]float64)

for currency, v := range raw {
var rates map[string]float64
if err := json.Unmarshal(v, &amp;amp;rates); err != nil {
return nil, err
}
r.Data[currency] = rates
}

return &amp;amp;r, nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now to add the mcp server we do the following&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.go

....

func main() {
...

mcp.AddTool(mcpServer, &amp;amp;mcp.Tool{
Title:       &quot;Currency Conversion&quot;,
Name:        &quot;currency-conversion&quot;,
Description: &quot;Convert an amount from one currency to another. Requires from_currency, to_currency (ISO 4217 codes), and amount. Uses latest exchange rates from an external API.&quot;,
}, ConvertCurrencyTool)
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now lets understand what is happening here and how it works. First we register this tool using mcp.AddTool. Here Title is just human readable text and does not provide any help or context to the AI agent. Name and Description are 2 important things when registering a tool as this 2 piece of information helps the AI agent to make the decision on which MCP server should be used.&lt;/p&gt;
&lt;p&gt;Now lets go to the ConvertCurrencyTool. This function takes 3 arguments which is the method signature and the 3rd parameter which is input is what we will focus on. It is like the request body that we expect to receive from the AI agent. In my case I am expecting fromCurrency, toCurrency and amount. For all the fields I have jsonschema specified which, like the tool description, helps the AI understand what each field represents. The SDK converts these tags into the MCP Tool schema that the model receives during tool discovery&lt;/p&gt;
&lt;p&gt;The ConvertCurrencyTool makes an API call to fetch the latest exchange rates, performs the currency conversion, and returns the result to the MCP client.&lt;/p&gt;
&lt;p&gt;There are a few more things which can be done during mcp server creation. Another useful server configuration is Instructions. These provide additional context to the AI model and can improve tool selection and overall responses.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func NewMcpServer(logger *slog.Logger) *mcp.Server {
implementation := &amp;amp;mcp.Implementation{
Title:   &quot;MCP Tutorial For Currency Conversion&quot;,
Name:    &quot;currency-conversion-mcp&quot;,
Version: &quot;1.0.0&quot;,
}

serverOptions := &amp;amp;mcp.ServerOptions{
Logger:       logger,
KeepAlive:    5 * time.Minute,
Instructions: `You are a helpful assistant that can convert currencies. You can convert currencies using the currency-conversion tool.`,
        Capabilities: &amp;amp;mcp.ServerCapabilities{
Tools: &amp;amp;mcp.ToolCapabilities{},
},
}

return mcp.NewServer(implementation, serverOptions)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is also one more important setting that we should we about when configuring the streamable http transport&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mcpHandler := mcp.NewStreamableHTTPHandler(func(request *http.Request) *mcp.Server {
return mcpServer
}, &amp;amp;mcp.StreamableHTTPOptions{
Logger:                     logger,
JSONResponse:               true,
Stateless:                  true,
DisableLocalhostProtection: true,
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the options part is important to understand. &lt;code&gt;JSONResponse&lt;/code&gt; is basic tells if the response type is json or not. &lt;code&gt;Stateless&lt;/code&gt; - this is one of the key option to understand. &lt;code&gt;Stateless&lt;/code&gt; mode tells the server not to rely on MCP sessions. Every request should contain all information required to process it, making deployment behind load balancers and horizontal scaling much easier.. This is very well explained in Anthropic courses named &lt;a rel=&quot;nofollow&quot; href=&quot;https://anthropic.skilljar.com/model-context-protocol-advanced-topics&quot;&gt;Model Context Protocol: Advanced Topics&lt;/a&gt;. I ideally like to keep it to &lt;code&gt;true&lt;/code&gt; as a REST API is always stateless and we use Authorization headers to validate the user so we can follow the same setup here too. The last option which is &lt;code&gt;DisableLocalhostProtection&lt;/code&gt; again an important flag to be set to &lt;code&gt;true&lt;/code&gt; when deploying to production. You can read more on this &lt;a rel=&quot;nofollow&quot; href=&quot;https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#local-mcp-server-compromise&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To find more examples on how to create &lt;a rel=&quot;nofollow&quot; href=&quot;https://modelcontextprotocol.io/specification/2025-11-25/server/prompts&quot;&gt;Prompt&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://modelcontextprotocol.io/specification/2025-11-25/server/resources&quot;&gt;Resource&lt;/a&gt; you can always refer following &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/modelcontextprotocol/go-sdk/tree/main/examples&quot;&gt;docs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How registration works&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When we call:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mcp.AddTool(mcpServer, &amp;amp;mcp.Tool{
    Title:       &quot;Currency Conversion&quot;,
    Name:        &quot;currency-conversion&quot;,
    Description: &quot;Convert an amount from one currency to another...&quot;,
}, ConvertCurrencyTool)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;we are not creating a new HTTP endpoint like we would in a typical REST application. Instead, we're registering the tool with the MCP server.&lt;/p&gt;
&lt;p&gt;From this point on, the lifecycle looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The tool is registered: The MCP server stores the tool's metadata, including its name, description, and input schema.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The MCP client connects: When a client such as Claude Desktop connects to the server, it asks the server which tools are available.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The server advertises its tools: The server responds with the list of registered tools, including their descriptions and JSON schemas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The LLM decides whether to use a tool: When the user asks a question like:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;How much is 28 USD in INR?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;the model analyzes the request and determines that the &lt;code&gt;currency-conversion&lt;/code&gt; tool is appropriate.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The client invokes the tool: The client sends a tool invocation request to the MCP server containing the required input&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;from_currency&quot;: &quot;USD&quot;,
  &quot;to_currency&quot;: &quot;INR&quot;,
  &quot;amount&quot;: 28
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your Go function executes: The Go SDK automatically deserializes the request into a &lt;code&gt;ConvertCurrencyInput&lt;/code&gt; struct and calls: &lt;code&gt;ConvertCurrencyTool(ctx, req, input)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The result is returned: Your function performs the currency conversion, returns the result, and the SDK sends it back to the client. Claude then uses that result to generate its final response to the user.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
In this tutorial, we built a complete MCP server in Go using the official Go SDK. We started with a simple greeting tool, expanded it into a practical currency conversion service, exposed it using Streamable HTTP, discussed important server configuration options.&lt;/p&gt;
&lt;p&gt;In the next step, you can deploy the same server to Render and make it accessible remotely without changing the MCP client configuration.&lt;/p&gt;
&lt;p&gt;The complete source code for this tutorial is available on GitHub:&lt;/p&gt;
&lt;p&gt;GitHub Repository: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/shaileshhb/mcp-tutorial&quot;&gt;https://github.com/shaileshhb/mcp-tutorial&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29194/how-to-build-an-mcp-server-in-golang-using-the-official-mcp-sdk</guid>
<pubDate>Wed, 30 Sep 2026 06:51:40 +0000</pubDate>
</item>
<item>
<title>ForgeZero: What Happens When Someone Finally Stops Forgiving Your Build System</title>
<link>https://coderlegion.com/27194/forgezero-what-happens-when-someone-finally-stops-forgiving-your-build-system</link>
<description>&lt;p&gt;In 1976, a Bell Labs researcher named Stuart Feldman was annoyed at a coworker. The story, as it's been retold for half a century now, goes something like this: a colleague kept shipping broken binaries because he'd edit a source file and forget to recompile the parts of the program that depended on it. Feldman's fix wasn't a lecture. It was &lt;code&gt;make&lt;/code&gt; — a tool whose entire reason for existing was to answer one question correctly, automatically, every single time: &lt;em&gt;what, exactly, needs to be rebuilt right now, and what doesn't?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That question turned out to be one of the hardest, most quietly load-bearing problems in all of software engineering. Fifty years later, we're still answering it badly, most of the time, and most engineers have simply stopped noticing.&lt;/p&gt;
&lt;p&gt;Make aged the way anything does when it becomes foundational infrastructure nobody's allowed to break: badly, slowly, and in public. Its dependency model — timestamps, phony targets, recursive invocation — leaked complexity into every project that grew past a few dozen files, and autotools showed up to paper over that with an entire generation-of-generators architecture that, to this day, remains one of the most feared corners of the open-source toolchain. CMake arrived at the turn of the millennium out of Kitware's need to build ITK and VTK across radically different platforms without hand-maintaining a Makefile per OS — a meta-build system that generates &lt;em&gt;other&lt;/em&gt; build systems, which is either a brilliant abstraction or a confession that the underlying problem was never actually solved, depending on which decade of CMake you personally suffered through.&lt;/p&gt;
&lt;p&gt;And then there's Ninja. Evan Martin built it at Google specifically because Chromium's build had grown so large that Make's dependency-graph evaluation had become a measurable, painful part of every engineer's day — and his answer was almost insultingly minimal: a build file format so sparse that the project's own documentation tells you, directly, that you are not meant to write Ninja files by hand. Ninja doesn't try to be pleasant. It tries to be a fast, dumb, extremely well-behaved execution target for something smarter sitting above it, generating its files for it. That single design decision — &lt;em&gt;radical minimalism as a feature, not a limitation&lt;/em&gt; — is one of the most underrated ideas in build tooling history, and it's the closest ancestor to what comes next in this story.&lt;/p&gt;
&lt;p&gt;Because none of Make, Autotools, CMake, or Ninja were ever built for the workload I actually live in: assembly-heavy code, small C translation units by the hundreds, embedded and bare-metal targets, mixed-language repositories where the &lt;em&gt;coordination&lt;/em&gt; between compiler invocations costs more than the invocations themselves. I started building ForgeZero to solve that gap about a year and a half ago, quietly, on my own time, the way most infrastructure worth trusting actually gets built. It went public in May 2026. This is the deep engineering account of what it actually is.&lt;/p&gt;
&lt;h3 id=&quot;the-thesis-stated-plainly&quot;&gt;The Thesis, Stated Plainly&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;A build system should handle planning, invalidation, and routing. It should not try to reinvent the compiler.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That single sentence is the entire architectural spine of ForgeZero, and everything below is just the consequence of taking it seriously. ForgeZero is not GCC, Clang, Zig, NASM, FASM, GAS, or LD, and it has no interest in becoming any of them. It's the thin, transparent coordination layer that decides &lt;em&gt;what&lt;/em&gt; those tools need to do, &lt;em&gt;when&lt;/em&gt;, and &lt;em&gt;whether they need to do it again at all&lt;/em&gt; — and then gets entirely out of the way.&lt;/p&gt;
&lt;p&gt;The value proposition compresses into one relationship:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Build Throughput ≈ Useful Compiler Work
                    ─────────────────────────────────────────
                    Discovery + Dispatch + Repeated Work + Invalidation Cost
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every serious build-system optimization in ForgeZero attacks the denominator. None of it touches the numerator, because the numerator — actual code generation correctness — belongs to GCC, Clang, Zig, and the assemblers, permanently, and ForgeZero has no ambition to argue with that division of labor.&lt;/p&gt;
&lt;h3 id=&quot;the-pipeline-as-a-chain-of-owned-transformations&quot;&gt;The Pipeline as a Chain of Owned Transformations&lt;/h3&gt;
&lt;p&gt;Filesystem state becomes a source set. The source set becomes a dependency closure. That closure becomes a preprocessing state, then a cache identity, then a set of compiler and assembler actions, then an object graph, then a linker invocation, then a verified artifact.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;filesystem state
    -&amp;gt; source set
    -&amp;gt; dependency closure
    -&amp;gt; preprocessing state
    -&amp;gt; cache identity
    -&amp;gt; compiler/assembler actions
    -&amp;gt; object graph
    -&amp;gt; linker invocation
    -&amp;gt; verified artifact
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What makes this more than a diagram is that &lt;strong&gt;each arrow is an owned boundary with its own failure class.&lt;/strong&gt; A discovery failure is not a cache failure. A cache failure is not a linker failure. When something goes wrong, an engineer using ForgeZero is diagnosing one specific, narrow transformation — not reverse-engineering an opaque shell invocation that happened to fail somewhere in the middle of a black box.&lt;/p&gt;
&lt;p&gt;Four properties fall directly out of respecting that boundary discipline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Observability&lt;/strong&gt; — the selected backend, target, flags, object paths, cache decisions, and exact linker command are all visible, not inferred.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Controllability&lt;/strong&gt; — ABI, compiler, linker, optimization level, sysroot, and output format are never hidden behind a &quot;just trust the abstraction&quot; layer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amortization&lt;/strong&gt; — unchanged units don't get recompiled, and metadata checks run &lt;em&gt;before&lt;/em&gt; the expensive stages, not after.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composability&lt;/strong&gt; — assembly, C-family code, Zig, Gloria, custom preprocessing, and security tooling all live inside one coordinated pipeline instead of five disconnected ones duct-taped together in CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;who-this-was-actually-built-for&quot;&gt;Who This Was Actually Built For&lt;/h3&gt;
&lt;p&gt;This isn't a general-purpose build tool wearing a performance costume. It has specific, named audiences, and it's honest about who they are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Assembly engineers&lt;/strong&gt; — boot code, runtime glue, kernel-adjacent components, flat binaries, hand-managed ELF/PE/Mach-O pipelines, where NASM/FASM/GAS selection and the resulting object format can't be papered over.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C/C++/Objective-C engineers&lt;/strong&gt; who need direct, unfiltered access to GCC, Clang, or Zig — their own &lt;code&gt;-I&lt;/code&gt;, &lt;code&gt;-D&lt;/code&gt;, &lt;code&gt;-O&lt;/code&gt;, sanitizer, and linker flags surviving intact into the actual command line.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedded and cross-compilation engineers&lt;/strong&gt; targeting ARM, RISC-V, and bare-metal ABIs, where the target triple has to participate directly in backend selection, not get normalized away.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release and supply-chain engineers&lt;/strong&gt; who need reproducible builds, source-tree verification, SBOM generation, SAST-style auditing, and filesystem isolation — as genuinely separate concerns from the build cache, never conflated with it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build infrastructure authors&lt;/strong&gt; looking to replace a Make/Ninja/shell-script Frankenstein with one coordinator that has a predictable cache model and room for hooks, watchers, and compile databases.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cache-identity-the-part-everyone-gets-wrong&quot;&gt;Cache Identity: The Part Everyone Gets Wrong&lt;/h3&gt;
&lt;p&gt;Here's the mistake that shows up in almost every homegrown build cache: treating &quot;the source bytes didn't change&quot; as sufficient justification for reusing a compiled object. It isn't, and pretending it is produces silent, maddening correctness bugs. The same source file produces a &lt;em&gt;different&lt;/em&gt; correct object under a different compiler, a different compiler version, a different target triple, a different sysroot, different optimization flags, different sanitizer policy, or different linker configuration.&lt;/p&gt;
&lt;p&gt;ForgeZero's action-level cache identity is built from the sorted inputs, their digests, the action name, and the full environment and toolchain parameters — not the source hash alone. Two layers do the heavy lifting:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metadata-first invalidation&lt;/strong&gt; checks file size and modification time before touching the file's bytes at all. If both match a cache entry, the expensive digest step is skipped entirely. Only a mismatch triggers a full keyed digest computation. This is explicitly a performance optimization, not a cryptographic guarantee — and ForgeZero doesn't pretend otherwise. Security-sensitive verification lives in its own, separately-specified &lt;code&gt;verify&lt;/code&gt;/SBOM contract, deliberately kept apart from the invalidation fast path.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The disk hash cache format, &lt;code&gt;FZHC3&lt;/code&gt;,&lt;/strong&gt; stores path length, metadata, a context fingerprint, and a 32-byte digest, written via a temp-file-then-rename sequence specifically to avoid leaving a half-written cache entry behind on a crash. Small detail. The kind of detail that separates a cache you can trust from one you quietly stop trusting after the third mysterious corruption.&lt;/p&gt;
&lt;h3 id=&quot;two-hashes-one-honest-boundary&quot;&gt;Two Hashes, One Honest Boundary&lt;/h3&gt;
&lt;p&gt;This is where ForgeZero's engineering has genuinely moved since it first went public, and it's worth explaining precisely, because the project draws a hard, deliberate line between two hashing systems that solve &lt;em&gt;related but different&lt;/em&gt; problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BoomBoom Hash&lt;/strong&gt; is the original incremental-invalidation engine, built as a specialized layer over keyed BLAKE3. Build context — compiler identity, version, target triple, ordered flags — gets domain-separated and hashed into a derived key first, so the same source bytes produce different digests under different build contexts without any cross-contamination between cache domains. Source files are broken into logical chunks — directive regions, brace-delimited blocks, top-level statements, residual spans — each independently digested and placed as a leaf in a flat, heap-layout Merkle tree. Change one chunk, and only the path from that leaf to the root gets recomputed: &lt;code&gt;O(log n)&lt;/code&gt; instead of a full &lt;code&gt;O(n)&lt;/code&gt; re-aggregation. Past four changed chunks, a persistent worker pool picks up jobs through atomic fetch-and-add over a flat array, each worker holding its own independent keyed BLAKE3 state, with zero shared mutable state at the hasher level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BB64&lt;/strong&gt; is newer, and it exists precisely &lt;em&gt;because&lt;/em&gt; BoomBoom Hash, built on a general-purpose cryptographic primitive, is doing more work than some invalidation paths actually need. BB64 is a purpose-built, non-cryptographic 64-bit hash with a hand-written AVX2 Plan 9 assembly hot path, a scalar fallback verified for bit-exact equivalence against the SIMD path, explicit tail handling for inputs of arbitrary length, and SIMD-accelerated byte comparison and delimiter scanning. On an AMD Ryzen 7 PRO 4750U, it measured roughly &lt;strong&gt;16.87 GB/s at 0 B/op and 0 allocs/op&lt;/strong&gt; — against roughly 0.42 GB/s for FNV-1a and roughly 1.71 GB/s for BLAKE3 on the same benchmark input. That's about &lt;strong&gt;40x FNV-1a and nearly 10x BLAKE3&lt;/strong&gt;, for a task — PCH cache-key generation, keyed off header content, compiler, target, flags, and positions — that never needed cryptographic guarantees in the first place.&lt;/p&gt;
&lt;p&gt;And here's the part that should matter most to anyone evaluating this as infrastructure, not just admiring the throughput number: &lt;strong&gt;BB64 is explicitly, deliberately never used for security-sensitive file integrity, package verification, or any cryptographic surface.&lt;/strong&gt; That boundary isn't an oversight waiting to be &quot;optimized away&quot; later. It's the whole point. A fast non-cryptographic hash and a keyed cryptographic hash are different tools solving different problems, and collapsing that distinction to save one dependency is exactly the kind of shortcut that turns into a CVE two years later. ForgeZero doesn't take it.&lt;/p&gt;
&lt;h3 id=&quot;below-the-go-standard-library-the-discovery-layer&quot;&gt;Below the Go Standard Library: The Discovery Layer&lt;/h3&gt;
&lt;p&gt;This is the part that will make a systems engineer sit up, because it's the difference between &quot;a build tool written in Go&quot; and &quot;a build tool written by someone who's actually spent time fighting &lt;code&gt;syscall&lt;/code&gt; on Linux.&lt;/p&gt;
&lt;p&gt;Source discovery on Linux bypasses the convenient, portable &lt;code&gt;filepath.WalkDir&lt;/code&gt; entirely in favor of &lt;strong&gt;raw &lt;code&gt;getdents64&lt;/code&gt; traversal via &lt;code&gt;unix.Getdents&lt;/code&gt;&lt;/strong&gt;, paired with &lt;strong&gt;&lt;code&gt;openat&lt;/code&gt; using &lt;code&gt;O_NOFOLLOW&lt;/code&gt;&lt;/strong&gt; to keep directory traversal from silently following a symlink somewhere it shouldn't. Metadata collection goes through &lt;strong&gt;Linux &lt;code&gt;statx&lt;/code&gt;&lt;/strong&gt; for type, inode, size, and modification time in a single call, falling back to &lt;code&gt;Fstatat&lt;/code&gt; on kernels where &lt;code&gt;statx&lt;/code&gt; isn't reliably available. Directory reads retry on &lt;code&gt;EINTR&lt;/code&gt; instead of surfacing a spurious failure, malformed dirents get validated rather than trusted, and path ordering is made deterministic so two runs on the same tree produce the same discovery order — a small property that matters enormously the first time you're trying to reproduce a flaky build. Non-Linux platforms fall back cleanly to &lt;code&gt;filepath.WalkDir&lt;/code&gt;, because the portable API contract stays identical even when the backend implementation doesn't.&lt;/p&gt;
&lt;p&gt;The payoff isn't hypothetical: manual dirent parsing measurably cut Linux discovery allocations on a 1,024-file benchmark from roughly &lt;strong&gt;3,182 down to 2,088 allocations per operation&lt;/strong&gt; — about a third fewer allocations, on a code path that runs at the very start of every single build.&lt;/p&gt;
&lt;h3 id=&quot;io-uring-used-honestly&quot;&gt;io_uring, Used Honestly&lt;/h3&gt;
&lt;p&gt;ForgeZero's io_uring integration batches file reads into a single submission cycle instead of one syscall per file, drains completion queue entries fully with explicit user-data bounds checking, handles zero-length files without generating an invalid empty READ request, and supports &lt;code&gt;IORING_OP_STATX&lt;/code&gt; for metadata collection through the same ring. It enables &lt;code&gt;IORING_SETUP_COOP_TASKRUN&lt;/code&gt; where the kernel supports it, with a clean setup fallback for older kernels that don't.&lt;/p&gt;
&lt;p&gt;And then there's the detail that actually tells you how this team thinks: &lt;strong&gt;&lt;code&gt;IORING_OP_GETDENTS&lt;/code&gt; was never wired up, because the target kernel ABI doesn't expose a usable io_uring GETDENTS opcode&lt;/strong&gt; — so rather than fabricate support for an operation that doesn't reliably exist, directory listing stays on the syscall path where it actually works. That's a small, unglamorous decision. It's also exactly the kind of decision that separates infrastructure you can trust from infrastructure that looks impressive in a README until someone runs it on the wrong kernel version.&lt;/p&gt;
&lt;h3 id=&quot;linking-scheduling-and-the-false-sharing-detail-most-projects-never-bother-with&quot;&gt;Linking, Scheduling, and the False-Sharing Detail Most Projects Never Bother With&lt;/h3&gt;
&lt;p&gt;Symbol cache decoding is strict — malformed records are rejected outright rather than partially trusted, triggering a fresh extraction through &lt;code&gt;nm&lt;/code&gt;, &lt;code&gt;objdump&lt;/code&gt;, or &lt;code&gt;readelf&lt;/code&gt; instead of silently propagating a corrupted cache entry into a link step. Symbol results move through &lt;strong&gt;indexed, preallocated result slots&lt;/strong&gt; instead of a per-object queue payload, cutting allocation churn on the hot link path. The scheduler supports &lt;strong&gt;idempotent shutdown&lt;/strong&gt; and explicit stopped-state handling, so a second shutdown call doesn't do anything surprising.&lt;/p&gt;
&lt;p&gt;And then there's this: &lt;strong&gt;worker and scheduler queue producer/consumer counters are deliberately separated across 64-byte cache lines&lt;/strong&gt;, specifically to avoid false sharing — the scenario where two unrelated atomic counters happen to land on the same CPU cache line and start silently fighting each other for cache coherency traffic under concurrent load, tanking throughput for no reason visible anywhere in the code. This is genuinely advanced systems-engineering territory, the kind of optimization that only shows up in codebases that have actually been profiled under real concurrent load rather than just &quot;made to compile and pass CI.&quot;&lt;/p&gt;
&lt;h3 id=&quot;gloria-the-language-that-refuses-to-hide-anything&quot;&gt;Gloria: The Language That Refuses to Hide Anything&lt;/h3&gt;
&lt;p&gt;Sitting inside ForgeZero is &lt;strong&gt;Gloria&lt;/strong&gt;, an experimental language that isn't trying to compete with C, Rust, or a full assembler frontend. It occupies a specific, narrow niche: a minimal language for producing a small, fully-understood fragment of machine code in contexts where a libc, a runtime, and a general-purpose compiler pipeline would be overkill.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source -&amp;gt; lexer -&amp;gt; tokens -&amp;gt; codegen -&amp;gt; machine bytes -&amp;gt; relocation -&amp;gt; raw binary
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's no mandatory intermediate object file, no libc startup sequence, no standard linker script. The emitter writes bytes directly, and ForgeZero writes them out as a raw binary. Functions land in a contiguous output buffer; calls are written first with relocation placeholders, and once the function table is complete, relative displacements get patched directly into the buffer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;disp32 = offset(target) - (offset(call) + 5)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the entire mechanism that lets Gloria skip a full object-file linker for small raw images. Compile this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gloria&quot;&gt;fn main() {
    let a = 10;
    let b = 20;

    if b &amp;gt; a {
        print(&quot;hello world lol&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and disassembling the resulting &lt;code&gt;gloria.bin&lt;/code&gt; shows exactly what you'd hope for from a language that refuses to hide anything: an entry jump and stack-frame prologue, 64-bit immediate loads materializing the literals, a &lt;code&gt;cmp&lt;/code&gt;/&lt;code&gt;jle&lt;/code&gt; pair lowering the comparison, and — because the observed output loop writes directly to &lt;code&gt;0xb8000&lt;/code&gt;, the classic x86 VGA text-memory address — a byte-by-byte character-and-attribute write loop instead of a hidden &lt;code&gt;printf&lt;/code&gt; call. Every single opcode in that disassembly traces back to a specific, readable decision the emitter made. That transparency, not competing with production languages, is the entire point of Gloria existing at all.&lt;/p&gt;
&lt;h3 id=&quot;security-as-a-neighbor-not-a-costume&quot;&gt;Security as a Neighbor, Not a Costume&lt;/h3&gt;
&lt;p&gt;ForgeZero keeps its security surfaces &lt;strong&gt;deliberately separate&lt;/strong&gt; from its performance surfaces, because conflating &quot;this build was fast&quot; with &quot;this build can be trusted&quot; is exactly how supply-chain incidents happen. Path validation constrains CLI paths, symlink boundaries, and FZP include resolution to explicitly allowed roots. A dedicated seal/integrity subsystem tracks trust state for executables and toolchains — a fundamentally different question from &quot;do I need to recompile this file,&quot; and the codebase treats it as one. SBOM generation produces a CycloneDX-style component inventory with hashes; a separate audit subsystem runs SAST-style checks for secrets, vendor-specific risk patterns, and configuration violations; manifest-based source-tree verification is kept algorithmically stable specifically so release verification stays interoperable across versions instead of silently drifting.&lt;/p&gt;
&lt;p&gt;Cache hits are fast. Cache hits are not proof of integrity. ForgeZero's architecture never lets those two facts get confused with each other.&lt;/p&gt;
&lt;h3 id=&quot;the-redis-case-study-read-correctly&quot;&gt;The Redis Case Study, Read Correctly&lt;/h3&gt;
&lt;p&gt;The number worth reporting is this one:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fz -p performance -dir redis/src -mode c -out redis-server -j 16

user       27.66 s
system     11.68 s
CPU        1155%
elapsed     3.404 s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it precisely, because precision is the entire point of this project. &lt;strong&gt;1155% CPU utilization&lt;/strong&gt; means the work was genuinely spread across multiple hardware threads, not disguised behind a fast single-thread path. &lt;strong&gt;3.404 seconds elapsed&lt;/strong&gt; against nearly 40 seconds of combined CPU time shows the bottleneck sitting in parallel compile-and-link work, not in sequential orchestration overhead — which is exactly where you want a build's time to be spent. The dependency closure for that run wasn't trivial: &lt;code&gt;fpconv&lt;/code&gt;, &lt;code&gt;hdr_histogram&lt;/code&gt;, &lt;code&gt;hiredis&lt;/code&gt;, &lt;code&gt;libxxhash&lt;/code&gt;, &lt;code&gt;linenoise&lt;/code&gt;, &lt;code&gt;lua&lt;/code&gt;, and &lt;code&gt;tre&lt;/code&gt; were each independently compiled into static archives, then handed to the linker alongside Redis's own translation units — proving the pipeline handles real, dependency-aware, multi-ABI native builds, not just a flat folder of standalone &lt;code&gt;.c&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;The honest framing matters more than the headline number: this is &lt;strong&gt;end-to-end orchestration latency on one specific host, toolchain, and cache state&lt;/strong&gt; — not a claim about compiler speed, and not a number that means anything without the CPU model, governor, storage, toolchain versions, cache warmth, and parallelism level that produced it. ForgeZero's own documentation says exactly that, unprompted, which is a more convincing performance claim than any raw benchmark number could be on its own.&lt;/p&gt;
&lt;h3 id=&quot;cross-platform-without-pretending-every-platform-is-the-same&quot;&gt;Cross-Platform Without Pretending Every Platform Is the Same&lt;/h3&gt;
&lt;p&gt;The Go core stays portable while low-level operations are split cleanly across build-tagged files: Linux gets native &lt;code&gt;getdents64&lt;/code&gt;/&lt;code&gt;statx&lt;/code&gt;/io_uring fast paths, other Unix-like targets get &lt;code&gt;mmap&lt;/code&gt;/descriptor-based paths, Windows gets its own executable, security, and filesystem branches entirely. Cross-platform, here, doesn't mean identical capability everywhere — it means an &lt;strong&gt;identical semantic contract&lt;/strong&gt; at the API boundary regardless of which backend is actually doing the work underneath it. Linux can have the fastest path available to it without Windows being forced into an emulation layer pretending to be a kernel it isn't.&lt;/p&gt;
&lt;h3 id=&quot;the-installer-nobody-notices-until-it-breaks&quot;&gt;The Installer Nobody Notices Until It Breaks&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;install.sh&lt;/code&gt; got rewritten as a strict POSIX &lt;code&gt;sh&lt;/code&gt; script with explicit platform and architecture validation, &lt;code&gt;--help&lt;/code&gt; and missing-option handling, retry-and-timeout logic on both &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;wget&lt;/code&gt;, and remote &lt;em&gt;and&lt;/em&gt; local checksum verification before anything gets executed. The insecure &lt;code&gt;mktemp -u&lt;/code&gt; pattern — which creates a predictable race window between naming a temp path and actually using it — was replaced with a properly private temporary directory. Writable installs land through a temp-file-then-atomic-rename sequence; privileged installs go through &lt;code&gt;sudo install&lt;/code&gt; rather than a raw copy; a full dry-run mode exists that downloads and modifies nothing; cleanup traps make sure temp files don't survive a failed run. It's the least glamorous file in the entire repository, and it's exactly the file that determines whether someone's first five minutes with the project go smoothly or end with a half-installed binary and a GitHub issue.&lt;/p&gt;
&lt;h3 id=&quot;what-forgezero-deliberately-doesn-t-do-and-why-that-s-worth-trusting&quot;&gt;What ForgeZero Deliberately Doesn't Do — And Why That's Worth Trusting&lt;/h3&gt;
&lt;p&gt;The most senior-engineer-coded section of this entire project's documentation isn't a feature list. It's a short list of things the team explicitly &lt;em&gt;chose not to build&lt;/em&gt;, and it's worth reading as a statement of engineering values as much as a technical note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BB64 stays out of every security-sensitive path&lt;/strong&gt; — integrity checks, package verification, and cryptographic APIs all keep using the keyed BLAKE3-based BoomBoom Hash, on purpose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;IORING_OP_GETDENTS&lt;/code&gt; was never faked into existence&lt;/strong&gt; to check a feature-completeness box, because the kernel ABI in question genuinely doesn't expose a usable opcode for it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;clone3&lt;/code&gt;/&lt;code&gt;execveat&lt;/code&gt; weren't forced into the process launcher&lt;/strong&gt; without a proper lifecycle design, because Go's context cancellation, descriptor ownership, and cross-platform launcher contracts all deserve a real design pass, not a shortcut.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A shared-memory persistent cache wasn't shipped&lt;/strong&gt; without a versioned format, crash recovery, and an ownership protocol behind it — because a cache that can silently corrupt itself is worse than no cache at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No thousand-fold speedup claims got made&lt;/strong&gt; without workload-specific measurements standing behind them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any team can list what they built. Far fewer are willing to publish, in plain language, what they refused to ship half-finished — and that restraint is a far stronger signal of engineering maturity than another benchmark chart would ever be.&lt;/p&gt;
&lt;h3 id=&quot;where-this-actually-leaves-you&quot;&gt;Where This Actually Leaves You&lt;/h3&gt;
&lt;p&gt;ForgeZero isn't trying to convince you that &lt;code&gt;gcc -O3&lt;/code&gt; needs replacing. It's making a much narrower, much more defensible claim: that the &lt;em&gt;coordination&lt;/em&gt; surrounding native compilation — discovery, invalidation, scheduling, dispatch, caching, linking — has its own real, measurable cost, and that cost has been quietly tolerated by an entire industry for decades because Make, Autotools, CMake, and Ninja were each solving the build problem of their own era, not this one. Assembly-heavy, embedded, mixed-language, cache-identity-sensitive native builds were never really the target audience for any of them.&lt;/p&gt;
&lt;p&gt;A year and a half of quiet, unglamorous systems work — cache-line-aware schedulers, kernel-ABI-honest io_uring integration, a hash function that knows exactly which problems it's allowed to touch and which ones it isn't — is what it actually takes to make that narrower claim true instead of just plausible. That's the whole pitch. Not magic. Not a thousand-fold speedup. Just a build system that finally stops forgiving itself for not knowing precisely why it did what it just did.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/27194/forgezero-what-happens-when-someone-finally-stops-forgiving-your-build-system</guid>
<pubDate>Mon, 14 Sep 2026 21:33:21 +0000</pubDate>
</item>
<item>
<title>GoGraph updates, Providing Structural Awareness to AI Coding Agents in Go.</title>
<link>https://coderlegion.com/26640/gograph-updates-providing-structural-awareness-to-ai-coding-agents-in-go</link>
<description>&lt;p&gt;&lt;strong&gt;GoGraph Four Months Later: From Repository Map to Evidence Layer for AI Coding Agents&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A few months ago, I wrote about a problem I kept seeing with AI coding agents: they could write Go perfectly well, but before doing anything useful they spent a surprising amount of their context window trying to understand the repository.&lt;/p&gt;
&lt;p&gt;They would grep for symbols, guess filenames, read large files, follow imports manually, and slowly reconstruct relationships that the Go compiler already knows.&lt;/p&gt;
&lt;p&gt;That was the motivation behind &lt;strong&gt;GoGraph&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The first version I wrote about in May was relatively simple in concept: parse a Go repository, turn its structure into a graph, and expose that graph to coding agents through CLI commands and MCP.&lt;/p&gt;
&lt;p&gt;The basic idea has not changed.&lt;/p&gt;
&lt;p&gt;But &lt;strong&gt;GoGraph&lt;/strong&gt; itself has changed quite a lot.&lt;/p&gt;
&lt;p&gt;Today, I would describe it less as a static repository map and more as a local evidence layer between a Go codebase and an AI coding agent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Original Model: Give the Agent a Map&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The first version concentrated heavily on AST-based structural analysis.&lt;/p&gt;
&lt;p&gt;GoGraph parsed source code and extracted things such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;packages&lt;/li&gt;
&lt;li&gt;functions and methods&lt;/li&gt;
&lt;li&gt;imports&lt;/li&gt;
&lt;li&gt;calls&lt;/li&gt;
&lt;li&gt;interfaces and implementations&lt;/li&gt;
&lt;li&gt;HTTP routes&lt;/li&gt;
&lt;li&gt;tests&lt;/li&gt;
&lt;li&gt;SQL-related structures&lt;/li&gt;
&lt;li&gt;error paths&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Commands such as plan, review, and errorflow were intended to answer practical questions before and after an agent edited code.&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;p&gt;grep&lt;br&gt;
rg&lt;br&gt;
awk&lt;br&gt;
sed&lt;br&gt;
read file&lt;br&gt;
grep again&lt;br&gt;
read another file&lt;br&gt;
guess&lt;/p&gt;
&lt;p&gt;the agent could ask GoGraph for a bounded structural answer.&lt;/p&gt;
&lt;p&gt;That is still useful, and the AST layer still exists.&lt;/p&gt;
&lt;p&gt;The limitation is that Go is not completely understandable from syntax alone.&lt;/p&gt;
&lt;p&gt;Consider this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Store interface {
    Save(User) error
}
func Create(s Store, u User) error {
    return s.Save(u)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the AST, we know that Create calls Store.Save.&lt;/p&gt;
&lt;p&gt;But that does not necessarily tell us which concrete Save implementation executes.&lt;/p&gt;
&lt;p&gt;That becomes increasingly important once an agent starts reasoning about impact rather than simply locating code.&lt;/p&gt;
&lt;p&gt;GoGraph Now Has a Precision Layer&lt;/p&gt;
&lt;p&gt;The largest architectural change is that GoGraph no longer relies only on syntactic relationships.&lt;/p&gt;
&lt;p&gt;Precise builds now use Go’s type information together with go/packages, go/types, SSA, and Class Hierarchy Analysis.&lt;/p&gt;
&lt;p&gt;The result is effectively a two-layer model.&lt;/p&gt;
&lt;p&gt;The AST layer remains fast, resilient, and useful even when a repository cannot be fully type-checked.&lt;/p&gt;
&lt;p&gt;The precision layer adds compiler-aware relationships when the repository can be successfully loaded.&lt;/p&gt;
&lt;p&gt;This matters particularly for interfaces.&lt;/p&gt;
&lt;p&gt;An AST-level analysis might know that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;service -&amp;gt; Repository.Save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Type-aware analysis can identify actual implementations of that interface.&lt;/p&gt;
&lt;p&gt;SSA analysis can sometimes go further and prove that a particular interface value contains one concrete implementation at a call site.&lt;/p&gt;
&lt;p&gt;GoGraph deliberately distinguishes those cases.&lt;/p&gt;
&lt;p&gt;A call that is proven receives different treatment from a call that is merely a possible dynamic dispatch target.&lt;/p&gt;
&lt;p&gt;That distinction turned out to be important for agents.&lt;/p&gt;
&lt;p&gt;An AI should not see:&lt;/p&gt;
&lt;p&gt;A calls B&lt;/p&gt;
&lt;p&gt;when what the analysis actually knows is:&lt;/p&gt;
&lt;p&gt;A may call B&lt;/p&gt;
&lt;p&gt;Those are different facts.&lt;/p&gt;
&lt;p&gt;Exact and Possible Relationships&lt;/p&gt;
&lt;p&gt;This led to another change in the graph model.&lt;/p&gt;
&lt;p&gt;Relationships now carry more information about how strongly GoGraph knows them.&lt;/p&gt;
&lt;p&gt;For example, paths can contain relationships that are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;exact&lt;/li&gt;
&lt;li&gt;ambiguous&lt;/li&gt;
&lt;li&gt;possible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Commands can request exact-only results when an agent needs a conservative answer.&lt;/p&gt;
&lt;p&gt;When several possible paths exist, GoGraph also ranks them deterministically rather than returning whichever traversal happens to find first.&lt;/p&gt;
&lt;p&gt;The preference is roughly toward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;stronger evidence&lt;/li&gt;
&lt;li&gt;shorter paths&lt;/li&gt;
&lt;li&gt;production code rather than tests&lt;/li&gt;
&lt;li&gt;typed resolution rather than heuristics&lt;/li&gt;
&lt;li&gt;fewer cross-repository transitions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This sounds like an implementation detail, but it changes agent behavior substantially.&lt;/p&gt;
&lt;p&gt;Determinism is useful when an LLM repeatedly asks the same structural question during planning, editing, and review. The answer should not change merely because map iteration order changed.&lt;/p&gt;
&lt;p&gt;A First-Call Tool for Agents&lt;/p&gt;
&lt;p&gt;One problem became obvious after using GoGraph with actual coding agents.&lt;/p&gt;
&lt;p&gt;Even with dozens of specialized commands, the agent first has to know which command to use.&lt;/p&gt;
&lt;p&gt;So GoGraph now has explore.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gograph explore &quot;authentication middleware&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or through MCP, gograph_explore.&lt;/p&gt;
&lt;p&gt;It performs bounded lexical discovery and, when a symbol can be selected unambiguously, combines several kinds of context into one response:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;matching symbols&lt;/li&gt;
&lt;li&gt;source location&lt;/li&gt;
&lt;li&gt;direct callers&lt;/li&gt;
&lt;li&gt;direct callees&lt;/li&gt;
&lt;li&gt;tests&lt;/li&gt;
&lt;li&gt;upstream impact&lt;/li&gt;
&lt;li&gt;package context&lt;/li&gt;
&lt;li&gt;deeper call relationships when requested&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are compact and deep modes.&lt;/p&gt;
&lt;p&gt;This is deliberately not semantic RAG.&lt;/p&gt;
&lt;p&gt;GoGraph does not pretend that it “understands” a natural-language question. It tokenizes the query deterministically, finds structural candidates, and reports how the selection was made.&lt;/p&gt;
&lt;p&gt;If a symbol is ambiguous, it exposes the ambiguity instead of silently picking one.&lt;/p&gt;
&lt;p&gt;That is an important design rule throughout the newer versions of GoGraph:&lt;/p&gt;
&lt;p&gt;Uncertainty should be data, not something hidden by the tool.&lt;/p&gt;
&lt;p&gt;From Repository Analysis to Change Analysis&lt;/p&gt;
&lt;p&gt;The original plan idea answered:&lt;/p&gt;
&lt;p&gt;What could be affected if I modify this symbol?&lt;/p&gt;
&lt;p&gt;That is useful before editing.&lt;/p&gt;
&lt;p&gt;But after an agent has changed the repository, the more interesting question is:&lt;/p&gt;
&lt;p&gt;What did I actually change, and what does that change affect?&lt;/p&gt;
&lt;p&gt;GoGraph now performs declaration-level change analysis against Git references and the working tree.&lt;/p&gt;
&lt;p&gt;It distinguishes things such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;edited declarations&lt;/li&gt;
&lt;li&gt;added declarations&lt;/li&gt;
&lt;li&gt;removed declarations&lt;/li&gt;
&lt;li&gt;excluded declarations&lt;/li&gt;
&lt;li&gt;changes that cannot be evaluated reliably&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Untracked Go files are included when looking at working-tree changes.&lt;/p&gt;
&lt;p&gt;This also avoids a surprisingly dangerous failure mode.&lt;/p&gt;
&lt;p&gt;Imagine the agent deletes a function.&lt;/p&gt;
&lt;p&gt;Looking only at the current graph cannot tell you who called the deleted function, because the function no longer exists.&lt;/p&gt;
&lt;p&gt;Change analysis therefore needs historical evidence from the baseline rather than pretending the current graph contains enough information.&lt;/p&gt;
&lt;p&gt;When GoGraph does not have enough evidence to calculate an impact safely, newer versions prefer to report that the evaluation is incomplete rather than returning an empty impact set.&lt;/p&gt;
&lt;p&gt;For AI tooling, “I cannot prove this” is much safer than “nothing is affected.”&lt;/p&gt;
&lt;p&gt;Graph Freshness Became a First-Class Concept&lt;/p&gt;
&lt;p&gt;Another thing I underestimated in the original version was graph freshness.&lt;/p&gt;
&lt;p&gt;A perfectly accurate graph of yesterday’s checkout is still the wrong answer.&lt;/p&gt;
&lt;p&gt;GoGraph now tracks graph state explicitly.&lt;/p&gt;
&lt;p&gt;Results can distinguish:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;persisted vs. in-memory graph&lt;/li&gt;
&lt;li&gt;current vs. stale graph&lt;/li&gt;
&lt;li&gt;complete vs. partial parsing&lt;/li&gt;
&lt;li&gt;AST vs. precise vs. precision-fallback analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MCP can refresh stale graphs automatically.&lt;/p&gt;
&lt;p&gt;If precise enrichment fails but AST analysis succeeds, GoGraph can return the fresh AST result while explicitly saying that precise analysis failed.&lt;/p&gt;
&lt;p&gt;If refreshing itself fails, it can retain the last trusted graph while exposing that it is stale.&lt;/p&gt;
&lt;p&gt;The important part is that these states are not collapsed into one vague “success.”&lt;/p&gt;
&lt;p&gt;For an AI agent, provenance matters.&lt;/p&gt;
&lt;p&gt;There is a substantial difference between:&lt;/p&gt;
&lt;p&gt;No callers found.&lt;/p&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;p&gt;No callers found.&lt;br&gt;
Graph: current.&lt;br&gt;
Analysis: precise.&lt;br&gt;
Parsing: complete.&lt;/p&gt;
&lt;p&gt;There is an even larger difference between that and:&lt;/p&gt;
&lt;p&gt;No callers found.&lt;br&gt;
Graph: stale.&lt;br&gt;
Analysis: AST fallback.&lt;/p&gt;
&lt;p&gt;The model should be allowed to reason differently about those answers.&lt;/p&gt;
&lt;p&gt;Large Results Are Now Explicitly Bounded&lt;/p&gt;
&lt;p&gt;Token efficiency was the original reason I created GoGraph, so there is an obvious irony if a structural query returns a 200 KB JSON response.&lt;/p&gt;
&lt;p&gt;Newer GoGraph query APIs therefore put considerably more effort into bounded output.&lt;/p&gt;
&lt;p&gt;Large result sets use deterministic pagination and expose information such as:&lt;/p&gt;
&lt;p&gt;total&lt;br&gt;
returned&lt;br&gt;
truncated&lt;br&gt;
next_cursor&lt;/p&gt;
&lt;p&gt;The cursor is tied to the graph snapshot and query selection, so it cannot silently continue over a different graph after the repository changes.&lt;/p&gt;
&lt;p&gt;MCP output also has an explicit response-size budget.&lt;/p&gt;
&lt;p&gt;This is not merely an API optimization.&lt;/p&gt;
&lt;p&gt;A tool built for LLMs needs to consider context consumption as part of its interface design.&lt;/p&gt;
&lt;p&gt;Traditional developer tooling often assumes:&lt;/p&gt;
&lt;p&gt;Returning more information is better.&lt;/p&gt;
&lt;p&gt;Agent tooling often has the opposite requirement:&lt;/p&gt;
&lt;p&gt;Return the smallest amount of information that preserves the evidence required for the decision.&lt;/p&gt;
&lt;p&gt;Better Test Reasoning&lt;/p&gt;
&lt;p&gt;Test discovery has also moved beyond simply asking whether a symbol name appears in a _test.go file.&lt;/p&gt;
&lt;p&gt;GoGraph can now trace tests transitively through call relationships and distinguish exact test coverage paths from possible ones.&lt;/p&gt;
&lt;p&gt;This becomes useful with architectures where a test reaches the target through several layers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TestCreateUser
    -&amp;gt; handler
        -&amp;gt; service
            -&amp;gt; repository
                -&amp;gt; Save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Router callbacks and other intermediary functions can participate in that attribution.&lt;/p&gt;
&lt;p&gt;Interface-backed test doubles are also important.&lt;/p&gt;
&lt;p&gt;GoGraph’s typed test analysis can recognize cases where a test assigns a concrete fake or mock to an interface and prove the resulting call target when the type information allows it.&lt;/p&gt;
&lt;p&gt;That makes commands such as tests and untested much more useful as structural evidence for coding agents.&lt;/p&gt;
&lt;p&gt;It still does not mean “this code is correctly tested.”&lt;/p&gt;
&lt;p&gt;That requires running the tests.&lt;/p&gt;
&lt;p&gt;It means something narrower and more defensible:&lt;/p&gt;
&lt;p&gt;These tests have a statically observable relationship to this declaration.&lt;/p&gt;
&lt;p&gt;SQL Became Structured Evidence&lt;/p&gt;
&lt;p&gt;SQL started as another useful static extraction target.&lt;/p&gt;
&lt;p&gt;It has since become a more structured query system.&lt;/p&gt;
&lt;p&gt;GoGraph can classify statically resolvable PostgreSQL statements and expose information such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;operation&lt;/li&gt;
&lt;li&gt;read/write access&lt;/li&gt;
&lt;li&gt;referenced tables&lt;/li&gt;
&lt;li&gt;access per table&lt;/li&gt;
&lt;li&gt;source location&lt;/li&gt;
&lt;li&gt;module&lt;/li&gt;
&lt;li&gt;test/production origin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It understands more than literal strings.&lt;/p&gt;
&lt;p&gt;Statically provable local constants, variables, assignments, and bounded concatenations can also participate in SQL resolution.&lt;/p&gt;
&lt;p&gt;Dynamic SQL is intentionally not presented as if it were fully known.&lt;/p&gt;
&lt;p&gt;The result is useful for questions such as:&lt;/p&gt;
&lt;p&gt;Which code writes the oauth_clients table?&lt;/p&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;p&gt;Which production paths execute DELETE statements?&lt;/p&gt;
&lt;p&gt;Again, the goal is not to replace a SQL parser, database audit system, or runtime tracing.&lt;/p&gt;
&lt;p&gt;It is to give an agent a useful static census of evidence that exists in the repository.&lt;/p&gt;
&lt;p&gt;HTTP Relationships Also Became More Precise&lt;/p&gt;
&lt;p&gt;Route extraction existed in the original version, but HTTP reasoning has expanded.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GoGraph&lt;/strong&gt; now distinguishes constructing an HTTP request from actually proving that the request is dispatched.&lt;/p&gt;
&lt;p&gt;It can preserve statically known URL bases and suffixes.&lt;/p&gt;
&lt;p&gt;More importantly, this becomes useful in the workspace model.&lt;/p&gt;
&lt;p&gt;A repository can declare mappings between outbound HTTP client authorities and another repository in the same workspace.&lt;/p&gt;
&lt;p&gt;That allows GoGraph to represent a relationship such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;frontend-api
    -&amp;gt; HTTP request
        -&amp;gt; identity-service
            -&amp;gt; POST /users
                -&amp;gt; CreateUser handler
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;without inventing relationships from runtime environment variables or guessed hostnames.&lt;/p&gt;
&lt;p&gt;GoGraph Can Now Analyze a Workspace&lt;/p&gt;
&lt;p&gt;This is probably the biggest conceptual expansion since the original article.&lt;/p&gt;
&lt;p&gt;Modern systems are often not one Go repository.&lt;/p&gt;
&lt;p&gt;You may have:&lt;/p&gt;
&lt;p&gt;identity-service/&lt;br&gt;
agent-service/&lt;br&gt;
gateway/&lt;br&gt;
shared-lib/&lt;/p&gt;
&lt;p&gt;Each repository has its own graph.&lt;/p&gt;
&lt;p&gt;GoGraph can now build a workspace overlay across them.&lt;/p&gt;
&lt;p&gt;The individual repository graphs remain independent. The workspace adds explicitly resolved relationships between them.&lt;/p&gt;
&lt;p&gt;These can include cross-repository Go relationships and configured HTTP relationships.&lt;/p&gt;
&lt;p&gt;The workspace can then answer questions such as:&lt;/p&gt;
&lt;p&gt;What path connects this gateway handler&lt;br&gt;
to this identity-service function?&lt;/p&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;p&gt;What repositories may be affected by this change?&lt;/p&gt;
&lt;p&gt;There is also a separate read-only workspace MCP server exposing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gograph_workspace_status
gograph_workspace_query
gograph_workspace_path
gograph_workspace_impact
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This matters because the “whole codebase” visible to an AI agent is increasingly not the same thing as a Git repository.&lt;/p&gt;
&lt;p&gt;The useful unit is often the engineering workspace.&lt;/p&gt;
&lt;p&gt;Go Build Configuration Is Part of the Graph&lt;/p&gt;
&lt;p&gt;Another source of false confidence is pretending that a Go repository has one universal structure.&lt;/p&gt;
&lt;p&gt;It often does not.&lt;/p&gt;
&lt;p&gt;Build tags, GOOS, GOARCH, GOFLAGS, cgo, go.work, generated files, and module selection can all change which source files constitute the program.&lt;/p&gt;
&lt;p&gt;GoGraph now records the effective build selection used to construct the graph.&lt;/p&gt;
&lt;p&gt;Explicit build tags are supported.&lt;/p&gt;
&lt;p&gt;If the environment used to query a persisted graph no longer matches the environment used to build it, the graph can be considered stale rather than silently mixing incompatible views of the program.&lt;/p&gt;
&lt;p&gt;That is another example of something which is easy to ignore in a developer convenience tool but important in an evidence tool.&lt;/p&gt;
&lt;p&gt;Local Does Not Automatically Mean Safe&lt;/p&gt;
&lt;p&gt;The original article emphasized that GoGraph runs locally.&lt;/p&gt;
&lt;p&gt;That remains true.&lt;/p&gt;
&lt;p&gt;But over time I became much more conservative about what “local static analysis” should be allowed to touch.&lt;/p&gt;
&lt;p&gt;The current code performs extensive path and source confinement checks.&lt;/p&gt;
&lt;p&gt;Among other things, it treats Go build inputs, module/workspace metadata, graph artifacts, and generated files carefully around symbolic links and repository boundaries.&lt;/p&gt;
&lt;p&gt;The goal is simple:&lt;/p&gt;
&lt;p&gt;Asking an AI agent to inspect an untrusted repository should not casually turn a static-analysis request into arbitrary traversal outside the intended source tree.&lt;/p&gt;
&lt;p&gt;Go tooling itself can legitimately access module caches, proxies, and toolchains, so the boundary cannot simply be “never touch anything outside the repository.”&lt;/p&gt;
&lt;p&gt;Instead, GoGraph tries to distinguish repository-controlled source authority from legitimate Go dependency resolution.&lt;/p&gt;
&lt;p&gt;This security work is considerably less visible than a new query command, but I now consider it part of the core design.&lt;/p&gt;
&lt;p&gt;MCP Is No Longer Just a Wrapper Around CLI Output&lt;/p&gt;
&lt;p&gt;MCP was already present in the original version.&lt;/p&gt;
&lt;p&gt;Its role has expanded.&lt;/p&gt;
&lt;p&gt;The project MCP server now exposes most of GoGraph’s repository analysis capabilities directly, with CLI and MCP generally sharing the same underlying result contracts.&lt;/p&gt;
&lt;p&gt;That prevents an unfortunate architecture where:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CLI implementation
       +
separate MCP implementation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;slowly develops different semantics.&lt;/p&gt;
&lt;p&gt;The MCP server can also expose its own version and analysis capabilities, which matters because restarting an MCP process is necessary after installing a newer GoGraph binary. An already-running process does not magically inherit the new tool schema.&lt;/p&gt;
&lt;p&gt;The server still runs locally over stdio.&lt;/p&gt;
&lt;p&gt;There is no hosted GoGraph service receiving the repository.&lt;/p&gt;
&lt;p&gt;What Has Not Changed&lt;/p&gt;
&lt;p&gt;Despite all of these additions, there are several things I deliberately do not want GoGraph to become.&lt;/p&gt;
&lt;p&gt;It is not an AI model.&lt;/p&gt;
&lt;p&gt;It does not decide what code should be written.&lt;/p&gt;
&lt;p&gt;It does not replace go test.&lt;/p&gt;
&lt;p&gt;It does not replace the compiler.&lt;/p&gt;
&lt;p&gt;It does not replace runtime tracing.&lt;/p&gt;
&lt;p&gt;It does not replace semantic code search.&lt;/p&gt;
&lt;p&gt;And it should not pretend static analysis can prove runtime behavior that static analysis cannot see.&lt;/p&gt;
&lt;p&gt;I increasingly think the useful division of labor looks like this:&lt;/p&gt;
&lt;p&gt;LLM&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;reasoning
interpretation
implementation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Semantic search / grep&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;discovery
text
configuration
documentation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GoGraph&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;structural evidence
relationships
impact
provenance
uncertainty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go compiler / tests&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;executable verification
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These tools complement each other.&lt;/p&gt;
&lt;p&gt;The Larger Lesson&lt;/p&gt;
&lt;p&gt;When I wrote the first article, I described the problem mostly as a context-window problem.&lt;/p&gt;
&lt;p&gt;I still think that is true, but I would phrase it slightly differently now.&lt;/p&gt;
&lt;p&gt;The scarce resource is not simply tokens.&lt;/p&gt;
&lt;p&gt;It is reliable context.&lt;/p&gt;
&lt;p&gt;Giving an agent 50,000 lines of source code is context.&lt;/p&gt;
&lt;p&gt;Giving it this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CreateUser
  exact caller:
    POST /api/v1/users -&amp;gt; UsersHandler.Create
  exact dependency:
    UserRepository.Insert
  database:
    INSERT -&amp;gt; users
  attributed tests:
    TestCreateUser
    TestCreateUserDuplicate
  graph:
    current
    precise
    complete
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is also context.&lt;/p&gt;
&lt;p&gt;But the second form has already converted a large amount of syntax into a much smaller set of claims.&lt;/p&gt;
&lt;p&gt;That is where I now see GoGraph fitting.&lt;/p&gt;
&lt;p&gt;It is not trying to make the LLM smarter.&lt;/p&gt;
&lt;p&gt;It is trying to reduce how much of the LLM’s intelligence has to be spent rediscovering facts that static analysis can provide deterministically.&lt;/p&gt;
&lt;p&gt;The original goal was to give AI coding agents a map.&lt;/p&gt;
&lt;p&gt;The current goal is slightly more demanding:&lt;/p&gt;
&lt;p&gt;Give them evidence, identify where that evidence came from, distinguish certainty from possibility, and say when the evidence is incomplete.&lt;/p&gt;
&lt;p&gt;For coding agents operating on large Go systems, I think that is a much more useful foundation.&lt;/p&gt;
&lt;p&gt;GoGraph on GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ozgurcd/gograph&quot;&gt;https://github.com/ozgurcd/gograph&lt;/a&gt; &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26640/gograph-updates-providing-structural-awareness-to-ai-coding-agents-in-go</guid>
<pubDate>Wed, 09 Sep 2026 18:31:10 +0000</pubDate>
</item>
<item>
<title>Fighting Build-System Complexity: Alex Voste on Systems Programming, Performance and ForgeZero</title>
<link>https://coderlegion.com/25614/fighting-build-system-complexity-alex-voste-systems-programming-performance-forgezero</link>
<description>&lt;h2 id=&quot;from-systems-engineering-to-forgezero-alex-voste-on-performance-build-systems-assembly-and-the-fight-against-overhead&quot;&gt;From Systems Engineering to ForgeZero: Alex Voste on Performance, Build Systems, Assembly and the Fight Against Overhead&lt;/h2&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Developer Stories | CoderLegion&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Meet &lt;strong&gt;Alex Voste&lt;/strong&gt;, a Canada-based systems-focused developer working across &lt;strong&gt;C, Go, x86_64 Assembly, NASM, FASM, C++, Linux, performance optimization, and developer tooling&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Alex describes himself as a network systems engineer and is currently building &lt;strong&gt;ForgeZero&lt;/strong&gt;, a hardened cross-platform build toolchain for C and Assembly developers.&lt;/p&gt;
&lt;p&gt;His approach to software is strongly shaped by a fascination with what happens underneath the abstractions — from memory and computer architecture to compilers, build systems, and raw execution speed.&lt;/p&gt;
&lt;p&gt;In this Developer Story, Alex shares how he moved from PHP and Java toward systems engineering, why he decided to build ForgeZero, his philosophy around performance and complexity, what he thinks modern build systems get wrong, and why he believes developers working close to the hardware will need strong fundamentals even in an increasingly AI-driven world.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-from-systems-engineering-to-forgezero&quot;&gt;1. From Systems Engineering to ForgeZero&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;How did you get into systems engineering and what eventually led you to building ForgeZero?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I often describe myself as a network systems engineer, but let's be clear, that wasn't a title I woke up one morning and slapped on my LinkedIn. It took years of grinding through the trenches to actually earn it.&lt;/p&gt;
&lt;p&gt;I started way back with PHP and web development, then moved into Java, doing the usual backend work. Honestly, it got boring fast. I was building the same conventional stuff everyone else was, shipping the same CRUD, wiring the same frameworks, and calling it engineering when really it was just assembly-line work with better naming conventions.&lt;/p&gt;
&lt;p&gt;I wanted to go deeper: how programming languages actually work under the hood, how systems manage memory, or how my phone quietly phones home to corporate servers whether I asked it to or not.&lt;/p&gt;
&lt;p&gt;Building a custom build tool wasn't some boardroom decision. It started years ago as a passion project between me and a friend.&lt;/p&gt;
&lt;p&gt;We were two enthusiasts trying to write our own operating system in Assembly from scratch, controlling literally everything down to the bit.&lt;/p&gt;
&lt;p&gt;Tools like Make or CMake ran completely against that philosophy. We didn't want heavy, opaque black boxes making decisions on our behalf that we never signed off on.&lt;/p&gt;
&lt;p&gt;So I built my own toolchain, first in dependency-free Node.js, and later rebuilt entirely in Go, because at some point you get tired of fighting the tool instead of the actual problem.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-the-problem-with-modern-build-systems&quot;&gt;2. The Problem With Modern Build Systems&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What problem do you think modern build systems have, and what is ForgeZero trying to do differently?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I firmly believe most modern build systems, and honestly most software in general, run far too many redundant operations that nobody actually needs.&lt;/p&gt;
&lt;p&gt;They try to cover every edge case and every platform under the sun simultaneously, even when nobody asked, and you end up paying for that indecision in CPU cycles you never approved spending.&lt;/p&gt;
&lt;p&gt;ForgeZero's philosophy is heavily inspired by &lt;strong&gt;Gentoo&lt;/strong&gt; and &lt;strong&gt;Linux From Scratch (LFS)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A build toolchain should do exactly what the developer explicitly asks it to do, nothing more.&lt;/p&gt;
&lt;p&gt;No hidden magic, no unnecessary overhead, zero wasted cycles.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If I didn't type it, it shouldn't be running.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That principle sits at the heart of how I think about build tooling.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-why-build-another-build-system-&quot;&gt;3. Why Build Another Build System?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;People often ask why another build system is necessary. Why did you decide to build ForgeZero instead of using existing solutions?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;People ask me all the time: why reinvent the wheel?&lt;/p&gt;
&lt;p&gt;Honestly, I don't see ForgeZero as just another wheel.&lt;/p&gt;
&lt;p&gt;It's more like a wheel with a twin-turbo engine bolted on, built by someone who was personally offended by how slow the old wheel was.&lt;/p&gt;
&lt;p&gt;The codebase is lean, sitting around &lt;strong&gt;42k+ lines of clean code&lt;/strong&gt;, with no dead weight padding out a changelog just to look impressive.&lt;/p&gt;
&lt;p&gt;I originally built it purely to save my own time.&lt;/p&gt;
&lt;p&gt;But somewhere around the third cup of coffee at 3 AM, inspiration hits, and you start bolting on optimizations you never planned at 9 PM.&lt;/p&gt;
&lt;p&gt;My motivation was simple: I had a clear architectural idea for how build orchestration could be blazingly fast, and I wasn't going to sit around waiting for someone else to get to it.&lt;/p&gt;
&lt;p&gt;When I benchmarked it against real-world projects, ForgeZero came out multiple times faster than the existing solutions.&lt;/p&gt;
&lt;p&gt;My goal is to give developers what they actually care about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;raw speed&lt;/li&gt;
&lt;li&gt;intelligent caching&lt;/li&gt;
&lt;li&gt;real security&lt;/li&gt;
&lt;li&gt;total manual control&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not a dashboard cheerfully telling them everything's fine while it quietly isn't.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-why-go-and-assembly-&quot;&gt;4. Why Go and Assembly?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why did you choose Go as the foundation for ForgeZero, and where does Assembly fit into the project?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I chose Go as the foundation because it strikes a rare balance between simplicity and safety.&lt;/p&gt;
&lt;p&gt;Write pure C or Assembly for everything, and it's incredibly easy to leave behind a subtle memory bug or race condition you'll be debugging at 4 AM, hating your past self with real, focused intensity.&lt;/p&gt;
&lt;p&gt;Go gives me near-C performance while sparing me the catastrophic runtime failures I no longer have the patience to hunt at that hour.&lt;/p&gt;
&lt;p&gt;That said, I do use Assembly in ForgeZero, specifically &lt;strong&gt;Plan 9 Assembly&lt;/strong&gt;, on the performance-critical paths.&lt;/p&gt;
&lt;p&gt;That's where a huge chunk of our raw execution speed comes from, and no amount of clever Go abstraction replaces hand-placed instructions where it actually counts.&lt;/p&gt;
&lt;p&gt;When it's a choice between an optimization that adds complexity or keeping things simple for safety's sake, I'll take the optimization almost every time.&lt;/p&gt;
&lt;p&gt;ForgeZero has a fair amount of low-level, high-risk Go code where we bypass standard allocations entirely.&lt;/p&gt;
&lt;p&gt;As long as I keep tight control over those boundaries, the performance gains are massive, and I'd rather own that risk deliberately than inherit someone else's by accident.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-where-does-the-performance-difference-come-from-&quot;&gt;5. Where Does the Performance Difference Come From?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ForgeZero reportedly performs significantly faster than some existing build tools. What is behind that difference?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you look at the benchmarks where ForgeZero significantly outperforms tools like &lt;strong&gt;Ninja&lt;/strong&gt; on large task sets, the secret isn't some clever trick hiding in a corner.&lt;/p&gt;
&lt;p&gt;It's simplicity, plain and unglamorous.&lt;/p&gt;
&lt;p&gt;And I know that answer disappoints people hunting for a silver bullet.&lt;/p&gt;
&lt;p&gt;When your architecture isn't burning cycles managing legacy abstractions, allocation overhead, or bloated state graphs nobody asked it to maintain, execution ends up blindingly fast almost by default.&lt;/p&gt;
&lt;p&gt;We do fewer things, but we do them at the bare-metal limit, on purpose, every single time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-building-for-low-level-developers&quot;&gt;6. Building for Low-Level Developers&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What do you think existing developer tooling gets wrong about low-level developers?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What existing tooling gets wrong about low-level developers is assuming we want convenience over control.&lt;/p&gt;
&lt;p&gt;Most modern dev tools are built around a web-dev mindset: hide the complexity, abstract the system, install 500 MB of dependencies for something that should take twenty lines, and pretend the hardware isn't even there.&lt;/p&gt;
&lt;p&gt;Systems engineers, C programmers, Assembly devs, we don't want black boxes.&lt;/p&gt;
&lt;p&gt;We want to see the hardware.&lt;/p&gt;
&lt;p&gt;We want toolchains that stay out of our way, don't force opinionated directory structures on us like we're kids who need supervision, and don't burn cycles on telemetry or bloated config parsers nobody reads.&lt;/p&gt;
&lt;p&gt;Existing tools try to be everything for everyone and end up being genuinely excellent at nothing.&lt;/p&gt;
&lt;p&gt;Low-level devs just want a tool that gets out of the way, respects the hardware, and executes instantly.&lt;/p&gt;
&lt;p&gt;Full stop.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-open-source-and-building-in-public&quot;&gt;7. Open Source and Building in Public&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What role does open source play in your approach to software development?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My core philosophy has always centered on building free, open-source software.&lt;/p&gt;
&lt;p&gt;That doesn't mean I never build proprietary products that generate revenue, but when it comes to fundamental tooling, I'd rather keep it open for the community, because gatekeeping a build tool behind a paywall feels beneath the entire point of building one.&lt;/p&gt;
&lt;p&gt;For instance, I'm currently working on a confidential AI project applied to medical engineering, more on that down the road.&lt;/p&gt;
&lt;p&gt;Building in public keeps your architecture honest, because anyone can audit your code, and nothing sharpens discipline quite like knowing a stranger might read your worst commit at 2 AM and judge you for it, correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-performance-vs-complexity&quot;&gt;8. Performance vs. Complexity&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;How do you decide when a performance optimization is worth the additional complexity?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In theory, there's a point where chasing performance stops being worth the engineering complexity.&lt;/p&gt;
&lt;p&gt;But if I'm being completely honest about my own process, I don't always respect that limit, and I'm not going to pretend otherwise just to sound balanced in an interview.&lt;/p&gt;
&lt;p&gt;I have a relentless drive to push code until it literally cannot run any faster.&lt;/p&gt;
&lt;p&gt;As a general rule though, performance optimizations are worth the complexity when they scale linearly with user productivity.&lt;/p&gt;
&lt;p&gt;If an optimization saves a developer five seconds on every single build loop, that's hours of focus reclaimed across a real team every week.&lt;/p&gt;
&lt;p&gt;But if an optimization makes a codebase unmaintainable just to shave two microseconds off an idle thread nobody's even timing, you've crossed into vanity engineering.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Vanity engineering is just ego wearing a lab coat.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Balance comes down to measuring real-world impact over synthetic vanity, every time, no exceptions.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;9-ai-and-low-level-engineering&quot;&gt;9. AI and Low-Level Engineering&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;How do you think AI will affect low-level software engineering?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My stance on this is firm: &lt;strong&gt;AI will never fully master low-level systems engineering&lt;/strong&gt;, and I'll happily die on this hill.&lt;/p&gt;
&lt;p&gt;I might sound provocative, but based on my own experience, LLMs operate at a noticeably lower level of competence in systems programming compared to high-level web development.&lt;/p&gt;
&lt;p&gt;Low-level engineering demands deep spatial reasoning, hardware awareness, and architectural intuition that isn't sitting anywhere in a training set in a usable form.&lt;/p&gt;
&lt;p&gt;I actually tested this.&lt;/p&gt;
&lt;p&gt;I tried using an AI assistant to help build a low-level, cryptographically secure messenger.&lt;/p&gt;
&lt;p&gt;I wasn't asking it to write AES-256-GCM from scratch, I just wanted help with a simple client-server network implementation.&lt;/p&gt;
&lt;p&gt;It failed miserably.&lt;/p&gt;
&lt;p&gt;I spent days fighting hallucinations and sloppy memory handling before scrapping the AI output entirely and writing the whole thing myself, in a fraction of the time it cost me babysitting the assistant.&lt;/p&gt;
&lt;p&gt;AI can fetch docs, summarize a man page, and sound confident doing it.&lt;/p&gt;
&lt;p&gt;But it can't reason about bare-metal constraints the way someone who's actually been burned by them can.&lt;/p&gt;
&lt;p&gt;For low-level devs, understanding C, Assembly, and computer architecture is only going to matter more, not less, because someone still has to fix the broken code AI generates, and that someone needs to actually know what they're looking at.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;10-what-comes-next-for-forgezero-&quot;&gt;10. What Comes Next for ForgeZero?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Where do you want to take ForgeZero next?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My immediate goal is assembling a core team of like-minded developers, engineers who are ready to break things, innovate, and move fast without asking permission for every single decision.&lt;/p&gt;
&lt;p&gt;I want ForgeZero to be completely free, hyper-fast, and genuinely intelligent, possibly integrating lightweight, targeted AI for build-graph optimization down the line, used smartly and sparingly, not bolted on as a marketing checkbox.&lt;/p&gt;
&lt;p&gt;I'm always inviting developers to jump into the repo and build this with me.&lt;/p&gt;
&lt;p&gt;No corporate bureaucracy, no meetings that could've been a commit message, just pure engineering.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;11-the-engineer-behind-the-optimization&quot;&gt;11. The Engineer Behind the Optimization&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Where does your obsession with performance and removing unnecessary complexity come from?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alex:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Honestly, a deep-seated refusal to accept artificial limits, the ones nobody ever actually proved necessary, just inherited from whoever came before.&lt;/p&gt;
&lt;p&gt;Most developers look at a slow tool or a bloated framework and think, well, I guess that's just how software is now, and move on with their day.&lt;/p&gt;
&lt;p&gt;I can't do that.&lt;/p&gt;
&lt;p&gt;When I see an idle loop burning CPU cycles, or a build tool taking 30 seconds to parse a dependency tree that should take 50 milliseconds, it feels like a personal insult.&lt;/p&gt;
&lt;p&gt;Call it perfectionism, call it an obsession with zero overhead.&lt;/p&gt;
&lt;p&gt;I genuinely love taking a bloated process, stripping away the noise, and watching it run at the absolute limit of the hardware.&lt;/p&gt;
&lt;p&gt;That feeling never gets old.&lt;/p&gt;
&lt;p&gt;The day it does is probably the day I stop doing this for a living.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;about-alex-voste&quot;&gt;About Alex Voste&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Alex Voste&lt;/strong&gt; is a developer based in Canada with a focus on systems programming, performance optimization, open source, and developer tooling.&lt;/p&gt;
&lt;p&gt;His technical interests include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;x86_64 Assembly&lt;/li&gt;
&lt;li&gt;NASM&lt;/li&gt;
&lt;li&gt;FASM&lt;/li&gt;
&lt;li&gt;C++&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Performance optimization&lt;/li&gt;
&lt;li&gt;Linkers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alex is currently building &lt;strong&gt;ForgeZero&lt;/strong&gt;, a hardened cross-platform build toolchain for Assembly and C developers.&lt;/p&gt;
&lt;h4 id=&quot;forgezero&quot;&gt;ForgeZero&lt;/h4&gt;
&lt;p&gt;ForgeZero brings together tools including &lt;strong&gt;NASM, GAS, FASM, GCC, Clang, LD, and Zig&lt;/strong&gt; behind a unified workflow for low-level developers.&lt;/p&gt;
&lt;p&gt;You can explore the project here:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/forgezero-cli/forgezero&quot;&gt;https://github.com/forgezero-cli/forgezero&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CoderLegion Profile:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/alexvoste&quot;&gt;https://coderlegion.com/user/alexvoste&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Alex's story is a reminder that software development doesn't have to stop at the abstraction layer.&lt;/p&gt;
&lt;p&gt;For some engineers, the interesting questions begin underneath it:&lt;/p&gt;
&lt;p&gt;What is the machine actually doing?&lt;/p&gt;
&lt;p&gt;Where are the cycles going?&lt;/p&gt;
&lt;p&gt;Why is this process taking so long?&lt;/p&gt;
&lt;p&gt;Which parts of the system are genuinely necessary?&lt;/p&gt;
&lt;p&gt;And perhaps most importantly:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens if we remove everything that doesn't need to be there?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ForgeZero is Alex's answer to those questions — a project built around speed, control, simplicity, security, and a strong dislike of unnecessary overhead.&lt;/p&gt;
&lt;p&gt;Whether you agree with all of Alex's opinions about modern tooling or AI, his approach highlights an important principle for developers at every level:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Understand the system underneath the abstraction.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That understanding becomes even more valuable when the tools themselves start writing the code.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25614/fighting-build-system-complexity-alex-voste-systems-programming-performance-forgezero</guid>
<pubDate>Fri, 28 Aug 2026 12:28:22 +0000</pubDate>
</item>
<item>
<title>Gnat - Self-Hosted Analytics</title>
<link>https://coderlegion.com/24653/gnat-self-hosted-analytics</link>
<description>&lt;p&gt;A few months back I was crawling the web for a side project and came across an analytics platform's data that had exposed users' raw IPs. It wasn't malicious, just careless. But it stuck with me: most people using these tools have no idea their visitors' data is sitting around like that.&lt;/p&gt;
&lt;p&gt;That's the moment that pushed me to build &lt;strong&gt;Gnat&lt;/strong&gt;, a self-hosted analytics tool that doesn't cut corners on privacy, but also doesn't force you into a painful setup to get it.&lt;/p&gt;
&lt;h3 id=&quot;no-third-party-no-phoning-home&quot;&gt;No third party, no phoning home&lt;/h3&gt;
&lt;p&gt;Gnat runs entirely on infrastructure you control. No third party in the loop, no API calling home anywhere, no data leaving your server. Completely free and open source.&lt;/p&gt;
&lt;h3 id=&quot;one-binary-zero-bloat&quot;&gt;One binary, zero bloat&lt;/h3&gt;
&lt;p&gt;Most self-hosted analytics tools make you choose: something lightweight with barely any real analysis, or something with real depth that also means Docker, a multi-service stack, and its own ops burden.&lt;/p&gt;
&lt;p&gt;Gnat is a single Go binary. No CGO, no Docker, no multi-service stack. The dashboard is compiled straight into the executable.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tar xzf gnat-vX.Y.Z-linux-amd64.tar.gz
cd gnat-vX.Y.Z-linux-amd64
./gnat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the whole install.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://gnat-np2m.onrender.com/dashboard&quot;&gt;https://gnat-np2m.onrender.com/dashboard&lt;/a&gt; (password: &lt;code&gt;vJzTMw0bjTtWHyNpBCPGaZqE&lt;/code&gt;, feel free to poke around, it's a shared demo instance so please be gentle with it)&lt;/p&gt;
&lt;p&gt;Visit my portfolio here and see it appear on the analytics dashboard &lt;a rel=&quot;nofollow&quot; href=&quot;https://var-raphael.vercel.app&quot;&gt;https://var-raphael.vercel.app&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;real-analytical-depth&quot;&gt;Real analytical depth&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pageviews and custom events via JSON POST, no per-language SDKs&lt;/li&gt;
&lt;li&gt;Funnels, cohorts, retention curves&lt;/li&gt;
&lt;li&gt;Auto-discovered path analysis&lt;/li&gt;
&lt;li&gt;SQLite by default, or Postgres/MySQL if that's how you already run infrastructure&lt;/li&gt;
&lt;li&gt;CSV, JSON, and raw SQL export, it's your data&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;built-for-the-agent-era&quot;&gt;Built for the agent era&lt;/h3&gt;
&lt;p&gt;Gnat ships with a native MCP server. Claude or any MCP-compatible agent can query your analytics directly, no separate API glue.&lt;/p&gt;
&lt;h3 id=&quot;where-it-s-at&quot;&gt;Where it's at&lt;/h3&gt;
&lt;p&gt;Early days, v0.1.0, and I'm actively building on it. Multi-site support, SSO/team access, and managed hosting are on the roadmap but not built yet.&lt;/p&gt;
&lt;p&gt;Licensed under AGPLv3, free to self-host and modify for any purpose, including commercially, but if you run a modified version as a service for others, the source has to stay open under the same terms.&lt;/p&gt;
&lt;p&gt;I'd love feedback, issues, and contributors, especially if you've felt the same Docker-and-multi-service fatigue trying to self-host analytics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/var-raphael/Gnat&quot;&gt;https://github.com/var-raphael/Gnat&lt;/a&gt;&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/24653/gnat-self-hosted-analytics</guid>
<pubDate>Fri, 14 Aug 2026 19:45:17 +0000</pubDate>
</item>
<item>
<title>Ninja is Slow. We Built a Build System in Go That Outperforms It by 39x on a Quarter-Million Tasks</title>
<link>https://coderlegion.com/21836/ninja-is-slow-we-built-build-system-in-go-that-outperforms-it-by-39x-on-quarter-million-tasks</link>
<description>&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2975149926680001067&quot; alt=&quot;ForgeZero Speed Pipeline&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;forgezero-2-0-we-built-a-build-system-39x-faster-than-ninja&quot;&gt;ForgeZero 2.0: We Built a Build System 39x Faster Than Ninja&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;And yes, it's open source. No bullshit, just architecture.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-everyone-ignores&quot;&gt;The Problem Everyone Ignores&lt;/h3&gt;
&lt;p&gt;You've been there. Sitting at your desk, coffee getting cold, waiting for your build to finish. 10 seconds. 30 seconds. A minute.&lt;/p&gt;
&lt;p&gt;CMake parses its DSL. Ninja forks 2,000 processes. Make thrashes the disk. GNU Autotools exists, and everyone pretends not to notice.&lt;/p&gt;
&lt;p&gt;This isn't a feature gap. This is &lt;strong&gt;architectural malpractice&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Every major build system was designed in an era when parallelism meant &quot;spawn processes.&quot; That's it. No thought given to the actual cost—fork syscalls, context switching, memory duplication, I/O bottlenecks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We decided to fix it properly.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-forgezero-actually-is&quot;&gt;What ForgeZero Actually Is&lt;/h3&gt;
&lt;p&gt;ForgeZero is a cross-platform build system for C, C++, Objective-C, and Assembly. Written in Go. Single binary. No configuration files needed.&lt;/p&gt;
&lt;p&gt;Point it at a directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz build -o binary
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It detects your source files, selects compilers (GCC, Clang, Zig), picks assemblers (NASM, GAS, FASM, or embedded), and outputs production-ready binaries, libraries, or ISO images.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That's literally it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No Makefile. No CMakeLists.txt. No autotools scripts. One command. Full parallelism. Reproducible builds by default.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/forgezero-cli/forgezero&quot;&gt;https://github.com/forgezero-cli/forgezero&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;License:&lt;/strong&gt; GPLv3 (fully open source)&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-benchmark-that-broke-ninja-s-narrative&quot;&gt;The Benchmark That Broke Ninja's Narrative&lt;/h3&gt;
&lt;p&gt;Real project. 2,000 object files. 1,000 .c source files. Real hardware.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ForgeZero:  258 ms  (User: 348ms, System: 520ms)
Ninja:      10.1 s  (User: 73.9s, System: 47.8s)

Speedup: 39.2x ✓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn't synthetic. This isn't a contrived test case. This is what happens when you link 2,000 real object files on a real codebase.&lt;/p&gt;
&lt;p&gt;The gap &lt;strong&gt;grows with scale:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;File Count &lt;/th&gt;&lt;th&gt;ForgeZero &lt;/th&gt;&lt;th&gt;Ninja &lt;/th&gt;&lt;th&gt;Speedup&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1,000     &lt;/td&gt;&lt;td&gt;~85ms     &lt;/td&gt;&lt;td&gt;~2.7s &lt;/td&gt;&lt;td&gt;32x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2,000     &lt;/td&gt;&lt;td&gt;~258ms    &lt;/td&gt;&lt;td&gt;~10.1s&lt;/td&gt;&lt;td&gt;39x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10,000    &lt;/td&gt;&lt;td&gt;~1.2s     &lt;/td&gt;&lt;td&gt;~50s  &lt;/td&gt;&lt;td&gt;~42x&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ninja's overhead scales &lt;strong&gt;linearly&lt;/strong&gt; with file count. ForgeZero's scales &lt;strong&gt;logarithmically&lt;/strong&gt;. The larger your codebase, the more you win.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watch the actual benchmark:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://t.me/wienton/176&quot;&gt;https://t.me/wienton/176&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-demolishes-every-competitor&quot;&gt;Why This Demolishes Every Competitor&lt;/h3&gt;
&lt;h4 id=&quot;1-architecture-one-process-infinite-goroutines&quot;&gt;1. Architecture: One Process, Infinite Goroutines&lt;/h4&gt;
&lt;p&gt;Traditional systems:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;foreach file:
  fork() → new process
  compile
  wait for completion
  write .o to disk
  read .o back for linking
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ForgeZero:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spawn N goroutines (microseconds, not milliseconds)
all 2000 files compile in parallel
all objects stay in memory
linker reads from memory arena
zero context switching overhead
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The difference:&lt;/strong&gt; Goroutine creation = microseconds. Process fork = milliseconds. At 2,000 files, you're talking about &lt;strong&gt;1-2 seconds&lt;/strong&gt; saved just on process overhead.&lt;/p&gt;
&lt;h4 id=&quot;2-zero-allocations-in-hot-paths&quot;&gt;2. Zero Allocations in Hot Paths&lt;/h4&gt;
&lt;p&gt;This is the part other build systems don't even think about.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BenchmarkLinkerHotPath:  0 allocs/op, 0 B/op
BenchmarkCodegenHotPath: 0 allocs/op, 0 B/op
BenchmarkHashBLAKE3:     0 allocs/op, 0 B/op
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;No garbage collection pauses.&lt;/strong&gt; No &quot;stop-the-world&quot; events. Deterministic latency like C or Rust.&lt;/p&gt;
&lt;p&gt;Achieved through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pre-allocated arenas for intermediate objects&lt;/li&gt;
&lt;li&gt;Stack buffers for I/O operations&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync.Pool&lt;/code&gt; for temporary structures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero &lt;code&gt;make()&lt;/code&gt;, &lt;code&gt;append()&lt;/code&gt;, or &lt;code&gt;new()&lt;/code&gt; in critical sections&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you're linking thousands of files, GC pauses compound. We eliminated them entirely.&lt;/p&gt;
&lt;h4 id=&quot;3-memory-resident-object-cache&quot;&gt;3. Memory-Resident Object Cache&lt;/h4&gt;
&lt;p&gt;Ninja writes every &lt;code&gt;.o&lt;/code&gt; file to disk, then reads it back. Disk I/O at SSD speeds is 500 MB/sec.&lt;/p&gt;
&lt;p&gt;ForgeZero keeps objects in memory: &lt;strong&gt;10+ GB/sec throughput.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;On a 2000-file project, this alone saves &lt;strong&gt;200-300ms&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;4-blake3-instead-of-sha-256&quot;&gt;4. BLAKE3 Instead of SHA-256&lt;/h4&gt;
&lt;p&gt;Caching depends on file hashes. SHA-256 is cryptographically solid but slow.&lt;/p&gt;
&lt;p&gt;BLAKE3 is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;7x faster&lt;/strong&gt; than SHA-256&lt;/li&gt;
&lt;li&gt;Parallel (SIMD accelerated)&lt;/li&gt;
&lt;li&gt;Cryptographically modern&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On 2000 files: SHA-256 (~500ms) vs BLAKE3 (~70ms). That's &lt;strong&gt;430ms saved per rebuild.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;5-custom-lock-free-scheduler&quot;&gt;5. Custom Lock-Free Scheduler&lt;/h4&gt;
&lt;p&gt;ForgeZero doesn't rely on the OS scheduler. It implements its own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Mutex&lt;/code&gt;, &lt;code&gt;Semaphore&lt;/code&gt;, &lt;code&gt;WaitGroup&lt;/code&gt; (no external dependencies)&lt;/li&gt;
&lt;li&gt;Lock-free ring buffer for task queues (CAS atomics)&lt;/li&gt;
&lt;li&gt;Worker pool with goroutine reuse&lt;/li&gt;
&lt;li&gt;Atomic counters (no mutex contention)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even under load, zero contention. Maximum throughput.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-new-in-forgezero-2-0-v5-3-0-&quot;&gt;What's New in ForgeZero 2.0 (v5.3.0)&lt;/h3&gt;
&lt;h4 id=&quot;embedded-x86-assembler&quot;&gt;Embedded x86 Assembler&lt;/h4&gt;
&lt;p&gt;NASM is gone. ForgeZero now includes a native Intel-syntax assembler that generates correct ELF64 object files.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One dependency eliminated&lt;/li&gt;
&lt;li&gt;Works on all platforms (Linux, Windows, macOS)&lt;/li&gt;
&lt;li&gt;Same behavior everywhere&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;iso-image-generation&quot;&gt;ISO Image Generation&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz build -iso -iso-out kernel.iso
qemu-system-x86_64 -cdrom kernel.iso
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For kernel and firmware developers: &lt;strong&gt;no more mkisofs scripts.&lt;/strong&gt; One flag. Bootable ISO. Done.&lt;/p&gt;
&lt;p&gt;Support for &lt;code&gt;-iso-hybrid&lt;/code&gt; (BIOS + UEFI).&lt;/p&gt;
&lt;h4 id=&quot;three-cache-modes&quot;&gt;Three Cache Modes&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz build                  # disk mode (default, persistent)
fz build -cache=ram       # memory mode (CI/CD, ephemeral envs)
fz build -cache=off       # no cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perfect for Docker containers where disk is slow or ephemeral.&lt;/p&gt;
&lt;h4 id=&quot;inline-bash-scripts&quot;&gt;Inline Bash Scripts&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;scripts:
  - bash: echo &quot;Building...&quot;
  - bash: ./configure --prefix=/usr
  - bash: make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If no shell exists, uses embedded minimal runner (cd, export, direct binary calls).&lt;/p&gt;
&lt;h4 id=&quot;rollback-support&quot;&gt;Rollback Support&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz --rollback              # Revert to previous version
fz --rollback-to v5.2.0    # Revert to specific version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Production-grade versioning. If something breaks, you recover in seconds.&lt;/p&gt;
&lt;h4 id=&quot;rewritten-scheduler&quot;&gt;Rewritten Scheduler&lt;/h4&gt;
&lt;p&gt;Removed external workerpool dependency. Built custom with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Better error aggregation&lt;/li&gt;
&lt;li&gt;Context-based cancellation&lt;/li&gt;
&lt;li&gt;Graceful shutdown&lt;/li&gt;
&lt;li&gt;Full race condition safety&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;rewritten-config-system&quot;&gt;Rewritten Config System&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Low-allocation merging (no unnecessary copies)&lt;/li&gt;
&lt;li&gt;Parallel config loading&lt;/li&gt;
&lt;li&gt;Clear merge semantics (strings override, slices deduplicate, maps merge)&lt;/li&gt;
&lt;li&gt;Safe defaults with validation&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;cross-platform-reality&quot;&gt;Cross-Platform Reality&lt;/h3&gt;
&lt;p&gt;Single codebase. Three platforms. Identical behavior.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linux:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Direct syscalls via &lt;code&gt;golang.org/x/sys/unix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SYS_OPENAT&lt;/code&gt;, &lt;code&gt;SYS_UNLINKAT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;No libc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native implementation via &lt;code&gt;golang.org/x/sys/windows&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CreateFile&lt;/code&gt;, &lt;code&gt;ReadFile&lt;/code&gt;, &lt;code&gt;WriteFile&lt;/code&gt; APIs&lt;/li&gt;
&lt;li&gt;Retry logic for file locks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full Darwin syscall support&lt;/li&gt;
&lt;li&gt;Identical behavior to Linux&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;VFS Layer&lt;/strong&gt; abstracts all OS differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Path/separator normalization&lt;/li&gt;
&lt;li&gt;TOCTOU attack prevention&lt;/li&gt;
&lt;li&gt;Symlink boundary enforcement (security by default)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;built-in-security-not-bolted-on-&quot;&gt;Built-In Security (Not Bolted On)&lt;/h3&gt;
&lt;h4 id=&quot;fz-audit-sast-scanner&quot;&gt;fz audit — SAST Scanner&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz audit              # Scan for all issues
fz audit --strict     # Strict mode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Detects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Secrets (AWS keys, GitHub tokens, API keys)&lt;/li&gt;
&lt;li&gt;Dangerous patterns (gets, sprintf without bounds, alloca in loops)&lt;/li&gt;
&lt;li&gt;License violations (GPL/AGPL in proprietary projects)&lt;/li&gt;
&lt;li&gt;Known CVEs in dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;fz-sbom-software-bill-of-materials&quot;&gt;fz sbom — Software Bill of Materials&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz sbom --output sbom.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CycloneDX format with BLAKE3 hashes for every component. Essential for aerospace, medical, and fintech systems.&lt;/p&gt;
&lt;h4 id=&quot;fz-verify-source-integrity&quot;&gt;fz verify — Source Integrity&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz verify --manifest .fz.manifest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Checks every file against BLAKE3 manifest. Detects corruption and supply chain attacks.&lt;/p&gt;
&lt;h4 id=&quot;--reproducible-bit-for-bit-reproducibility&quot;&gt;--reproducible — Bit-for-Bit Reproducibility&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz build --reproducible
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two builds of identical code produce &lt;strong&gt;identical binaries&lt;/strong&gt; (down to the last byte):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build ID suppressed&lt;/li&gt;
&lt;li&gt;Timestamps zeroed&lt;/li&gt;
&lt;li&gt;Paths normalized&lt;/li&gt;
&lt;li&gt;Order deterministic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verifiable that no one modified your source.&lt;/p&gt;
&lt;h4 id=&quot;fz-doctor-environment-diagnostics&quot;&gt;fz doctor — Environment Diagnostics&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compiler availability and versions&lt;/li&gt;
&lt;li&gt;File access permissions&lt;/li&gt;
&lt;li&gt;Platform integrity&lt;/li&gt;
&lt;li&gt;Required dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Human-readable report of what works and what doesn't.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;supported-everything&quot;&gt;Supported Everything&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Category &lt;/th&gt;&lt;th&gt;Details&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Languages &lt;/td&gt;&lt;td&gt;C, C++, Objective-C, Assembly (NASM/GAS/FASM), Gloria (custom bare-metal language)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Output &lt;/td&gt;&lt;td&gt;ELF, PE, Mach-O, static libraries, shared libraries, flat binaries, ISO images&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cross-Compilation &lt;/td&gt;&lt;td&gt;ARM (32/64), RISC-V, WebAssembly, Windows, full support via Zig toolchain&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tools &lt;/td&gt;&lt;td&gt;&lt;code&gt;-compile-commands&lt;/code&gt; (LSP), &lt;code&gt;-watch&lt;/code&gt; (file monitoring), &lt;code&gt;-shell&lt;/code&gt; (REPL), &lt;code&gt;-bench&lt;/code&gt; (profiling), &lt;code&gt;-debug&lt;/code&gt; (DWARF symbols)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Advanced &lt;/td&gt;&lt;td&gt;Bare-metal linking, custom linker scripts, freestanding environments, BIOS+UEFI support&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;package-manager-fz-pm-&quot;&gt;Package Manager (fz pm)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz pm add &amp;lt;repo&amp;gt;[@version]    # Add from Git
fz pm remove &amp;lt;package&amp;gt;         # Remove
fz pm list                     # List installed
fz pm catalog                  # Browse official catalog
fz pm search &amp;lt;query&amp;gt;           # Search
fz pm install &amp;lt;name&amp;gt;           # Install with BLAKE3 verification
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All packages verified with BLAKE3 before installation. &lt;strong&gt;No npm dependency hell.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-numbers-don-t-lie&quot;&gt;The Numbers Don't Lie&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;976 commits&lt;/strong&gt; in 2 months (16 commits/day—this is sprint-grade work)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;42,048 lines of code&lt;/strong&gt; (32,275 Go, rest tests + docs)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;234 Go files&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;90%+ test coverage&lt;/strong&gt; on critical packages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 allocations&lt;/strong&gt; in hot paths (benchmarked)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100% golangci-lint&lt;/strong&gt; in strict mode&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Passes all -race checks&lt;/strong&gt; (no data races)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Works on Linux, Windows, macOS&lt;/strong&gt; with zero code changes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is not fantasy. This is production-grade code.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;comparison-the-reality-check&quot;&gt;Comparison: The Reality Check&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature &lt;/th&gt;&lt;th&gt;Make &lt;/th&gt;&lt;th&gt;Ninja &lt;/th&gt;&lt;th&gt;CMake &lt;/th&gt;&lt;th&gt;ForgeZero&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Config files &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Caching &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;3 modes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Parallelism &lt;/td&gt;&lt;td&gt;Processes &lt;/td&gt;&lt;td&gt;Processes &lt;/td&gt;&lt;td&gt;Processes &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Goroutines&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Zero-alloc paths &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Embedded assembler &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ISO images &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Package manager &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SAST/SBOM &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cross-compilation &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Full&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Speed&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Baseline &lt;/td&gt;&lt;td&gt;Fast &lt;/td&gt;&lt;td&gt;Medium &lt;/td&gt;&lt;td&gt;&lt;strong&gt;39x Ninja&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Windows native &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rollback support &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reproducible builds &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;One system dominates every dimension.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;system-requirements&quot;&gt;System Requirements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OS:&lt;/strong&gt; Linux, Windows (native or WSL2), macOS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Architecture:&lt;/strong&gt; amd64, arm64&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional:&lt;/strong&gt; GCC/Clang (C/C++), Zig (cross-toolchain), Git (package manager)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For ISO:&lt;/strong&gt; xorriso (separate installation)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything else is &lt;strong&gt;embedded in a single binary.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-matters-the-reality&quot;&gt;Why This Matters: The Reality&lt;/h3&gt;
&lt;h4 id=&quot;speed-changes-workflow&quot;&gt;Speed Changes Workflow&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Before: 10 seconds per rebuild = context lost
After:  250 milliseconds per rebuild = never interrupt flow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn't productivity theater. This is &lt;strong&gt;fundamental work process improvement.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;On a 50-engineer team, if each engineer rebuilds 50 times per day:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ninja:      10s × 50 builds × 50 engineers = 41,666 seconds wasted
ForgeZero:  0.25s × 50 builds × 50 engineers = 1,041 seconds wasted

Saved: 40,625 seconds/day = 11.3 person-hours/day
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;That's literally one full-time engineer's output, reclaimed.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;no-config-no-errors&quot;&gt;No Config = No Errors&lt;/h4&gt;
&lt;p&gt;CMakeLists.txt, Makefile, autotools—these are programming languages with their own syntax, semantics, and pitfalls.&lt;/p&gt;
&lt;p&gt;ForgeZero needs &lt;strong&gt;zero configuration&lt;/strong&gt; to work. YAML if you want customization. That's it.&lt;/p&gt;
&lt;p&gt;New developers don't spend a day figuring out the build system. They &lt;code&gt;fz build&lt;/code&gt;. Done.&lt;/p&gt;
&lt;h4 id=&quot;systems-work-is-irreplaceable&quot;&gt;Systems Work Is Irreplaceable&lt;/h4&gt;
&lt;p&gt;This tool exists because systems programming is &lt;strong&gt;fundamentally different.&lt;/strong&gt; You can't CMake your way out of bare-metal development.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kernel developers need embedded assembly → we built it in&lt;/li&gt;
&lt;li&gt;Firmware developers need ISO images → integrated&lt;/li&gt;
&lt;li&gt;Security-critical projects need SBOM and reproducible builds → bundled&lt;/li&gt;
&lt;li&gt;Embedded devs need cross-compilation → fully supported&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We're not trying to replace Gradle or Maven. We're building the &lt;strong&gt;right tool for systems work.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-roadmap&quot;&gt;The Roadmap&lt;/h3&gt;
&lt;p&gt;We're not done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux Kbuild support for kernel compilation&lt;/li&gt;
&lt;li&gt;Enhanced IDE integration (VSCode, CLion)&lt;/li&gt;
&lt;li&gt;Gloria language expansion (custom systems language)&lt;/li&gt;
&lt;li&gt;Further performance optimization (targeting 50x+ speedup)&lt;/li&gt;
&lt;li&gt;Additional architecture support (PowerPC, MIPS)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is a living project.&lt;/strong&gt; 976 commits in 2 months. Not a one-off. Not a side project. Real engineering work.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;who-this-is-for&quot;&gt;Who This Is For&lt;/h3&gt;
&lt;h4 id=&quot;you-re-a-systems-programmer&quot;&gt;You're a Systems Programmer&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Building kernels, bootloaders, or firmware&lt;/li&gt;
&lt;li&gt;Need deterministic, reproducible builds&lt;/li&gt;
&lt;li&gt;Want security integrated, not bolted on&lt;/li&gt;
&lt;li&gt;Tired of Makefile spaghetti&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is for you.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;you-manage-a-build-infrastructure&quot;&gt;You Manage a Build Infrastructure&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Your CI/CD pipeline is 60% waiting for builds&lt;/li&gt;
&lt;li&gt;You want to scale without renting more cloud resources&lt;/li&gt;
&lt;li&gt;You need reproducible, verifiable builds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This saves money and time.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;you-care-about-simplicity&quot;&gt;You Care About Simplicity&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;You believe configuration is a source of bugs&lt;/li&gt;
&lt;li&gt;You want tools that work zero-config and scale up&lt;/li&gt;
&lt;li&gt;You respect your developers' time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This respects your choices.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;get-started&quot;&gt;Get Started&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Linux/macOS
curl -sSL https://raw.githubusercontent.com/forgezero-cli/ForgeZero/main/install.sh | bash

# First build
fz build -o my_program

# Watch the speed difference
touch src/main.c &amp;amp;&amp;amp; fz build -o my_program
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Follow updates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://t.me/wienton/176&quot;&gt;https://t.me/wienton/176&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-statement&quot;&gt;The Statement&lt;/h3&gt;
&lt;p&gt;ForgeZero isn't an improvement on Ninja. It's not an optimization of Make. It's &lt;strong&gt;a complete rewrite&lt;/strong&gt; of what a build system should be, informed by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modern hardware (multi-core, SIMD, large memory)&lt;/li&gt;
&lt;li&gt;Modern languages (Go, with its goroutines and concurrency primitives)&lt;/li&gt;
&lt;li&gt;Real systems work (assembly, bare-metal, cross-compilation)&lt;/li&gt;
&lt;li&gt;Security best practices (reproducible builds, SBOM, SAST)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is a challenge to the industry:&lt;/strong&gt; Slow builds aren't inevitable. They're &lt;strong&gt;architectural choices.&lt;/strong&gt; Fix the architecture, and speed fixes itself.&lt;/p&gt;
&lt;p&gt;If you're tired of waiting for builds. If you're exhausted by CMake configuration. If you just want to &lt;strong&gt;code, not configure&lt;/strong&gt;—ForgeZero is for you.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-details&quot;&gt;The Details&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/alexvoste&quot;&gt;Alex Voste&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/forgezero-cli/forgezero&quot;&gt;https://github.com/forgezero-cli/forgezero&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Latest Release:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/forgezero-cli/forgezero/releases/tag/v5.3.0&quot;&gt;v5.3.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; GPLv3 (fully open source, fully free)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Fast builds aren't a luxury. They're an engineering necessity. ForgeZero delivers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make it so.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21836/ninja-is-slow-we-built-build-system-in-go-that-outperforms-it-by-39x-on-quarter-million-tasks</guid>
<pubDate>Fri, 03 Jul 2026 13:10:46 +0000</pubDate>
</item>
<item>
<title>Why I chose a Tiered Caching strategy for PixoraCloud.</title>
<link>https://coderlegion.com/21337/why-i-chose-a-tiered-caching-strategy-for-pixoracloud</link>
<description>&lt;p&gt;I’m deep in the lab engineering the Pixora transformation engine (using libvips + Go), and I had to make a call on caching: Redis vs. Local Disk.&lt;br&gt;
Common mistake: Using Redis for everything.&lt;br&gt;
Reality: RAM is fast, but RAM is expensive. If I cache every transformed image in Redis, the server bill will bankrupt the project before we even hit Beta.&lt;/p&gt;
&lt;p&gt;My Solution: The Two-Tiered Architecture.&lt;br&gt;
L1 (Redis): Stores only the &quot;Hot&quot; assets (the most requested thumbnails/avatars). This keeps our sub-120ms latency promise for 90% of requests.&lt;br&gt;
L2 (Disk/Object Storage): Stores the processed, high-res versions. If it's not in Redis, we grab it here.&lt;br&gt;
The Trade-off:&lt;br&gt;
Redis gives us the raw speed to hit sub-50ms lookups.&lt;br&gt;
Disk storage keeps our operating costs lean so we can pass those savings to you.&lt;br&gt;
We aren't just building a CDN; we’re building infrastructure that has to survive in a low-bandwidth, high-latency market.&lt;br&gt;
Building in public is all about these trade-offs. What would you have chosen? Let’s talk architecture in the comments. 👇&lt;/p&gt;
&lt;p&gt;Join waitlist &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixoracloud.pxxl.click&quot;&gt;https://pixoracloud.pxxl.click&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21337/why-i-chose-a-tiered-caching-strategy-for-pixoracloud</guid>
<pubDate>Wed, 24 Jun 2026 20:02:29 +0000</pubDate>
</item>
<item>
<title>Go types explained: the foundation you need before writing HTTP handlers</title>
<link>https://coderlegion.com/21148/go-types-explained-the-foundation-you-need-before-writing-http-handlers</link>
<description>&lt;h2 id=&quot;a-quick-introduction-to-go-types&quot;&gt;A quick introduction to Go types&lt;/h2&gt;
&lt;p&gt;According to &lt;a rel=&quot;nofollow&quot; href=&quot;https://go.dev/ref/spec#Types&quot;&gt;https://go.dev/ref/spec#Types&lt;/a&gt;, in Go, a type specifies a set of values, along with operations and methods specific to those values.&lt;/p&gt;
&lt;p&gt;This is something fundamental about Go: &lt;strong&gt;you can define your own types based on existing ones&lt;/strong&gt;, and those types can have methods attached to them.&lt;/p&gt;
&lt;p&gt;You've probably seen this with structs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// A struct type — groups fields together
type User struct {
    Name  string
    Email string
}

// A method attached to the User type
func (u User) Greet() string {
    return &quot;Hello, &quot; + u.Name
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But Go lets you do the same thing with &lt;strong&gt;any&lt;/strong&gt; type, including functions.&lt;/p&gt;
&lt;h3 id=&quot;function-types&quot;&gt;Function types&lt;/h3&gt;
&lt;p&gt;In Go, functions are values. You can assign them to variables, pass them as arguments, and — most importantly here, &lt;strong&gt;define a named type based on a function signature&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This defines a new type called &quot;Greeter&quot;
// whose underlying type is a function that takes a string and returns a string.
type Greeter func(name string) string
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;Greeter&lt;/code&gt; is a type, just like &lt;code&gt;int&lt;/code&gt; or &lt;code&gt;string&lt;/code&gt; or &lt;code&gt;User&lt;/code&gt;. Any function with the right signature (&lt;code&gt;func(string) string&lt;/code&gt;) is automatically a &lt;code&gt;Greeter&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This function matches the Greeter signature
func sayHello(name string) string {
    return &quot;Hello, &quot; + name
}

// So we can assign it to a Greeter variable
var g Greeter = sayHello

fmt.Println(g(&quot;Alice&quot;)) // Hello, Alice
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;attaching-methods-to-a-function-type&quot;&gt;Attaching methods to a function type&lt;/h4&gt;
&lt;p&gt;Here's where it gets interesting. Because &lt;code&gt;Greeter&lt;/code&gt; is now a named type, &lt;strong&gt;you can attach methods to it&lt;/strong&gt;, exactly like you would with a struct:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// A method on a function type — this is valid Go
func (g Greeter) Shout(name string) string {
    return strings.ToUpper(g(name)) // call the function, then shout it
}

fmt.Println(g.Shout(&quot;Alice&quot;)) // HELLO, ALICE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This might look strange at first. A function with a method? But it makes perfect sense in Go. &lt;code&gt;Greeter&lt;/code&gt; is just a type, and any type can have methods.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-this-matters-for-http-handlers&quot;&gt;Why this matters for HTTP handlers&lt;/h2&gt;
&lt;p&gt;Now that you understand function types, let's look at how Go's HTTP package uses this exact pattern to make your life easier.&lt;/p&gt;
&lt;p&gt;Go's HTTP package has two things that are easy to confuse at first: &lt;code&gt;http.Handler&lt;/code&gt; and &lt;code&gt;http.HandlerFunc&lt;/code&gt;. They sound almost identical, but they play different roles: one is an interface, and one is a type.&lt;/p&gt;
&lt;h3 id=&quot;http-handler-the-interface&quot;&gt;http.Handler — the interface&lt;/h3&gt;
&lt;p&gt;An HTTP handler in GO is an object or function responsible for processing an incoming HTTP request and constructing an HTTP response.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http.Handler&lt;/code&gt; is the interface that represents anything that can handle an HTTP request. It has exactly one method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// http.Handler is an interface — a contract.
// It says: &quot;I don't care what you are.
// As long as you have a ServeHTTP method, you can handle HTTP requests.&quot;
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;http.ResponseWriter&lt;/code&gt;: An interface used to construct and send the HTTP response (setting headers, status codes, and writing the body).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;*http.Request&lt;/code&gt;: A pointer to a struct containing all incoming data (headers, query parameters, URL path, and request body).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, to be recognized as a handler, a type must implement the &lt;code&gt;http.Handler&lt;/code&gt; interface by defining a &lt;code&gt;ServeHTTP&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is what Go's HTTP server actually works with internally. When you call &lt;code&gt;http.ListenAndServe&lt;/code&gt;, the second argument must be an &lt;code&gt;http.Handler&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// The second argument is an http.Handler —
// Go's server calls ServeHTTP on it for every incoming request.
http.ListenAndServe(&quot;:8080&quot;, myHandler)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any type that has a &lt;code&gt;ServeHTTP&lt;/code&gt; method satisfies this interface automatically. A struct, a function type, anything, as long as it has that one method.&lt;/p&gt;
&lt;h3 id=&quot;http-handlerfunc-the-type&quot;&gt;http.HandlerFunc — the type&lt;/h3&gt;
&lt;p&gt;Here's the problem: writing a full struct with a &lt;code&gt;ServeHTTP&lt;/code&gt; method for every single handler in your app is verbose. Most of the time, you just want to write a plain function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This is clean and simple, just a function.
// But it's NOT an http.Handler because it has no ServeHTTP method.
func greetUser(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, &quot;Hello!&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function can't be passed to &lt;code&gt;http.ListenAndServe&lt;/code&gt; directly, it's not an &lt;code&gt;http.Handler&lt;/code&gt;. It has no &lt;code&gt;ServeHTTP&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;This is exactly the problem &lt;code&gt;http.HandlerFunc&lt;/code&gt; solves. It's a named function type with a &lt;code&gt;ServeHTTP&lt;/code&gt; method attached, which makes it an &lt;code&gt;http.Handler&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// HandlerFunc is a named type based on a function signature.
// Any function that matches func(ResponseWriter, *Request)
// is automatically compatible with this type.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP is attached to HandlerFunc.
// It just calls the function itself — f is the function, so f(w, r) runs it.
// This single method is what makes HandlerFunc satisfy http.Handler.
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now Go can convert your plain function into an &lt;code&gt;http.HandlerFunc&lt;/code&gt;, which has &lt;code&gt;ServeHTTP&lt;/code&gt;, which satisfies &lt;code&gt;http.Handler&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can imagine that this is what Go does under the hood:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;var fn http.HandlerFunc = greetUser  // convert the function to HandlerFunc
var h  http.Handler    = fn          // HandlerFunc satisfies http.Handler
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But, in practice, you never write these steps manually; you use the &lt;code&gt;http.HandleFunc&lt;/code&gt; function.&lt;/p&gt;
&lt;h4 id=&quot;http-handlefunc&quot;&gt;http.HandleFunc&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// Registers the &quot;/hello&quot; route on the default mux.
// http.HandleFunc converts greetUser to HandlerFunc internally —
// you never see that conversion happen.
http.HandleFunc(&quot;/hello&quot;, greetUser)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;http.HandleFunc&lt;/code&gt; register a route on the default server mux. It takes a path and a handler function, converts the function to a &lt;code&gt;HandlerFunc&lt;/code&gt; internally, and registers it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http.HandleFunc&lt;/code&gt; actually uses &lt;code&gt;http.HandlerFunc&lt;/code&gt; internally, it's just hiding it from you:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This is roughly what http.HandleFunc does under the hood:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    // converts your function to HandlerFunc, then registers it
    DefaultServeMux.Handle(pattern, HandlerFunc(handler))
    //                                ^^^^^^^^^^^^^
    //                                HandlerFunc TYPE doing the conversion
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So &lt;code&gt;http.HandleFunc&lt;/code&gt; is convenience on top of &lt;code&gt;http.HandlerFunc&lt;/code&gt;. When you call &lt;code&gt;http.HandleFunc(&quot;/hello&quot;, greetUser)&lt;/code&gt;, Go silently wraps &lt;code&gt;greetUser&lt;/code&gt; in an &lt;code&gt;http.HandlerFunc&lt;/code&gt; and registers it. You only need to use &lt;code&gt;http.HandlerFunc&lt;/code&gt; directly when you're building something lower-level, like middleware, where you need to return an &lt;code&gt;http.Handler&lt;/code&gt; explicitly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;Here's the full picture in one example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// A plain function — clean and simple to write
func greetUser(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, &quot;Hello!&quot;)
}

// A struct handler — useful when you need to hold state like a DB client
type UserHandler struct {
    db *sql.DB // injected dependency
}

func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // can use h.db here
    fmt.Fprintln(w, &quot;Hello from UserHandler!&quot;)
}

func main() {
    mux := http.NewServeMux()

    // http.HandleFunc converts greetUser → HandlerFunc → Handler automatically
    mux.HandleFunc(&quot;/hello&quot;, greetUser)

    // UserHandler already has ServeHTTP — it satisfies http.Handler directly
    mux.Handle(&quot;/user&quot;, &amp;amp;UserHandler{db: myDB})

    // Both routes are registered on the same mux —
    // the server calls ServeHTTP on whichever one matches the request path.
    http.ListenAndServe(&quot;:8080&quot;, mux)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;TIP: Use a plain function (via &lt;code&gt;HandlerFunc&lt;/code&gt;) for simple handlers. Use a struct for handlers that need dependencies like a database client or a config object. The server doesn't care which one you chose — it just calls &lt;code&gt;ServeHTTP&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Think of it this way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;http.Handler&lt;/code&gt; is the &lt;strong&gt;job description&lt;/strong&gt;: &lt;em&gt;&quot;must be able to handle HTTP requests&quot;&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;http.HandlerFunc&lt;/code&gt; is one &lt;strong&gt;way to fulfill&lt;/strong&gt; that job description: &lt;em&gt;&quot;a plain function that handles HTTP requests&quot;&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your struct with a &lt;code&gt;ServeHTTP&lt;/code&gt; method is another way to fulfill it&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The server only knows about &lt;code&gt;http.Handler&lt;/code&gt;. It never knows whether you used a struct or a &lt;code&gt;HandlerFunc&lt;/code&gt; — and it doesn't need to. That's the power of the interface.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21148/go-types-explained-the-foundation-you-need-before-writing-http-handlers</guid>
<pubDate>Mon, 22 Jun 2026 14:44:03 +0000</pubDate>
</item>
<item>
<title>Why I Still Choose Go After All These Years</title>
<link>https://coderlegion.com/20174/why-i-still-choose-go-after-all-these-years</link>
<description>&lt;p&gt;&lt;strong&gt;Hello everyone.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'd like to discuss a topic that has sparked endless debates among developers for years:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What programming language should you choose for a serious project?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Throughout my career, I've worked with C, Assembly, Go, and spent a considerable amount of time exploring modern languages such as Rust and C++. The more experience I gain, the more convinced I become that &lt;strong&gt;there is no perfect programming language&lt;/strong&gt;. Every language has its own strengths and weaknesses.&lt;/p&gt;
&lt;p&gt;Despite that, I deliberately chose &lt;strong&gt;Go&lt;/strong&gt; as the foundation of my primary project: &lt;strong&gt;ForgeZero&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;ForgeZero is a cross-platform build orchestration tool written in Go, with carefully optimized performance-critical components implemented in Plan 9 Assembly to minimize runtime overhead and eliminate unnecessary allocations.&lt;/p&gt;
&lt;p&gt;At first glance, it might seem logical that after working with C and Assembly, the next step should be Rust or modern C++, since they are widely considered the future of systems programming.&lt;/p&gt;
&lt;h3 id=&quot;rust&quot;&gt;Rust&lt;/h3&gt;
&lt;p&gt;Rust solves an impressive number of memory safety problems.&lt;/p&gt;
&lt;p&gt;Its ownership model and borrow checker force developers to think about lifetimes and resource management before the code even compiles.&lt;/p&gt;
&lt;p&gt;However, safety comes at a cost.&lt;/p&gt;
&lt;p&gt;There are moments when it feels like the language dictates your architecture rather than the other way around. In large projects, satisfying the borrow checker can sometimes consume more time than solving the original problem itself.&lt;/p&gt;
&lt;p&gt;Even with Rust's strict guarantees, bugs still exist. No language can eliminate every possible programming mistake.&lt;/p&gt;
&lt;p&gt;Personally, I also find large Rust codebases relatively difficult to read. The amount of syntax and abstractions often makes understanding unfamiliar code slower than it should be.&lt;/p&gt;
&lt;h3 id=&quot;c-&quot;&gt;C++&lt;/h3&gt;
&lt;p&gt;C++ is an incredibly powerful language.&lt;/p&gt;
&lt;p&gt;Unfortunately, that power comes with enormous complexity.&lt;/p&gt;
&lt;p&gt;Templates, metaprogramming, concepts, multiple paradigms, countless coding styles, and dozens of ways to solve the same problem can make maintaining large codebases challenging.&lt;/p&gt;
&lt;p&gt;Sometimes reading someone else's C++ project feels less like software engineering and more like an archaeological expedition.&lt;/p&gt;
&lt;h3 id=&quot;c&quot;&gt;C&lt;/h3&gt;
&lt;p&gt;C remains one of the greatest programming languages ever created.&lt;/p&gt;
&lt;p&gt;It provides maximum control over the system, minimal abstractions, and extremely predictable performance, making it an outstanding choice for low-level development.&lt;/p&gt;
&lt;p&gt;The downside, however, is responsibility.&lt;/p&gt;
&lt;p&gt;Every pointer, every allocation, and every memory access becomes your responsibility. Bugs such as use-after-free, buffer overflows, or segmentation faults can be devastating.&lt;/p&gt;
&lt;p&gt;And perhaps even more importantly, building large software systems in C requires an enormous investment of engineering time.&lt;/p&gt;
&lt;p&gt;Had ForgeZero been written entirely in C, both development and long-term maintenance would have been significantly more expensive.&lt;/p&gt;
&lt;h3 id=&quot;why-go-&quot;&gt;Why Go?&lt;/h3&gt;
&lt;p&gt;Go occupies a very unique position.&lt;/p&gt;
&lt;p&gt;It is simple enough that new contributors can understand a codebase within days, while still being powerful enough to build serious production-grade software.&lt;/p&gt;
&lt;p&gt;Goroutines, channels, built-in concurrency, and a minimalistic syntax allow developers to focus on solving problems instead of fighting the language.&lt;/p&gt;
&lt;p&gt;Of course, Go has its own limitations.&lt;/p&gt;
&lt;p&gt;The runtime, garbage collector, and automatic memory management make it a poor choice for operating system kernels, drivers, or extremely low-level software.&lt;/p&gt;
&lt;p&gt;Yet the common belief that Go cannot deliver high performance is simply incorrect.&lt;/p&gt;
&lt;p&gt;With careful architecture, proper escape analysis, object pooling, and manual optimization of performance-critical paths, it is entirely possible to achieve nearly zero allocations where they actually matter.&lt;/p&gt;
&lt;p&gt;That is exactly the direction ForgeZero takes.&lt;/p&gt;
&lt;p&gt;For me, Go represents an excellent balance between development speed, maintainability, readability, and performance.&lt;/p&gt;
&lt;p&gt;It is not perfect.&lt;/p&gt;
&lt;p&gt;But it allows me to build complex tooling faster than most alternatives without sacrificing quality.&lt;/p&gt;
&lt;p&gt;That is why, after all these years, I still choose Go.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;am-i-betraying-c-&quot;&gt;Am I Betraying C?&lt;/h2&gt;
&lt;p&gt;Absolutely not.&lt;/p&gt;
&lt;p&gt;C shaped me as a software engineer, and I still consider it one of the greatest programming languages ever designed.&lt;/p&gt;
&lt;p&gt;In fact, one of ForgeZero's goals is to &lt;strong&gt;make C development easier&lt;/strong&gt;, improving build workflows and project management for both newcomers and experienced engineers.&lt;/p&gt;
&lt;p&gt;Go may be my primary language today, but that does not mean abandoning C.&lt;/p&gt;
&lt;p&gt;A huge portion of modern software infrastructure still depends on it.&lt;/p&gt;
&lt;p&gt;Operating system kernels, drivers, networking libraries, embedded software, and countless system components have been built and refined in C for decades.&lt;/p&gt;
&lt;p&gt;Many developers now discuss Rust's introduction into the Linux kernel and the future of systems programming.&lt;/p&gt;
&lt;p&gt;But realistically, no major company or open-source project is going to rewrite millions of lines of stable legacy C code overnight simply because a newer language exists.&lt;/p&gt;
&lt;p&gt;Large migrations take years—sometimes decades—and happen gradually.&lt;/p&gt;
&lt;p&gt;For that reason alone, C will remain relevant for a very long time.&lt;/p&gt;
&lt;p&gt;And personally, I still use it whenever it is the right tool for the job.&lt;/p&gt;
&lt;p&gt;Recently, while training a small neural network on my home machine, I encountered an interesting problem.&lt;/p&gt;
&lt;p&gt;The hardware is far from modern: an eight-core CPU from 2014 paired with a GTX 1060 6GB (Pascal architecture). Although training was performed on the GPU through PyTorch, the CPU was still under significant thermal stress.&lt;/p&gt;
&lt;p&gt;Instead of building a monitoring solution in Python or deploying heavyweight background services, I wrote a small daemon in C that continuously monitored CPU temperature and automatically terminated non-essential processes once critical thresholds were reached.&lt;/p&gt;
&lt;p&gt;Could I have written the same utility in Go?&lt;/p&gt;
&lt;p&gt;Absolutely.&lt;/p&gt;
&lt;p&gt;But for this particular low-level task, C was simply the better tool.&lt;/p&gt;
&lt;p&gt;No runtime, minimal dependencies, tiny resource usage, and complete control over the operating system.&lt;/p&gt;
&lt;p&gt;That is exactly why I never claim that Go should replace C.&lt;/p&gt;
&lt;p&gt;Quite the opposite.&lt;/p&gt;
&lt;p&gt;I believe that &lt;strong&gt;Go + C + Assembly&lt;/strong&gt; is an incredibly powerful combination.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt; excels at infrastructure, CLI applications, automation, backend services, and distributed systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt; remains unmatched for systems programming and direct interaction with operating system internals.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Assembly&lt;/strong&gt; is invaluable when every CPU cycle matters and maximum optimization is required.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A good engineer should choose the right tool for the problem—not the trendiest language on social media.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;my-advice-for-beginners&quot;&gt;My Advice for Beginners&lt;/h2&gt;
&lt;p&gt;If I had to start my programming journey again, I would begin with &lt;strong&gt;C&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Not because it is the &quot;best&quot; language, but because it forces you to understand how computers actually work.&lt;/p&gt;
&lt;p&gt;How memory is organized.&lt;/p&gt;
&lt;p&gt;How the stack and the heap operate.&lt;/p&gt;
&lt;p&gt;How pointers behave.&lt;/p&gt;
&lt;p&gt;How a compiler transforms source code into assembly instructions and eventually into machine code executed by the processor.&lt;/p&gt;
&lt;p&gt;Once those fundamentals become clear, learning Go, Rust, C++, Java, or almost any other language becomes dramatically easier.&lt;/p&gt;
&lt;p&gt;You stop memorizing syntax and start understanding software engineering.&lt;/p&gt;
&lt;p&gt;So my advice is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don't be afraid of low-level programming.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even if your future career revolves around web services or cloud infrastructure, understanding what happens beneath the abstractions will make you a far stronger engineer.&lt;/p&gt;
&lt;p&gt;Programming languages come and go.&lt;/p&gt;
&lt;p&gt;Fundamental computer science knowledge stays with you forever.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/20174/why-i-still-choose-go-after-all-these-years</guid>
<pubDate>Wed, 10 Jun 2026 13:38:44 +0000</pubDate>
</item>
<item>
<title>ForgeZero v4.2.0: 2.84x Faster Than Ninja. Zero CGO. Zero Compromise.</title>
<link>https://coderlegion.com/19090/forgezero-v4-2-0-2-84x-faster-than-ninja-zero-cgo-zero-compromise</link>
<description>&lt;p&gt;We shipped v4.1.0 with a clear mandate: eliminate heap allocations from every critical path,&lt;br&gt;
harden the syscall layer, and make the build engine deterministic at hardware limits.&lt;br&gt;
v4.2.0 delivers on that — and then goes further.&lt;/p&gt;
&lt;p&gt;This release drops CGO entirely, ships a native Windows syscall driver, and pushes&lt;br&gt;
the assembler hot-path through SIMD rewrites and sync.Pool recycling. The result is a&lt;br&gt;
build orchestrator that runs at 637 ms mean against Ninja's 1.813 s on a 1000-module&lt;br&gt;
C project. That is a 2.84x lead, sustained across 10 runs with sub-35 ms range variance.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;benchmark-forgezero-vs-ninja-1000-c-modules&quot;&gt;Benchmark: ForgeZero vs Ninja, 1000 C Modules&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Tool &lt;/th&gt;&lt;th&gt;Mean &lt;/th&gt;&lt;th&gt;Range &lt;/th&gt;&lt;th&gt;Runs&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;fz (ForgeZero) &lt;/td&gt;&lt;td&gt;637.3 ms &lt;/td&gt;&lt;td&gt;626.6 ms — 685.5 ms &lt;/td&gt;&lt;td&gt;10&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ninja &lt;/td&gt;&lt;td&gt;1.813 s &lt;/td&gt;&lt;td&gt;1.800 s — 1.834 s &lt;/td&gt;&lt;td&gt;10&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;2.84x faster. Consistent. Reproducible.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hardware: Intel i5-10310U. Cold filesystem cache. No artificial throttling.&lt;br&gt;
The variance on fz is 58.9 ms peak-to-peak. On ninja it is 34 ms — but starting from&lt;br&gt;
nearly three times higher. ForgeZero is not only faster, it is stable.&lt;/p&gt;
&lt;p&gt;The previous release already showed 3.25x against &lt;code&gt;make -j4&lt;/code&gt; at 100 modules. This holds&lt;br&gt;
as the module count scales to 1000 against a purpose-built parallel tool. That is not a&lt;br&gt;
benchmark trick. That is architecture.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-big-change-cgo-is-gone&quot;&gt;The Big Change: CGO is Gone&lt;/h3&gt;
&lt;p&gt;v4.1.0 still carried CGO at the entrypoint and in the C plugin loader. Every build that&lt;br&gt;
touched cplugin dragged in the C toolchain, broke cross-compilation, and introduced&lt;br&gt;
non-determinism in the binary ABI.&lt;/p&gt;
&lt;p&gt;v4.2.0 removes it completely.&lt;/p&gt;
&lt;p&gt;The mechanism is &lt;code&gt;GoContext&lt;/code&gt; — a pure Go struct that replaces the old C-coupled parameter&lt;br&gt;
passing interface in cplugin. Platform-specific loaders are now split by build tag:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;//go:build linux&lt;/code&gt; — Unix dynamic loader via CGO (for users who opt in)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;//go:build windows&lt;/code&gt; — Native DLL loader via &lt;code&gt;syscall&lt;/code&gt; and &lt;code&gt;golang.org/x/sys/windows&lt;/code&gt;,&lt;br&gt;
zero CGO dependency&lt;/li&gt;
&lt;li&gt;fallback — no-op loader for non-CGO builds, compiles clean on any target&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The entrypoint (&lt;code&gt;cmd/fz&lt;/code&gt;) no longer imports &lt;code&gt;&quot;C&quot;&lt;/code&gt; at all. The binary is now fully&lt;br&gt;
cross-compilable from any host to any target without a C toolchain present.&lt;/p&gt;
&lt;p&gt;C module structs were also aligned to 64-byte cache lines to eliminate false sharing&lt;br&gt;
on multi-core dispatch.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;assembler-simd-sync-pool-and-one-allocation-budget&quot;&gt;Assembler: SIMD, sync.Pool, and One Allocation Budget&lt;/h3&gt;
&lt;p&gt;The assembler hot-path in v4.1.0 was fast. In v4.2.0 it operates under a strict&lt;br&gt;
zero-allocation budget on every repeated call.&lt;/p&gt;
&lt;p&gt;Three changes made this possible:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SIMD + branchless lookups for whitespace and comment stripping.&lt;/strong&gt;&lt;br&gt;
The previous implementation used range loops with conditional branches. The rewrite&lt;br&gt;
uses branchless lookup tables and SIMD-width processing to strip comments and normalize&lt;br&gt;
whitespace without a single branch misprediction in the common case.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parser recycling via sync.Pool.&lt;/strong&gt;&lt;br&gt;
Each parse call previously allocated a new parser struct. The pool holds pre-warmed&lt;br&gt;
parser instances and recycles them across goroutines. Allocation churn in the assembler&lt;br&gt;
is now flat under sustained parallel load.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;splitArgs as a parser method with pre-allocated slice recycling.&lt;/strong&gt;&lt;br&gt;
The argument splitter was a free function that allocated a new slice on every call.&lt;br&gt;
It is now a method on the pooled parser, reusing the backing array across invocations.&lt;br&gt;
The slice is reset, not reallocated.&lt;/p&gt;
&lt;p&gt;Compiler flags are parsed once via &lt;code&gt;sync.Once&lt;/code&gt; lazy initialization. Repeated calls&lt;br&gt;
to the flag parser cost a single atomic read.&lt;/p&gt;
&lt;p&gt;FastCopy — the inner loop for instruction stream copying — was rewritten as a&lt;br&gt;
branchless unrolled function using &lt;code&gt;unsafe.Pointer&lt;/code&gt; arithmetic. No bounds checks.&lt;br&gt;
No interface dispatch. Direct memory throughput.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;linker-on-disk-symbol-cache-no-nm&quot;&gt;Linker: On-Disk Symbol Cache, No nm&lt;/h3&gt;
&lt;p&gt;The linker previously invoked the &lt;code&gt;nm&lt;/code&gt; utility to resolve symbol tables. &lt;code&gt;nm&lt;/code&gt; is a&lt;br&gt;
process fork per object file. At 1000 modules that is 1000 forks, each with exec&lt;br&gt;
overhead, pipe setup, and string parsing.&lt;/p&gt;
&lt;p&gt;v4.2.0 introduces a co-located on-disk symbol cache. Symbol data is written alongside&lt;br&gt;
the object file on first build and read directly on subsequent builds. The &lt;code&gt;nm&lt;/code&gt; binary&lt;br&gt;
is never invoked in the hot path.&lt;/p&gt;
&lt;p&gt;Object deduplication was simplified from a custom mmap-based structure to a native&lt;br&gt;
Go map. The mmap routines are gone. The map is faster for the deduplication workload&lt;br&gt;
and eliminates an entire category of platform-specific edge cases.&lt;/p&gt;
&lt;p&gt;Response file generation was refactored to use &lt;code&gt;bufio.Writer&lt;/code&gt; with &lt;code&gt;f.Sync&lt;/code&gt; removed.&lt;br&gt;
The sync was a correctness hedge that was never needed — the linker already operates&lt;br&gt;
under an ordering guarantee from the task graph.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;builder-and-utils-walkdir-zero-copy-loops-raw-syscalls&quot;&gt;Builder and Utils: WalkDir, Zero-Copy Loops, Raw Syscalls&lt;/h3&gt;
&lt;p&gt;The builder's source discovery was rewritten around &lt;code&gt;WalkDir&lt;/code&gt; with explicit&lt;br&gt;
&lt;code&gt;filepath.SkipDir&lt;/code&gt; support in the custom &lt;code&gt;SYS_GETDENTS64&lt;/code&gt; walker. Directory subtrees&lt;br&gt;
that cannot contain build targets are pruned at the kernel boundary, not in userspace.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CleanDir&lt;/code&gt; was deduplicated — it was being called multiple times per build target in&lt;br&gt;
the previous implementation. Slice allocations in the task dispatch worker were&lt;br&gt;
eliminated; the dispatch loop now operates on pre-allocated task structs with&lt;br&gt;
zero heap writes in the hot path.&lt;/p&gt;
&lt;p&gt;Platform-specific file hashing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux: Direct &lt;code&gt;SYS_OPENAT&lt;/code&gt; loops with defer-based cleanup and no &lt;code&gt;os.File&lt;/code&gt; allocation&lt;/li&gt;
&lt;li&gt;macOS: Low-level &lt;code&gt;mmap&lt;/code&gt; write interfaces, resource management tuned to Darwin's&lt;br&gt;
unified buffer cache behavior&lt;/li&gt;
&lt;li&gt;Windows / non-UNIX: Updated fallback stubs that compile clean and behave correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unused pools were purged. Digest-to-string conversions were rewritten to use&lt;br&gt;
stack-allocated byte slices instead of &lt;code&gt;fmt.Sprintf&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-v4-1-0-established&quot;&gt;What v4.1.0 Established&lt;/h3&gt;
&lt;p&gt;For context: v4.1.0 shipped the HADES ELF emitter with correct &lt;code&gt;.symtab&lt;/code&gt; ordering&lt;br&gt;
(local symbols before global, strict linker compliance), deterministic relocation&lt;br&gt;
calculation for &lt;code&gt;call&lt;/code&gt; and &lt;code&gt;jmp&lt;/code&gt; offsets, and the AEGIS security layer with TOCTOU&lt;br&gt;
mitigation, atomic SBOM generation, and &lt;code&gt;secureVendorPath&lt;/code&gt; symlink validation.&lt;/p&gt;
&lt;p&gt;The allocation regression suite and &lt;code&gt;golangci-lint&lt;/code&gt; enforcement were introduced in&lt;br&gt;
that release and remain green.&lt;/p&gt;
&lt;p&gt;v4.2.0 builds on that foundation without breaking it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;numbers-that-matter&quot;&gt;Numbers That Matter&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2.84x&lt;/strong&gt; faster than Ninja at 1000 C modules&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3.25x&lt;/strong&gt; faster than &lt;code&gt;make -j4&lt;/code&gt; at 100 modules (v4.1.0, still holds)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~1.18 GB/s&lt;/strong&gt; sustained copy throughput on i5-10310U, zero GC interference&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 allocs/op, 0 B/op&lt;/strong&gt; in &lt;code&gt;copyFileHot&lt;/code&gt; micro-benchmark&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 CGO dependencies&lt;/strong&gt; in the default build path&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100%&lt;/strong&gt; &lt;code&gt;golangci-lint&lt;/code&gt; compliance, strict config&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;get-it&quot;&gt;Get It&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/forgezero-cli/forgezero
cd forgezero
go build -o fz ./cmd/fz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No C toolchain required. No network calls at build time. Reproducible output on&lt;br&gt;
amd64 and arm64 across Linux, Windows, and macOS.&lt;/p&gt;
&lt;p&gt;Source, benchmarks, and the full commit log are in the repository.&lt;br&gt;
If you are running large C projects and measuring build latency, the numbers are&lt;br&gt;
worth verifying on your own hardware.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;ForgeZero is a low-level build orchestrator written in Go. It targets deterministic,&lt;br&gt;
zero-allocation execution for large-scale C and assembly projects.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19090/forgezero-v4-2-0-2-84x-faster-than-ninja-zero-cgo-zero-compromise</guid>
<pubDate>Wed, 27 May 2026 17:09:17 +0000</pubDate>
</item>
<item>
<title>Bridging the Context Gap: Providing Structural Awareness to AI Coding Agents in Go with GoGraph</title>
<link>https://coderlegion.com/17835/bridging-the-context-gap-providing-structural-awareness-to-ai-coding-agents-with-gograph</link>
<description>&lt;p&gt;The hardest part of using AI coding agents isn't getting them to write Go code. It's watching them burn through your context window just trying to figure out where things are.&lt;/p&gt;
&lt;p&gt;If you’ve spent any time using Claude, Antigravity or Copilot on a medium-to-large Go codebase, you know the drill. You ask the agent to modify a core component. Before it writes a single line of code, it starts blindly running grep, guessing file paths, and sequentially cat-ing files.&lt;/p&gt;
&lt;p&gt;By the time the agent finally maps out the upstream HTTP handlers, the database interactions, and the interface satisfactions, it has exhausted a massive chunk of its token limit. The context window is bloated with irrelevant side-material and raw text dumps. The more noisy, unstructured code you feed the agent, the higher the chance it gets confused and starts hallucinating bad logic.&lt;/p&gt;
&lt;p&gt;Agents don't fail because they are bad at writing logic. They fail because they have to brute-force their way to structural awareness.&lt;/p&gt;
&lt;p&gt;I got tired of watching my AI tools waste tokens and context guessing my repository structure, so I built &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ozgurcd/gograph&quot;&gt;Gograph&lt;/a&gt;. It’s a local static analysis engine and Model Context Protocol (MCP) server that gives your AI agents a deterministic, whole-repository structural map of your Go projects in a single, token-efficient JSON query.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why RAG and Terminal Searching Aren’t Enough&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Right now, the industry usually tries to solve the codebase context problem in two ways: &lt;em&gt;vector embeddings&lt;/em&gt; through &lt;em&gt;RAG&lt;/em&gt;, or by letting agents inspect the repository through terminal commands. Both approaches are useful, but neither is sufficient on its own for reliable understanding of a large Go codebase.&lt;/p&gt;
&lt;p&gt;The issue with RAG is that vector embeddings are good at semantic retrieval. If you want to find code related to “user authentication logic,” embedding search can often surface relevant files or nearby concepts. But embeddings do not provide deterministic code intelligence. They cannot prove that &lt;em&gt;UserStore.Update()&lt;/em&gt; satisfies the &lt;em&gt;Datastore&lt;/em&gt; interface, identify every caller across package boundaries, or trace how an error propagates through several layers of middleware. Those relationships require parsing, type checking, and static analysis.&lt;/p&gt;
&lt;p&gt;The issue with terminal search is that agents often fall back to tools like &lt;em&gt;grep&lt;/em&gt;, &lt;em&gt;rg&lt;/em&gt;, or reading files directly. These tools are valuable for exact strings, symbols, config keys, routes, and quick navigation. But plain text search cannot reliably capture relationships that only exist after the compiler understands the program. In Go, interface satisfaction is implicit, indirect calls may flow through interfaces, and behavior may depend on build tags, generated code, or package-level wiring. A search-only approach is likely to miss non-obvious dependencies.&lt;/p&gt;
&lt;p&gt;For large Go repositories, reliable agent context needs more than semantic search and raw file inspection. It needs compiler-aware code intelligence: symbols, packages, imports, interfaces, implementations, call graphs, route mappings, test relationships, and error flows. RAG can help find relevant areas. Terminal tools can help inspect concrete files. But the agent also needs structured knowledge of how the code actually fits together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fixing It With Static AST Analysis&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of relying only on semantic similarity or raw text search, I wanted a tool that gives agents structured knowledge of a Go repository.&lt;/p&gt;
&lt;p&gt;Gograph uses Go’s native parsing infrastructure, including go/ast and go/parser, to build a static graph of your repository. It runs entirely on your local machine: no network requests, no sending proprietary source code to third-party APIs. Its goal is to map concrete code structure such as packages, symbols, functions, methods, struct fields, imports, call sites, route wiring, tests, and recognizable dependency paths.&lt;/p&gt;
&lt;p&gt;Gograph ships with more than 20 specialized static analysis commands, including tools to map concurrency primitives, identify oversized objects, discover potentially dead code, and extract SQL-related structure. However, I designed three primary commands specifically to act as the core structural guardrails for AI agents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.gograph plan &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before an agent touches a file, it can run plan. Gograph analyzes the static code graph around the symbol and returns an impact-oriented view of the surrounding code.&lt;/p&gt;
&lt;p&gt;It can show callers, related packages, upstream HTTP routes where they are statically detectable, whether the execution path appears to touch persistence-related code, and which test files are likely to be affected.&lt;/p&gt;
&lt;p&gt;This gives the agent a concrete starting point instead of forcing it to guess from embeddings or repeatedly read files into context.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.gograph review &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After the agent makes an edit, it can run review. This acts as a structural sanity check over the affected area.&lt;/p&gt;
&lt;p&gt;It can flag likely breakage in routing paths, changed dependencies, stale tests, and interface-related drift where those relationships are visible to static analysis. It is not a replacement for compilation or go test. It is an earlier feedback layer that helps agents catch structural mistakes before running the full verification loop.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.gograph errorflow &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When a test fails with an error like &quot;invalid token signature&quot;, the agent should not have to guess where that message originated. It can pass the string to errorflow.&lt;/p&gt;
&lt;p&gt;Gograph searches for where the error is created or wrapped, then follows the static return and call path as far as it can identify it. When the path is visible in the source graph, it can show how the error moves from the originating function toward higher-level handlers or tests.&lt;/p&gt;
&lt;p&gt;This is useful in larger Go services where errors often pass through repository, service, middleware, and handler layers before reaching the test output or API response.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What It Looks Like in Practice&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s say you need an AI agent to add a &lt;em&gt;TenantID&lt;/em&gt; field to a &lt;em&gt;User&lt;/em&gt; struct in a shared package.&lt;/p&gt;
&lt;p&gt;If the agent relies only on raw search, it may find the struct, add the field, update the local constructor, and stop. The build may still fail because a &lt;em&gt;POST /api/v1/users&lt;/em&gt; handler in another package initializes the struct directly, or because repository code, SQL mapping, and integration tests also need updates.&lt;/p&gt;
&lt;p&gt;If the agent uses Gograph first, it can query the User symbol and get a structured view of the surrounding code. That view can show that the symbol is connected to a specific POST route, appears in repository code, participates in SQL-related paths, and is referenced by specific test files.&lt;/p&gt;
&lt;p&gt;The agent now has a better bounded scope for the change. Instead of editing one obvious file and discovering failures one by one, it can update the struct, handler, persistence path, and tests in a single coherent pass.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hooking It Up via MCP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Gograph works as a standalone CLI, but it is also designed to be consumed by AI agents through the Model Context Protocol, or MCP.&lt;/p&gt;
&lt;p&gt;MCP is an open protocol introduced by Anthropic for connecting AI systems to local tools and data sources. Gograph ships with a built-in MCP server that operates over standard I/O.&lt;/p&gt;
&lt;p&gt;Instead of forcing the model to read and interpret unstructured terminal output, the MCP integration gives it structured, machine-readable JSON.&lt;/p&gt;
&lt;p&gt;For an MCP-compatible client such as Cursor or Claude Desktop, the configuration looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    &quot;gograph&quot;: {
      &quot;command&quot;: &quot;gograph&quot;,
      &quot;args&quot;: [&quot;mcp&quot;, &quot;/path/to/your/repository&quot;]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once registered, the agent can query the repository graph before editing. It can inspect the structural impact of a proposed change, identify likely affected files, and build a more grounded execution plan before writing code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Getting Started&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you’re building in Go and tired of cleaning up after your AI tools, you can install Gograph right now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# macOS / Linux (via Homebrew)
brew install ozgurcd/tap/gograph
# Or using Go:
go install github.com/ozgurcd/gograph/cmd/gograph@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AI agents work better when they are given structured context instead of raw text alone. RAG is useful for semantic retrieval. Terminal commands are useful for exact inspection. But large Go codebases also need code-aware context: packages, symbols, call paths, routes, tests, persistence boundaries, and error flows.&lt;/p&gt;
&lt;p&gt;That is the problem Gograph is trying to solve.&lt;/p&gt;
&lt;p&gt;If you’re building in Go and tired of cleaning up after your AI tools, give Gograph a try.&lt;/p&gt;
&lt;p&gt;Source Code and Documentation: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ozgurcd/gograph&quot;&gt;https://github.com/ozgurcd/gograph&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you have ideas for other static analysis heuristics that would be useful for AI context, I'd love to hear your feedback! Feel free to open an issue or start a discussion on the GitHub repository.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17835/bridging-the-context-gap-providing-structural-awareness-to-ai-coding-agents-with-gograph</guid>
<pubDate>Thu, 14 May 2026 15:44:55 +0000</pubDate>
</item>
<item>
<title>We Rebuilt the Kubernetes Dashboard From Scratch — Here's What We Added</title>
<link>https://coderlegion.com/17774/we-rebuilt-the-kubernetes-dashboard-from-scratch-heres-what-we-added</link>
<description>&lt;h2 id=&quot;we-rebuilt-the-kubernetes-dashboard-from-scratch-here-s-what-we-added&quot;&gt;We Rebuilt the Kubernetes Dashboard From Scratch — Here's What We Added&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;The official Kubernetes Dashboard was archived in January 2026. The Angular codebase was unmaintainable, the toolchain was fragile, and the upstream maintainers called it quits. We needed a dashboard for our home lab security platform — so we built one from scratch.&lt;/p&gt;
&lt;p&gt;This is the story of what we built, why we made the decisions we did, and what makes it different from the alternatives.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-not-headlamp-lens-or-rancher-&quot;&gt;Why Not Headlamp, Lens, or Rancher?&lt;/h3&gt;
&lt;p&gt;We evaluated the obvious alternatives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lens&lt;/strong&gt; — desktop app, not in-cluster, commercial freemium model. Not suitable for a hardened in-cluster deployment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Headlamp&lt;/strong&gt; — great project, but its extensibility model relies on dynamically loaded JavaScript plugins. We preferred a dashboard with no plugin runtime and a fully auditable, static codebase.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rancher&lt;/strong&gt; — full multi-cluster platform, overkill for a single cluster, and it needs its own namespace footprint&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We wanted something that runs &lt;strong&gt;in-cluster&lt;/strong&gt; as a hardened pod, has &lt;strong&gt;no external dependencies&lt;/strong&gt; beyond the Kubernetes API, and is &lt;strong&gt;auditable&lt;/strong&gt; — no plugin runtime, no dynamic code loading.&lt;/p&gt;
&lt;p&gt;One more thing the original couldn't do: work with MetalLB. It was designed around &lt;code&gt;kubectl proxy&lt;/code&gt; and NodePort — never intended to sit behind a real in-cluster gateway. Getting it onto a MetalLB IP required Kong as the gateway, which the original had no support for.&lt;/p&gt;
&lt;p&gt;So we forked the archived dashboard and rebuilt it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-stack&quot;&gt;The Stack&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; React 19 + Material UI v6 + Vite + TypeScript&lt;br&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Four Go modules — API, Auth, Metrics Scraper, Common&lt;br&gt;
&lt;strong&gt;Gateway:&lt;/strong&gt; Kong 3.9 (DBless) as the in-cluster API gateway&lt;br&gt;
&lt;strong&gt;Auth:&lt;/strong&gt; Bearer token (Kubernetes Service Account) with CSRF protection&lt;br&gt;
&lt;strong&gt;Deploy:&lt;/strong&gt; Raw Kubernetes manifests — no Helm&lt;/p&gt;
&lt;p&gt;The original Go backend contracts were preserved. Only the UI layer was replaced.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-shipped-beyond-standard-dashboard-&quot;&gt;What We Shipped Beyond &quot;Standard Dashboard&quot;&lt;/h3&gt;
&lt;p&gt;The usual dashboard shows you workload lists, pod logs, and a shell. We added ten native features that normally require separate tools:&lt;/p&gt;
&lt;h4 id=&quot;1-policy-audit-polaris-native-&quot;&gt;1. Policy Audit (Polaris-native)&lt;/h4&gt;
&lt;p&gt;14 security checks against live pod specs — no Polaris deployment needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Privilege escalation allowed?&lt;/li&gt;
&lt;li&gt;Host network / host PID / host IPC?&lt;/li&gt;
&lt;li&gt;Resource limits set?&lt;/li&gt;
&lt;li&gt;Readiness and liveness probes defined?&lt;/li&gt;
&lt;li&gt;Image tag is &lt;code&gt;latest&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;seccompProfile&lt;/code&gt; configured?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each workload gets a score 0–100. The namespace gets an aggregate. Filterable by severity (Danger / Warning / Pass).&lt;/p&gt;
&lt;p&gt;The entire thing is a single Go package reading pod specs directly from the Kubernetes API. No admission webhooks, no third-party operators.&lt;/p&gt;
&lt;h4 id=&quot;2-resource-efficiency-goldilocks-style-&quot;&gt;2. Resource Efficiency (Goldilocks-style)&lt;/h4&gt;
&lt;p&gt;Compares actual CPU/memory usage (from &lt;code&gt;metrics.k8s.io&lt;/code&gt;) against container requests and limits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No Limits&lt;/strong&gt; — the container has no resource limits set (dangerous)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hot&lt;/strong&gt; — actual usage exceeds requests (risking OOM or CPU throttling)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cold&lt;/strong&gt; — requests are much larger than actual usage (wasting capacity)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OK&lt;/strong&gt; — requests and limits are reasonable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CSV export for capacity planning. With VictoriaMetrics enabled, trend arrows (↑ ↓ →) show whether usage is growing, shrinking, or stable.&lt;/p&gt;
&lt;h4 id=&quot;3-certificate-tracker&quot;&gt;3. Certificate Tracker&lt;/h4&gt;
&lt;p&gt;Parses every &lt;code&gt;kubernetes.io/tls&lt;/code&gt; Secret across all namespaces using Go's stdlib &lt;code&gt;crypto/x509&lt;/code&gt;. No cert-manager dependency.&lt;/p&gt;
&lt;p&gt;Shows: Common Name, SANs, issuer, expiry date, days remaining, and a status badge (Valid / Warning ≤30d / Critical ≤7d / Expired).&lt;/p&gt;
&lt;h4 id=&quot;4-rbac-viewer&quot;&gt;4. RBAC Viewer&lt;/h4&gt;
&lt;p&gt;Joins all ClusterRoles, Roles, ClusterRoleBindings, and RoleBindings into a flat view. Filters by subject, scope, or kind. Wildcard (&lt;code&gt;*&lt;/code&gt;) detection flags any binding that grants broad permissions.&lt;/p&gt;
&lt;h4 id=&quot;5-ai-assistant-claude-sonnet-&quot;&gt;5. AI Assistant (Claude Sonnet)&lt;/h4&gt;
&lt;p&gt;A chat drawer powered by Anthropic's Claude via SSE streaming. When opened from a pod detail page, it automatically injects the pod's spec, container statuses, and recent events as system context.&lt;/p&gt;
&lt;p&gt;Ask it: &quot;Why is this pod crash-looping?&quot; and it reads the events, the image, the environment variables, and the resource limits to give you an actual answer — not a generic Kubernetes doc quote.&lt;/p&gt;
&lt;h4 id=&quot;6-event-timeline&quot;&gt;6. Event Timeline&lt;/h4&gt;
&lt;p&gt;Live cluster event feed refreshing every 5 seconds. Events are time-bucketed by minute, Warning events highlighted in amber, filterable by namespace and free text. Useful for watching a rolling restart or debugging a flapping pod without staring at &lt;code&gt;kubectl get events -w&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;7-cluster-map&quot;&gt;7. Cluster Map&lt;/h4&gt;
&lt;p&gt;All Deployments, DaemonSets, and StatefulSets rendered as health cards grouped by namespace — zoom from 40% to 150%, filter by Error/Warning, click through to the workload detail. Useful for a quick visual sweep of the cluster when something is off.&lt;/p&gt;
&lt;h4 id=&quot;8-application-projects&quot;&gt;8. Application Projects&lt;/h4&gt;
&lt;p&gt;Per-namespace project cards showing pod health (Running / Pending / CrashLoopBackOff / Failed), workload counts (Deployments, StatefulSets, DaemonSets), and CPU/memory request totals. Clicking a card navigates to the pod list filtered to that namespace. System namespaces hidden by default with a toggle.&lt;/p&gt;
&lt;h4 id=&quot;9-kubescape-security-auto-detected-&quot;&gt;9. Kubescape Security (auto-detected)&lt;/h4&gt;
&lt;p&gt;If Kubescape Operator is running in the cluster, a &lt;strong&gt;Security&lt;/strong&gt; section appears automatically in the nav — no configuration, no feature flags. It reads Kubescape's CRDs (&lt;code&gt;spdx.softwarecomposition.kubescape.io&lt;/code&gt;) and displays:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security Overview&lt;/strong&gt; — SVG donut charts for config scan pass/fail ratio and CVE severity breakdown, plus a lowest-scoring workloads table. One-page health snapshot before you drill in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Config Scan&lt;/strong&gt; — compliance score per workload (0–100), filterable by namespace&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerabilities&lt;/strong&gt; — CVE findings per workload with severity breakdown (Critical / High / Medium / Low)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If Kubescape is not installed, the Security section simply doesn't appear.&lt;/p&gt;
&lt;h4 id=&quot;10-email-notifications-microsoft-graph-api-&quot;&gt;10. Email Notifications (Microsoft Graph API)&lt;/h4&gt;
&lt;p&gt;Two notification modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Health Digest&lt;/strong&gt; — daily email with cluster health score, namespace table, top issues&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Alerts&lt;/strong&gt; — real-time email on CrashLoop, OOM kill, ImagePullBackOff, NodeNotReady, PVC issues. 1-hour dedup per workload so you don't get flooded.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No SMTP server needed — sends via Microsoft Graph API (works with any Microsoft 365 / Entra ID account).&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;security-hardening&quot;&gt;Security Hardening&lt;/h3&gt;
&lt;p&gt;We ran an OWASP Top 10 audit and fixed everything we found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No wildcard RBAC&lt;/strong&gt; — each pod has its own ServiceAccount with least-privilege rules&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NetworkPolicy default-deny&lt;/strong&gt; — every pod has an explicit allow list; all other traffic is denied&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; on all containers except Kong (which writes Lua cache)&lt;/li&gt;
&lt;li&gt;All capabilities dropped (&lt;code&gt;drop: [ALL]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;seccompProfile: RuntimeDefault&lt;/code&gt; on all pods&lt;/li&gt;
&lt;li&gt;CSRF protection on all mutating POST endpoints&lt;/li&gt;
&lt;li&gt;Bearer tokens never logged, never cached in plain text&lt;/li&gt;
&lt;li&gt;API response cache keyed by &lt;code&gt;SHA256(token) + URL&lt;/code&gt; — users can never see each other's cached data&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;rbac-aware-ui&quot;&gt;RBAC-Aware UI&lt;/h3&gt;
&lt;p&gt;Every action button (Delete, Scale, Restart, Rollback, Pause) checks the user's actual Kubernetes permissions via &lt;code&gt;SelfSubjectAccessReview&lt;/code&gt; before rendering. If the user's token doesn't have &lt;code&gt;patch&lt;/code&gt; on &lt;code&gt;daemonsets&lt;/code&gt;, the Rollback button is greyed out with a tooltip — not shown at all, not erroring after the click.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;rollback-with-revision-history&quot;&gt;Rollback With Revision History&lt;/h3&gt;
&lt;p&gt;Deployments, DaemonSets, and StatefulSets all have a rollback button in the action bar. Click it and you get a revision picker:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deployments&lt;/strong&gt; — reads ReplicaSets annotated with &lt;code&gt;deployment.kubernetes.io/revision&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DaemonSets / StatefulSets&lt;/strong&gt; — reads &lt;code&gt;ControllerRevision&lt;/code&gt; objects (the same mechanism &lt;code&gt;kubectl rollout undo&lt;/code&gt; uses)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Select a revision, see the container image and age, click Rollback. Same UX as &lt;code&gt;kubectl rollout undo --to-revision=N&lt;/code&gt; but without leaving the browser.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;optional-victoriametrics-backend&quot;&gt;Optional: VictoriaMetrics Backend&lt;/h3&gt;
&lt;p&gt;The dashboard ships with SQLite as the default metrics cache (same as the original). If you deploy VictoriaMetrics alongside it (one StatefulSet, one PVC — the manifest is included), the metrics scraper starts pushing to it via remote write, and you get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pod CPU/memory sparklines with time range toggles (1h / 6h / 24h / 7d)&lt;/li&gt;
&lt;li&gt;Trend arrows on the Resource Efficiency page&lt;/li&gt;
&lt;li&gt;Historical data surviving pod restarts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you don't deploy VictoriaMetrics, nothing changes. The capabilities endpoint returns &lt;code&gt;{&quot;victoriaMetrics&quot;: false}&lt;/code&gt; and the UI shows nothing different.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;gateway-api-support&quot;&gt;Gateway API Support&lt;/h3&gt;
&lt;p&gt;If your cluster runs Istio, Envoy Gateway, Contour, or any other Gateway API implementation, the dashboard automatically detects &lt;code&gt;gateway.networking.k8s.io&lt;/code&gt; in the API server's discovery endpoint and adds a &lt;strong&gt;Gateway API&lt;/strong&gt; section to the nav — GatewayClasses, Gateways, HTTPRoutes. If the CRDs aren't there, the section doesn't appear.&lt;/p&gt;
&lt;p&gt;No configuration. No feature flags.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-architecture-decision-we-re-most-proud-of&quot;&gt;The Architecture Decision We're Most Proud Of&lt;/h3&gt;
&lt;p&gt;Everything is &lt;strong&gt;native Go&lt;/strong&gt; — no external operators, no CRD controllers, no plugin runtime.&lt;/p&gt;
&lt;p&gt;The policy audit doesn't need Polaris deployed. The cert tracker doesn't need cert-manager. The efficiency analysis doesn't need Goldilocks or VPA. The RBAC viewer doesn't need any extra tooling.&lt;/p&gt;
&lt;p&gt;This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One fewer deployment per feature&lt;/li&gt;
&lt;li&gt;No version coupling between the dashboard and external tools&lt;/li&gt;
&lt;li&gt;The entire security surface is one set of pods with auditable source code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For Kubescape, we read its CRDs dynamically — if Kubescape is present, the CVE and compliance data appears. If not, nothing changes. Same pattern for Gateway API.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;isms-core-integration&quot;&gt;ISMS Core Integration&lt;/h3&gt;
&lt;p&gt;The dashboard also exposes a &lt;code&gt;GET /api/v1/summary&lt;/code&gt; endpoint — a machine-readable cluster health snapshot (node status, pod counts by phase, recent Warning events, policy audit score, cert expiry counts). An &lt;a rel=&quot;nofollow&quot; href=&quot;https://isms-core.com/&quot;&gt;ISMS CORE&lt;/a&gt; connector polls this endpoint and maps the data to ISO 27001 controls, so cluster health flows into the wider security posture platform automatically.&lt;/p&gt;
&lt;p&gt;This follows the same pattern as the Kubescape and Gateway API integrations: the dashboard is a passive provider of structured data, not a controller.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://kubernetes-dashboard.com&quot;&gt;kubernetes-dashboard.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/isms-core-project/kubernetes-dashboard&quot;&gt;github.com/isms-core-project/kubernetes-dashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;License:&lt;/strong&gt; Apache 2.0&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17774/we-rebuilt-the-kubernetes-dashboard-from-scratch-heres-what-we-added</guid>
<pubDate>Thu, 14 May 2026 01:53:51 +0000</pubDate>
</item>
<item>
<title>I Got Bored and Cleaned Up 150+ GitHub Repos with a Tool I Built in Go</title>
<link>https://coderlegion.com/16346/i-got-bored-and-cleaned-up-150-github-repos-with-a-tool-i-built-in-go</link>
<description>&lt;p&gt;We’ve all been there scrolling through GitHub and cringing at a pile of abandoned projects and forgotten forks.&lt;/p&gt;
&lt;p&gt;I had over 150 repos and wanted to clean them up, but GitHub’s UI makes bulk deletion tedious. So I built a TUI app in Go to make it fast and simple.&lt;/p&gt;
&lt;p&gt;It lets you search, multi-select, and delete repositories in bulk, with quick access to repo details and browser links.&lt;/p&gt;
&lt;p&gt;In about 10 minutes, I cleared out dozens of unused repos and kept only what matters.&lt;/p&gt;
&lt;p&gt;If your GitHub needs a cleanup: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/christophervistal25/repository-cleaner&quot;&gt;Repository&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16346/i-got-bored-and-cleaned-up-150-github-repos-with-a-tool-i-built-in-go</guid>
<pubDate>Fri, 01 May 2026 19:11:14 +0000</pubDate>
</item>
<item>
<title>Shift-Left Code Quality: Inside DebtDrone CLI 2.0.0 and a Dual-Mode Architecture</title>
<link>https://coderlegion.com/15085/shift-left-code-quality-inside-debtdrone-cli-2-0-0-and-a-dual-mode-architecture</link>
<description>&lt;p&gt; Technical debt is a silent tax on engineering velocity. By the time it is flagged in a pull request, the cost to fix it has already multiplied.&lt;br&gt;
Today, I am thrilled to announce the release of DebtDrone CLI 2.0.0—a major architectural milestone that brings enterprise-grade code analysis directly to your local terminal and CI/CD pipelines.&lt;br&gt;
The engineered version 2.0.0 using a strict Ports &amp;amp; Adapters (Hexagonal) Architecture, transforming our statically-linked Go binary into a dual-purpose powerhouse:&lt;br&gt;
️ Interactive TUI (For Developers): A lightning-fast, Bubble Tea-powered terminal interface to visually explore complexity metrics and track historical debt reduction locally.&lt;br&gt;
 Headless CLI (For Pipelines): A deterministic, pipeline-ready executable to enforce automated quality gates (--fail-on=high) and block bad code from ever merging.&lt;br&gt;
Crucially, DebtDrone CLI 2.0.0 is powered by the exact same core analysis engine that will drive our upcoming DebtDrone SaaS platform. By upgrading today, you are getting an early, hands-on experience with the engine that is about to change how organizations map and manage code quality.&lt;/p&gt;
&lt;p&gt;I just published a deep dive into how we built this dual-mode architecture, how we prevented TUI state bloat using a nested router pattern, and how you can start shifting your code quality checks to the left today.&lt;/p&gt;
&lt;p&gt;Check out the full article and installation guide here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.endrilickollari.com/blog/debtdrone-cli-2-release&quot;&gt;https://www.endrilickollari.com/blog/debtdrone-cli-2-release&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15085/shift-left-code-quality-inside-debtdrone-cli-2-0-0-and-a-dual-mode-architecture</guid>
<pubDate>Fri, 17 Apr 2026 19:13:20 +0000</pubDate>
</item>
<item>
<title>The Go Programming Language: Everything You Need To Know</title>
<link>https://coderlegion.com/12095/the-go-programming-language-everything-you-need-to-know</link>
<description>&lt;p&gt;&lt;em&gt;Hello, I'm Ganesh. I'm working on&lt;/em&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://hexmos.com/freedevtools&quot;&gt;&lt;em&gt;FreeDevTools online&lt;/em&gt;&lt;/a&gt;&lt;em&gt;,&lt;/em&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://hexmos.com/freedevtools&quot;&gt;&lt;em&gt;currently building&lt;/em&gt;&lt;/a&gt; &lt;em&gt;a single platform for all development tools, cheat codes, and TL; DRs&lt;/em&gt; — a free, open-source hub where developers can quickly find and use tools without the hassle of searching the internet.&lt;/p&gt;
&lt;p&gt;In this series I will be explaining &lt;a rel=&quot;nofollow&quot; href=&quot;https://go.dev/&quot;&gt;Go language&lt;/a&gt;. I will be covering most of the topics in Go which would be normaly used in most of the applications. &lt;/p&gt;
&lt;p&gt;Main Goal of this series is to start from scratch and end in production grade application.&lt;/p&gt;
&lt;p&gt;If you are already familiar with Go and just want to go in deeper like want to understand internal working of Go and how it evolved this series would be a good fit for you.&lt;/p&gt;
&lt;p&gt;In this part, I will be explaining the history of Go and how it came out.&lt;/p&gt;
&lt;h3 id=&quot;what-is-go-&quot;&gt;What is Go?&lt;/h3&gt;
&lt;p&gt;an open-source, statically typed, and compiled programming language designed at Google to make it easier to build simple, reliable, and efficient software&lt;/p&gt;
&lt;p&gt;It combines the performance of a low-level compiled language like C++ with the readability and usability of modern dynamic languages like Python. &lt;/p&gt;
&lt;p&gt;As you learn more about Go, you will find that it is a language that is both powerful and easy to learn.&lt;/p&gt;
&lt;p&gt;There has been many open source projects built using Go (like listmonk, Docker, Kubernetes, etc).&lt;/p&gt;
&lt;h3 id=&quot;simple-hello-world&quot;&gt;Simple Hello World&lt;/h3&gt;
&lt;p&gt;Before even starting with go, let's just do a simple hello world program.&lt;/p&gt;
&lt;p&gt;I recommend you to go with officical Go tour to learn go.&lt;/p&gt;
&lt;p&gt;Link: &lt;a rel=&quot;nofollow&quot; href=&quot;https://tour.golang.org/welcome/1&quot;&gt;Go Tour&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As in every language, let's just do a simple hello world program.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main
import &quot;fmt&quot;
func main() {
    fmt.Println(&quot;Hello, World!&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above code, &lt;/p&gt;
&lt;p&gt;&lt;code&gt;package main&lt;/code&gt; is the package name, it is the entry point of the program.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import &quot;fmt&quot;&lt;/code&gt; is the import statement, it imports the fmt package which is used to print the output.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;func main() { ... }&lt;/code&gt; is the main function, it is the entry point of the program.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fmt.Println(&quot;Hello, World!&quot;)&lt;/code&gt; is the print statement, it prints the output.&lt;/p&gt;
&lt;p&gt;In this part, we just understood the simple surface level of go and also understand the basic structure of go program.&lt;/p&gt;
&lt;h3 id=&quot;upcoming-part&quot;&gt;Upcoming Part&lt;/h3&gt;
&lt;p&gt;In upcomming part I will be explaining the project structure of go project. How to setup locally and how to run it.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/12095/the-go-programming-language-everything-you-need-to-know</guid>
<pubDate>Mon, 23 Feb 2026 19:36:05 +0000</pubDate>
</item>
<item>
<title>How My Worst Complexity Solution Beat 100% Runtime</title>
<link>https://coderlegion.com/12061/how-my-worst-complexity-solution-beat-100-runtime</link>
<description>&lt;p&gt;I recently solved the &lt;code&gt;Leetcode 868. Binary Gap&lt;/code&gt; problem in Go, and encountered something interesting.&lt;/p&gt;
&lt;p&gt;My supposedly worst solution — with an &lt;code&gt;O(n log n)&lt;/code&gt; time complexity — ended up beating 100% of submissions in runtime.&lt;/p&gt;
&lt;p&gt;Meanwhile, the &quot;optimal&quot; space version used more memory.&lt;/p&gt;
&lt;p&gt;That made me realise something important:&lt;/p&gt;
&lt;p&gt;Big-O complexity might not always dominate — especially when input size is tightly bounded.&lt;/p&gt;
&lt;p&gt;In this case, the maximum binary length was only 32 bits. Sorting a handful of integers becomes effectively constant time. The theory said one thing. The measurements told another story.&lt;/p&gt;
&lt;p&gt;I wrote a short breakdown explaining:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why does this happen&lt;/li&gt;
&lt;li&gt;How online judges measure performance&lt;/li&gt;
&lt;li&gt;When asymptotic complexity truly matters&lt;/li&gt;
&lt;li&gt;And how to implement a truly optimal bitwise version&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're into performance reasoning and algorithm trade-offs, you might enjoy this one.&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rezzcode/leetcode-sunday-4-1025&quot;&gt;https://dev.to/rezzcode/leetcode-sunday-4-1025&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12061/how-my-worst-complexity-solution-beat-100-runtime</guid>
<pubDate>Sun, 22 Feb 2026 22:53:29 +0000</pubDate>
</item>
<item>
<title>Stop Leaking API Keys in your AI Agent Logs: A Go Sidecar Approach</title>
<link>https://coderlegion.com/11147/stop-leaking-api-keys-in-your-ai-agent-logs-a-go-sidecar-approach</link>
<description>&lt;h2 id=&quot;stop-leaking-api-keys-in-your-ai-agent-logs-a-go-sidecar-approach&quot;&gt;Stop Leaking API Keys in your AI Agent Logs: A Go Sidecar Approach&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Subtitle:&lt;/strong&gt; The Hidden Privacy Leak in your AI Agents (and why your LLM &quot;Audit Logs&quot; are a GDPR Nightmare)&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-problem&quot;&gt;1. The Problem&lt;/h3&gt;
&lt;p&gt;Everyone is building AI agents right now. Whether you're using LangChain, AutoGPT, or custom loops, you are almost certainly logging their work. You keep traces of every step to debug reasoning loops or monitor costs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here lies the pain:&lt;/strong&gt; Everything goes into these logs. User prompts, model responses, and raw JSON payloads from APIs.&lt;/p&gt;
&lt;h4 id=&quot;the-visualization&quot;&gt;The Visualization&lt;/h4&gt;
&lt;p&gt;Imagine this scenario:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Input Log:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;user&quot;: &quot;aragossa&quot;, &quot;prompt&quot;: &quot;My key is sk-live-123456&quot;} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What your Logging System Saved:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;user&quot;: &quot;aragossa&quot;, &quot;prompt&quot;: &quot;My key is sk-live-123456&quot;} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If a user pastes their password, API key, or PII into the chat, or if an API returns a sensitive internal token, that data is now permanently etched into your Elasticsearch, Datadog, or S3 bucket.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Consequence:&lt;/strong&gt; Your fine-tuning dataset is now &quot;poisoned&quot; with real user data. This is a massive GDPR violation and a ticking security time bomb. You can't just &quot;delete&quot; it if you don't know where it is.&lt;/p&gt;
&lt;h3 id=&quot;2-the-gap-why-usual-methods-fail&quot;&gt;2. The Gap: Why usual methods fail&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Regex?&lt;/strong&gt; Good luck maintaining a regex list for every possible API key format, session token, and PII variation in existence. It’s a game of whack-a-mole you will lose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ML-based protection?&lt;/strong&gt; Too slow. If your agent operates in real-time, you cannot afford a 500ms roundtrip to a BERT model just to sanitize specific log lines. PII scans often become the bottleneck.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python-native logic?&lt;/strong&gt; Processing massive text streams in an interpreted language (like Python) adds significant CPU overhead per log line compared to a compiled Go binary. In high-throughput pipes, this latency adds up fast.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Existing Observability Tools?&lt;/strong&gt;&lt;br&gt;
  Systems like Fluent Bit, Datadog, or OpenTelemetry already offer redaction and PII masking, usually via pattern rules and regex. For many workloads that’s perfectly fine. The trade‑off is that these pipelines are not optimized for AI‑agent traces: they either run late in the pipeline (after logs have already left the pod) or rely on configuration‑heavy pattern catalogs that are hard to keep up‑to‑date in a world of ever‑changing API keys and internal tokens.&lt;/p&gt;
&lt;p&gt;  Static scanners like &lt;strong&gt;TruffleHog&lt;/strong&gt; shine for repositories and CI, where you scan code at rest. They’re not meant to sit inline on a hot log stream and make sub‑millisecond decisions on every line.&lt;/p&gt;
&lt;p&gt;  Where &lt;strong&gt;PII‑Shield&lt;/strong&gt; is different is not in “inventing redaction”, but in the combination of techniques tailored for AI agents: entropy + bigram signals for unknown secrets, &lt;strong&gt;deterministic HMAC&lt;/strong&gt; instead of &lt;code&gt;***&lt;/code&gt; for referential integrity, and deep JSON traversal to keep your log schemas intact while still scrubbing sensitive values.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-the-implementation-enter-pii-shield&quot;&gt;3. The Implementation: Enter PII-Shield&lt;/h3&gt;
&lt;p&gt;Meet &lt;strong&gt;PII-Shield&lt;/strong&gt;. It’s a lightweight sidecar written in Go that sits right next to your agent.&lt;/p&gt;
&lt;h4 id=&quot;killer-feature-1-entropy-based-detection&quot;&gt;Killer Feature #1: Entropy-based Detection&lt;/h4&gt;
&lt;p&gt;We don't just search for &quot;password=&quot;. We look for &lt;strong&gt;chaos&lt;/strong&gt;.&lt;br&gt;
API keys and authentication tokens naturally have high &quot;entropy&quot; (randomness/complexity). Normal human speech has low entropy. By calculating the mathematical complexity of strings, we can flag 64-character hex strings or base64 blobs without knowing their specific format.&lt;/p&gt;
&lt;h4 id=&quot;killer-feature-2-deterministic-hmac&quot;&gt;Killer Feature #2: Deterministic HMAC&lt;/h4&gt;
&lt;p&gt;This is the feature that caught attention on Hacker News.&lt;br&gt;
We don't just replace secrets with &lt;code&gt;***&lt;/code&gt;. We turn &lt;code&gt;secret123&lt;/code&gt; into &lt;code&gt;[HIDDEN:a1b2c3]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Input Log:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;user&quot;: &quot;aragossa&quot;, &quot;prompt&quot;: &quot;My key is sk-live-123456&quot;} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;PII-Shield Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;user&quot;: &quot;aragossa&quot;, &quot;prompt&quot;: &quot;My key is [HIDDEN:8f2a1b]&quot;} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;br&gt;
This is a deterministic HMAC (Hash-based Message Authentication Code).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It allows you to &lt;strong&gt;trace&lt;/strong&gt; a specific user or session across multiple log lines without knowing who they are.&lt;/li&gt;
&lt;li&gt;It preserves &lt;strong&gt;Referential Integrity&lt;/strong&gt; for debugging. You can see that &quot;Session A&quot; failed 5 times, but you validly cannot see the Session ID itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;killer-feature-3-statistical-adaptive-threshold&quot;&gt;Killer Feature #3: Statistical Adaptive Threshold&lt;/h4&gt;
&lt;p&gt;PII-Shield doesn't just use a hardcoded number. It learns the &quot;baseline noise&quot; of your logs. By calculating the mean and standard deviation ($2\sigma$), it automatically adjusts the sensitivity to your specific environment.&lt;/p&gt;
&lt;h3 id=&quot;4-the-logic-sidecar-architecture&quot;&gt;4. The Logic: Sidecar Architecture&lt;/h3&gt;
&lt;p&gt;The architecture is dead simple, leveraging the power of Kubernetes sidecars or UNIX pipes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16949894056245681115&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Your agent simply writes logs to stdout. PII-Shield intercepts the stream, scans it in real-time with near-zero overhead, sanitizes it, and passes it forward.&lt;/p&gt;
&lt;h3 id=&quot;5-the-technical-meat&quot;&gt;5. The Technical Meat&lt;/h3&gt;
&lt;p&gt;Why Go? Because we need raw speed and no dependencies.&lt;/p&gt;
&lt;h4 id=&quot;1-entropy-amp-bigrams-the-math-&quot;&gt;1. Entropy &amp;amp; Bigrams (The Math)&lt;/h4&gt;
&lt;p&gt;Here is how we calculate the &quot;Chaos&quot; (Entropy) of a token in &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/aragossa/pii-shield/blob/main/pkg/scanner/scanner.go&quot;&gt;scanner.go&lt;/a&gt;. We use a combination of Shannon Entropy, Character Class Bonuses, and English Bigram analysis.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Bigram Check&lt;/strong&gt; is crucial: it penalizes strings that look like valid English (common letter pairs) and boosts score for &quot;unnatural&quot; strings. (Note: This is optimized for English but can be disabled or tuned via &lt;code&gt;PII_DISABLE_BIGRAM_CHECK&lt;/code&gt; for other languages).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// From scanner.go
func CalculateComplexity(token string) float64 {
    // 1. Shannon Entropy
    entropy := calculateShannon(token) 

    // 2. Class Bonus (Upper, Lower, Digit, Symbols)
    // bonus := float64(classes-1) * 0.5
    bonus := calculateClassBonus(token) 

    // 3. Bigram Check (English Likelihood)
    // Penalizes common English, boosts random noise
    bigramScore := calculateBigramAdjustment(token) 

    return entropy + bonus + bigramScore
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-false-positives-whitelists-&quot;&gt;2. False Positives? (Whitelists)&lt;/h4&gt;
&lt;p&gt;&quot;Entropy is great, but won't it eat my Git Hashes or UUIDs?&quot;&lt;br&gt;
PII-Shield includes built-in &lt;strong&gt;Whitelists&lt;/strong&gt; (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/aragossa/pii-shield/blob/main/pkg/scanner/scanner.go#670-760&quot;&gt;isSafe&lt;/a&gt; function) for standard identifiers like UUIDs, IPv6 addresses, Git Commit hashes (SHA-1), and MongoDB ObjectIDs. This ensures your debugging data stays intact while secrets get redacted.&lt;/p&gt;
&lt;h4 id=&quot;3-credit-card-detection-luhn-algorithm-&quot;&gt;3. Credit Card Detection (Luhn Algorithm)&lt;/h4&gt;
&lt;p&gt;Entropy isn't enough for credit cards, as numbers often have low randomness. PII-Shield includes a high-performance implementation of the &lt;strong&gt;Luhn Algorithm&lt;/strong&gt; to scan for valid card checksums in the stream (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/aragossa/pii-shield/blob/main/pkg/scanner/scanner.go#813-883&quot;&gt;FindLuhnSequences&lt;/a&gt;).&lt;/p&gt;
&lt;h4 id=&quot;4-deep-json-inspection&quot;&gt;4. Deep JSON Inspection&lt;/h4&gt;
&lt;p&gt;For JSON logs, PII-Shield performs deep inspection (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/aragossa/pii-shield/blob/main/pkg/scanner/scanner.go#962-981&quot;&gt;processJSONLine&lt;/a&gt;), preserving the schema while redacting values. &lt;br&gt;
&lt;em&gt;Note: PII-Shield re-serializes JSON (using &lt;code&gt;json.Marshal&lt;/code&gt;), which may change key order/sorting. It guarantees semantic integrity but is best used early in your pipeline, before any byte-sensitive steps (like signing or exact-diff comparisons).&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;5-deterministic-redaction&quot;&gt;5. Deterministic Redaction&lt;/h4&gt;
&lt;p&gt;Here is the HMAC logic using a secure Salt:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func redactWithHMAC(sensitiveData string) string {
    // CurrentConfig.Salt is loaded securely from env vars
    mac := hmac.New(sha256.New, currentConfig.Salt)
    mac.Write([]byte(sensitiveData))
    hash := hex.EncodeToString(mac.Sum(nil))
    // We only keep a short prefix for tracing identity
    return fmt.Sprintf(&quot;[HIDDEN:%s]&quot;, hash[:6])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main loop (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/aragossa/pii-shield/blob/main/cmd/cleaner/main.go&quot;&gt;cmd/cleaner/main.go&lt;/a&gt;) is a highly efficient buffered reader:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func main() {
    reader := bufio.NewScanner(os.Stdin)
    for reader.Scan() {
        text := reader.Text()
        // Core logic: 0 allocations for safe lines
        // Imports github.com/aragossa/pii-shield/pkg/scanner
        cleaned := scanner.ScanAndRedact(text) 
        fmt.Println(cleaned)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;a-note-on-scope-high-entropy-vs-natural-language&quot;&gt;A Note on Scope: High-Entropy vs. Natural Language&lt;/h4&gt;
&lt;p&gt;Let's be clear: PII-Shield is laser-focused on &lt;strong&gt;High-Entropy secrets&lt;/strong&gt; (API keys, tokens, auth headers) and structural patterns (Credit Cards). It is &lt;strong&gt;not&lt;/strong&gt; a magic NLP bullet for detecting names like &quot;John Smith&quot; or free-text addresses. For that, you should treat PII-Shield as a low-latency &quot;first line of defense&quot; for your infrastructure, potentially complemented by heavier offline NLP tools for semantic analysis.&lt;/p&gt;
&lt;h3 id=&quot;6-how-to-try-it&quot;&gt;6. How to try it&lt;/h3&gt;
&lt;p&gt;I am looking for edge cases. If you are building AI agents, try running your trace logs through this and see what it catches (or misses).&lt;/p&gt;
&lt;p&gt;You can run it locally with Docker:&lt;br&gt;
&lt;code&gt;docker run -i -e PII_SALT=&quot;mysalt&quot; pii-shield &amp;lt; logs.txt&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/aragossa/pii-shield&quot;&gt;https://github.com/aragossa/pii-shield&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;br&gt;
Security often trails behind innovation. With the explosion of AI Agents, we are generating massive amounts of sensitive data in logs. PII-Shield is a &quot;drop-in&quot; safety net to ensure your innovation doesn't become a liability.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11147/stop-leaking-api-keys-in-your-ai-agent-logs-a-go-sidecar-approach</guid>
<pubDate>Thu, 05 Feb 2026 21:59:45 +0000</pubDate>
</item>
<item>
<title>Technical debt isn't just messy code. It's a financial liability. </title>
<link>https://coderlegion.com/9019/technical-debt-isnt-just-messy-code-its-a-financial-liability</link>
<description>&lt;p&gt;I started DebtDrone as a CLI tool to measure code complexity. It was fast and effective, but I quickly realized a critical limitation: a CLI only gives you a snapshot. It tells you your code is complex today, but it doesn't tell you if you're burning cash on maintenance or if your refactoring sprints are actually paying off.&lt;br&gt;
To solve this, I’ve been architecting DebtDrone SaaS—transforming a local analyzer into a cloud platform that tracks the lifecycle of technical debt.&lt;/p&gt;
&lt;p&gt;I just published a deep dive into the engineering journey behind this pivot, covering: &lt;/p&gt;
&lt;p&gt; The Backend: Building a concurrent, high-performance analysis engine in Go. &lt;/p&gt;
&lt;p&gt; The Strategy: Moving from &quot;Linting&quot; to &quot;Risk Management&quot; for CTOs. &lt;/p&gt;
&lt;p&gt; The Future: Integrating Generative AI to not just find spaghetti code, but fix it.&lt;/p&gt;
&lt;p&gt;If you are an engineer interested in AST-based analysis or a founder worried about system stability, this is for you.&lt;/p&gt;
</description>
<category>Proj Mgmt Tools</category>
<guid isPermaLink="true">https://coderlegion.com/9019/technical-debt-isnt-just-messy-code-its-a-financial-liability</guid>
<pubDate>Sun, 28 Dec 2025 20:04:22 +0000</pubDate>
</item>
<item>
<title>Engineering DebtDrone: Building a High-Performance AST-Based Static Analyzer in Go</title>
<link>https://coderlegion.com/8619/engineering-debtdrone-building-a-high-performance-ast-based-static-analyzer-in-go</link>
<description>&lt;p&gt;The Limitations of Lexical Analysis&lt;br&gt;
In the world of static analysis, there is a distinct hierarchy of capability. At the bottom, you have lexical analysis—tools that treat code as a stream of strings. These are your grep-based linters. They are incredibly fast ($O(n)$ where $n$ is characters), but they are structurally blind.&lt;/p&gt;
&lt;p&gt;To a regex linter, a function signature is just a pattern to match. It cannot reliably distinguish between a nested closure, a generic type definition, or a comment that looks like code.&lt;/p&gt;
&lt;p&gt;When I set out to build DebtDrone, I wanted to measure Cognitive Complexity, not just cyclomatic complexity. Cyclomatic complexity counts paths through code (if/else/switch), but it fails to account for nesting. A flat switch statement with 50 cases is easy to read. A function with 3 levels of nested loops and conditionals is a maintenance nightmare.&lt;/p&gt;
&lt;p&gt;To measure this accurately, lexical analysis is insufficient. We need Syntactic Analysis. We need a tool that understands the code structure exactly as the compiler does.&lt;/p&gt;
&lt;p&gt;The Engine: Abstract Syntax Trees (AST)&lt;br&gt;
DebtDrone leverages Tree-sitter, an incremental parsing system that builds a concrete syntax tree for a source file. Unlike abstract syntax trees (ASTs) generated by language-specific compilers (like Go's go/ast), Tree-sitter provides a unified interface for traversing trees across 11+ languages.&lt;/p&gt;
&lt;p&gt;Parsing vs. Matching&lt;br&gt;
Consider the following Go snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func process(items []string) {
    if len(items) &amp;gt; 0 {              // +1 Nesting
        for _, item := range items { // +2 Nesting (1 + 1 penalty)
            if item == &quot;stop&quot; {      // +3 Nesting (2 + 1 penalty)
                return
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A regex tool might count the keywords if and for, giving this a score of 3. DebtDrone parses this into a tree structure. By traversing the tree, we can track nesting depth context. Every time we enter a Block node that is a child of an IfStatement or ForStatement, we increment a depth counter.&lt;/p&gt;
&lt;p&gt;The score isn't just 1 + 1 + 1. It is weighted by depth:&lt;/p&gt;
&lt;p&gt;Level 0: Base cost&lt;br&gt;
Level 1: Base cost + 1 (Nesting penalty)&lt;br&gt;
Level 2: Base cost + 2 (Nesting penalty)&lt;br&gt;
This yields a &quot;Cognitive Complexity&quot; score that accurately reflects the mental overhead required to understand the function.&lt;/p&gt;
&lt;p&gt;Architectural Decision: Why Go?&lt;br&gt;
I chose Go for three primary architectural reasons:&lt;/p&gt;
&lt;p&gt;Concurrency Primitives: Static analysis is an &quot;embarrassingly parallel&quot; problem. Each file can be parsed in isolation. Go's Goroutines and Channels allow DebtDrone to fan-out parsing tasks across all available CPU cores with minimal overhead.&lt;/p&gt;
&lt;p&gt;Memory Safety &amp;amp; Speed: While Rust was a contender (and Tree-sitter has excellent Rust bindings), Go provided the fastest iteration loop for the CLI's UX and plumbing, while still offering near-C execution speed.&lt;/p&gt;
&lt;p&gt;Single Binary Distribution: The ultimate goal was a zero-dependency binary that could drop into any CI/CD pipeline (GitHub Actions, GitLab CI, Jenkins) without requiring a runtime like Node.js or Python.&lt;/p&gt;
&lt;p&gt;The Engineering Challenge: CGO and Cross-Compilation&lt;br&gt;
The most significant technical hurdle was the dependency on go-tree-sitter. Because Tree-sitter is implemented in C for performance, incorporating it requires CGO (CGO_ENABLED=1).&lt;/p&gt;
&lt;p&gt;In the Go ecosystem, CGO is often considered a &quot;dealbreaker&quot; for easy distribution. Standard Go cross-compilation (GOOS=linux go build) is trivial because the Go compiler knows how to generate machine code for different architectures. However, once you enable CGO, you are bound by the host system's C linker.&lt;/p&gt;
&lt;p&gt;You cannot compile a macOS binary on a Linux CI runner using the standard gcc. You need a macOS-compatible linker and system headers.&lt;/p&gt;
&lt;p&gt;The Solution: goreleaser-cross&lt;br&gt;
To solve this, I architected the release pipeline around Dockerized Cross-Compilers. Instead of relying on the bare-metal runner, the release process spins up a container (ghcr.io/goreleaser/goreleaser-cross) that contains a massive collection of cross-compilation toolchains:&lt;/p&gt;
&lt;p&gt;o64-clang: For building macOS (Darwin) binaries on Linux.&lt;br&gt;
mingw-w64: For building Windows binaries on Linux.&lt;br&gt;
aarch64-linux-gnu-gcc: For ARM64 Linux builds.&lt;br&gt;
This configuration is managed via .goreleaser.yaml, where we dynamically inject the correct C compiler (CC) based on the target architecture:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;builds:
  - id: debtdrone-cli
    env:
      - CGO_ENABLED=1
      # Dynamic Compiler Selection
      - CC={{ if eq .Os &quot;darwin&quot; }}o64-clang{{ else if eq .Os &quot;windows&quot; }}x86_64-w64-mingw32-gcc{{ else }}gcc{{ end }}
      - CXX={{ if eq .Os &quot;darwin&quot; }}o64-clang++{{ else if eq .Os &quot;windows&quot; }}x86_64-w64-mingw32-g++{{ else }}g++{{ end }}
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64
This setup allows a standard Ubuntu GitHub Actions runner to produce native binaries for Mac (Intel/Apple Silicon), Windows, and Linux in a single pass.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Distribution Strategy: Homebrew Taps&lt;br&gt;
For v1.0.0, accessibility was key. While curl | bash scripts are common, they lack version management. I implemented a custom Homebrew Tap to treat DebtDrone as a first-class citizen on macOS.&lt;/p&gt;
&lt;p&gt;By adding a brews section to the GoReleaser config, the pipeline automatically:&lt;/p&gt;
&lt;p&gt;Generates a Ruby formula (debtdrone.rb) with the correct SHA256 checksums.&lt;br&gt;
Commits this formula to a separate homebrew-tap repository.&lt;br&gt;
Allows users to install/upgrade via brew install endrilickollari/tap/debtdrone.&lt;br&gt;
Beyond the Code: Impact by Role&lt;br&gt;
While the engineering behind DebtDrone is fascinating, its real value lies in how it empowers different stakeholders in the software development lifecycle.&lt;/p&gt;
&lt;p&gt;For the Developer: The &quot;Self-Check&quot; Before Commit&lt;br&gt;
We've all been there: you're deep in the zone, solving a complex edge case. You add a flag, then a nested if, then a loop to handle a collection. It works, but you've just created a &quot;complexity bomb.&quot;&lt;/p&gt;
&lt;p&gt;DebtDrone acts as a mirror. By running debtdrone check . locally, you get immediate feedback:&lt;/p&gt;
&lt;p&gt;&quot;Warning: processTransaction has a complexity score of 25 (Threshold: 15).&quot;&lt;/p&gt;
&lt;p&gt;This prompts a refactor before the code even reaches a pull request. It encourages writing smaller, more composable functions, which are inherently easier to test and debug.&lt;/p&gt;
&lt;p&gt;For the Team Lead: Objective Code Quality&lt;br&gt;
Code reviews can be subjective. &quot;This looks too complex&quot; is an opinion; &quot;This function has a complexity score of 42&quot; is a fact.&lt;/p&gt;
&lt;p&gt;DebtDrone provides an objective baseline for discussions. It helps leads identify:&lt;/p&gt;
&lt;p&gt;Hotspots: Which files are the most dangerous to touch?&lt;br&gt;
Trends: Is the codebase getting cleaner or messier over time?&lt;br&gt;
Gatekeeping: Preventing technical debt from leaking into the main branch by setting hard thresholds in CI.&lt;br&gt;
For DevOps: The Quality Gate&lt;br&gt;
In a CI/CD pipeline, DebtDrone serves as a lightweight, fast quality gate. Because it compiles to a single binary with zero dependencies, it can be dropped into any pipeline (GitHub Actions, GitLab CI, Jenkins) without complex setup.&lt;/p&gt;
&lt;p&gt;It supports standard exit codes (non-zero on failure) and can output results in JSON for integration with dashboarding tools. This ensures that &quot;maintainability&quot; is treated with the same rigor as &quot;passing tests.&quot;&lt;/p&gt;
&lt;p&gt;For the Business Analyst: Velocity &amp;amp; ROI&lt;br&gt;
Why should a business care about Abstract Syntax Trees? Because complexity kills velocity.&lt;/p&gt;
&lt;p&gt;High cognitive complexity directly correlates with:&lt;/p&gt;
&lt;p&gt;Longer onboarding times for new developers.&lt;br&gt;
Higher bug rates due to misunderstood logic.&lt;br&gt;
Slower feature delivery as developers spend more time deciphering old code than writing new code.&lt;br&gt;
By investing in tools like DebtDrone, organizations are investing in their long-term agility. It's not just about &quot;clean code&quot;—it's about sustainable development speed.&lt;/p&gt;
&lt;p&gt;Conclusion&lt;br&gt;
DebtDrone v1.0.0 represents a shift from &quot;linting as an afterthought&quot; to &quot;architectural analysis as a standard.&quot; By moving from Regex to ASTs, we eliminate false positives. By solving the CGO cross-compilation puzzle, we ensure the tool is available everywhere.&lt;/p&gt;
&lt;p&gt;The result is a CLI that runs locally, respects data privacy, and provides immediate, actionable feedback on technical debt.&lt;/p&gt;
&lt;p&gt;GitHub Repository: endrilickollari/debtdrone-cli&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/8619/engineering-debtdrone-building-a-high-performance-ast-based-static-analyzer-in-go</guid>
<pubDate>Fri, 19 Dec 2025 11:11:48 +0000</pubDate>
</item>
<item>
<title>Building Herctually: An AI Research Agent written in GO</title>
<link>https://coderlegion.com/7067/building-herctually-an-ai-research-agent-written-in-go</link>
<description>&lt;p&gt;Every week, I review competitors' articles and summarize them in a report. It’s repetitive and not exactly the favorite part of my week. I have been using a few new AI tools, such as Amp, Opencode, and I thought it would be nice to have a tool that could make things easier. So I built &quot;Herctually,&quot; an AI research assistant that automates the boring parts.&lt;/p&gt;
&lt;p&gt;We’ll try to build Hertcually in Go, from model setup to giving it powers like web search, reading, and report writing.&lt;/p&gt;
&lt;p&gt;But first, let's align on what an agent is. According to Thorsten Bell, an agent is &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/J1-W9O3n7j8?t=72&quot;&gt;“an LLM with access to tools, giving it the ability to modify something outside the context window“&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;How I see it is that an agent is a large language model (LLM) that can take actions, not just talk. It can search, write, read, and execute. Think of it as a chatbot with hands.&lt;/p&gt;
&lt;p&gt;We’ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Access to a model (via OpenRouter, so we can easily test GPT-4, Claude, etc.)&lt;/li&gt;
&lt;li&gt;A use case — in our case, automating research.&lt;/li&gt;
&lt;li&gt;A few tools — to extend the model’s abilities.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;setting-up-the-project&quot;&gt;Setting Up the Project&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir herctually
cd herctually
go mod init herctually
touch main.go
mkdir agent
touch agent/agent.go
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, in our &lt;code&gt;main.go&lt;/code&gt; file, we would like to set up the structure for running our agent. Some errors may appear initially, they’ll resolve once we add the missing pieces.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// main.go


package main

import (
 &quot;bufio&quot;
 &quot;context&quot;
 &quot;fmt&quot;
 &quot;log&quot;
 &quot;os&quot;

 &quot;github.com/openai/openai-go/v3&quot;
 &quot;github.com/openai/openai-go/v3/option&quot;
 &quot;herctually/agent&quot;
)

func main() {

 apiKey := os.Getenv(&quot;OPENROUTER_APIKEY&quot;)
 if apiKey == &quot;&quot; {
  log.Panic(&quot;OPENROUTER_APIKEY environment variable is required&quot;)
  os.Exit(1)
 }

 baseURL := os.Getenv(&quot;OPENROUTER_BASEURL&quot;)
 if baseURL == &quot;&quot; {
  log.Panic(&quot;OPENROUTER_BASEURL environment variable is required&quot;)
  os.Exit(1)
 }

 scanner := bufio.NewScanner(os.Stdin)
 getUserMessage := func() (string, bool) {
  if !scanner.Scan() {
   return &quot;&quot;, false
  }

  return scanner.Text(), true
 }

 llm := openai.NewClient(
  option.WithAPIKey(apiKey),
  option.WithBaseURL(baseURL),
 )

 ag := agent.New(&amp;amp;llm, getUserMessage)

 if err := ag.Run(context.Background()); err != nil {
  fmt.Printf(&quot;Error: %s\n&quot;, err)
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let's add the agent code.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// agent/agent.go

package agent

import (
 &quot;context&quot;
 &quot;fmt&quot;

 &quot;github.com/openai/openai-go/v3&quot;
)

type Agent struct {
 llm            *openai.Client
 getUserMessage func() (string, bool)
}

func New(llm *openai.Client, getUserMessage func() (string, bool)) *Agent {
 return &amp;amp;Agent{
  llm:            llm,
  getUserMessage: getUserMessage,
 }
}

var systemPrompt = `&amp;lt;role&amp;gt;
   You are 'Herctually,' a sharp, logical, and strategic research assistant, focused on clarity and precise argumentation.
   &amp;lt;/role&amp;gt;
   &amp;lt;personality&amp;gt;
   Polite, confident, analytical. You challenge ideas before you support them. You enjoy exposing weak logic. You’re not here to please, you’re here to clarify.
   &amp;lt;/personality&amp;gt;
   &amp;lt;rules&amp;gt;
   - Be concise but deep.
   - Separate facts, inferences, and speculation.
   - Always surface what others overlook.
   - Correct errors ruthlessly, including the user’s.
   &amp;lt;/rules&amp;gt;
   &amp;lt;goal&amp;gt;
   Deliver insights with precision, not politeness. Your output should feel like a distilled intelligence briefing from someone who actually thinks.
   &amp;lt;/goal&amp;gt;`

func (a *Agent) Run(ctx context.Context) error {
 conversation := []openai.ChatCompletionMessageParamUnion{
  openai.SystemMessage(systemPrompt),
 }

 fmt.Println(&quot;Chat with Herctually (use 'ctrl-c' to quit)&quot;)

 for {
  fmt.Print(&quot;\u001b[94mYou\u001b[0m: &quot;)
  userInput, ok := a.getUserMessage()
  if !ok {
   break
  }
  userMessage := openai.UserMessage(userInput)
  conversation = append(conversation, userMessage)

  message, err := a.runInference(ctx, conversation)
  if err != nil {
   return err
  }
  conversation = append(conversation, message.Choices[0].Message.ToParam())
  for _, content := range message.Choices {
   fmt.Printf(&quot;\u001b[93mHerctually\u001b[0m: %s\n&quot;, content.Message.Content)
  }
 }

 return nil
}

func (a *Agent) runInference(ctx context.Context, conversation []openai.ChatCompletionMessageParamUnion) (*openai.ChatCompletion, error) {

 message, err := a.llm.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
  Model:               openai.ChatModelGPT4o,
  MaxCompletionTokens: openai.Int(1024),
  Messages:            conversation,
 })
 return message, err
}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, let's add our environment variables, install our dependencies, and run it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export OPENROUTER_APIKEY=&quot;sk-xxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;
export OPENROUTER_BASEURL=&quot;https://openrouter.ai/api/v1&quot;

# Download the dependencies
go mod tidy
# Run it
go run main.go
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can just talk to Herctually, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ go run main.go
Chat with Herctually (use 'ctrl-c' to quit)
You: Hi
Herctually: Greetings. Do you have a specific question or topic you'd like to discuss? I'm here to provide clear and precise insights.
You: What is https://oreoluwabs.com/ about
Herctually: I currently do not have the capability to browse the internet in real-time or access external content. However, I can offer guidance on how to assess a website's legitimacy or purpose.
You:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have a basic agent that has a personality, we can ask and can respond on a subject matter!&lt;/p&gt;
&lt;p&gt;But how do we make her do the things we mentioned, search the web, read, and write reports? The answer is Tools. Models allow us to enhance their output by calling tools.&lt;/p&gt;
&lt;p&gt;Let’s give her the ability to be able to search the web.&lt;/p&gt;
&lt;p&gt;Let's make an &lt;code&gt;agent/tools.go&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// agent/tools.go
package agent

import (
 &quot;encoding/json&quot;
 &quot;fmt&quot;
 &quot;io&quot;
 &quot;io/fs&quot;
 &quot;net/http&quot;
 &quot;os&quot;
 &quot;path&quot;
 &quot;path/filepath&quot;
 &quot;strings&quot;

 &quot;github.com/openai/openai-go/v3&quot;
)

type ToolDefinition struct {
 Name        string                    `json:&quot;name&quot;`
 Description string                    `json:&quot;description&quot;`
 InputSchema openai.FunctionParameters `json:&quot;input_schema&quot;`
 Function    func(input json.RawMessage) (string, error)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A tool requires a Name we can use as an identifier, a description to describe what the tool can do, a schema (usually jsonschema) to describe what parameters the tool has and requires, and lastly, the function to run.&lt;/p&gt;
&lt;p&gt;We would need to make this update in &lt;code&gt;agent/agent.go&lt;/code&gt;. This allows us to specify what tools our agent has access to.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// agent/agent.go

type Agent struct {
 llm            *openai.Client
 getUserMessage func() (string, bool)
 tools          []ToolDefinition
}

func New(llm *openai.Client, getUserMessage func() (string, bool), tools []ToolDefinition) *Agent {
 return &amp;amp;Agent{
  llm:            llm,
  getUserMessage: getUserMessage,
  tools:          tools,
 }
}

func (a *Agent) Run(ctx context.Context) error {
 conversation := []openai.ChatCompletionMessageParamUnion{
  openai.SystemMessage(systemPrompt),
 }

 fmt.Println(&quot;Chat with Herctually (use 'ctrl-c' to quit)&quot;)

 readUserInput := true
 for {
  if readUserInput {
   fmt.Print(&quot;\u001b[94mYou\u001b[0m: &quot;)
   userInput, ok := a.getUserMessage()
   if !ok {
    break
   }
   userMessage := openai.UserMessage(userInput)
   conversation = append(conversation, userMessage)
  }

  message, err := a.runInference(ctx, conversation)
  if err != nil {
   return err
  }
  conversation = append(conversation, message.Choices[0].Message.ToParam())

  toolResults := []openai.ChatCompletionMessageParamUnion{}

  toolCalls := message.Choices[0].Message.ToolCalls
  for _, call := range toolCalls {
   result := a.executeTool(call.ID, call.Function.Name, []byte(call.Function.Arguments))

   toolResults = append(toolResults, result)
  }

  for _, content := range message.Choices {
   if content.Message.Content != &quot;&quot; {
    fmt.Printf(&quot;\u001b[93mHerctually\u001b[0m: %s\n&quot;, content.Message.Content)
   }
  }

  if len(toolResults) == 0 {
   readUserInput = true
   continue
  }
  readUserInput = false

  for _, ccmtcu := range toolResults {
   conversation = append(conversation, ccmtcu)
  }
 }
 return nil
}

func (a *Agent) executeTool(id, name string, input json.RawMessage) openai.ChatCompletionMessageParamUnion {
 var toolDef ToolDefinition
 var found bool

 for _, tool := range a.tools {
  if tool.Name == name {
   toolDef = tool
   found = true
   break
  }
 }

 if !found {
  return openai.ToolMessage(&quot;tool not found&quot;, id)
 }

 fmt.Printf(&quot;\u001b[92mtool\u001b[0m: %s(%s)\n&quot;, name, input)
 response, err := toolDef.Function(input)
 if err != nil {
  return openai.ToolMessage(err.Error(), id)
 }

 return openai.ToolMessage(response, id)
}


func (a *Agent) runInference(ctx context.Context, conversation []openai.ChatCompletionMessageParamUnion) (*openai.ChatCompletion, error) {
 opentools := []openai.ChatCompletionToolUnionParam{}

 for _, tool := range a.tools {
  opentools = append(opentools, openai.ChatCompletionToolUnionParam{
   OfFunction: &amp;amp;openai.ChatCompletionFunctionToolParam{
    Function: openai.FunctionDefinitionParam{
     Name:        tool.Name,
     Description: openai.String(tool.Description),
     Parameters:  tool.InputSchema,
    },
   },
  })
 }

 message, err := a.llm.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
  Model:               openai.ChatModelGPT4o,
  MaxCompletionTokens: openai.Int(1024),
  Messages:            conversation,
  Tools:               opentools,
 })
 return message, err
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can create our first tool definition, &lt;code&gt;web_search&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// agent/tools.go


import (
 &quot;encoding/json&quot;
 &quot;fmt&quot;
 &quot;io&quot;
 &quot;io/fs&quot;
 &quot;net/http&quot;
 &quot;os&quot;
 &quot;path&quot;
 &quot;path/filepath&quot;
 &quot;strings&quot;

 // Add this
 &quot;github.com/invopop/jsonschema&quot;
 &quot;github.com/openai/openai-go/v3&quot;
)


type SurfTheWebInput struct {
 Url string `json:&quot;url&quot; jsonschema_description:&quot;The url to fetch data from.&quot;`
}

var SurfTheWebDefinition = ToolDefinition{
 Name:        &quot;web_search&quot;,
 Description: &quot;Search the web for information.&quot;,
 InputSchema: SurfTheWebInputSchema,
 Function:    SurfTheWeb,
}
var SurfTheWebInputSchema = GenerateSchema[SurfTheWebInput]()

func SurfTheWeb(input json.RawMessage) (string, error) {
 surfWebInput := SurfTheWebInput{}
 err := json.Unmarshal(input, &amp;amp;surfWebInput)
 if err != nil {
  panic(err)
 }

 req, err := http.NewRequest(&quot;GET&quot;, surfWebInput.Url, nil)
 if err != nil {
  return &quot;&quot;, err
 }

 client := &amp;amp;http.Client{}
 resp, err := client.Do(req)
 if err != nil {
  return &quot;&quot;, err
 }
 defer resp.Body.Close()

 if resp.StatusCode != http.StatusOK {
  return &quot;&quot;, fmt.Errorf(&quot;search api returned non-200 status: %s&quot;, resp.Status)
 }

 bodyBytes, err := io.ReadAll(resp.Body)
 if err != nil {
  return &quot;&quot;, fmt.Errorf(&quot;failed to read response body: %w&quot;, err)
 }
 bodyString := string(bodyBytes)

 return bodyString, nil
}


func GenerateSchema[T any]() openai.FunctionParameters {
 reflector := jsonschema.Reflector{
  AllowAdditionalProperties: false,
  DoNotReference:            true,
 }

 var v T
 schema := reflector.Reflect(v)

 return openai.FunctionParameters{
  &quot;type&quot;:       schema.Type,
  &quot;properties&quot;: schema.Properties,
  &quot;required&quot;:   schema.Required,
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// main.go

func main() {
    // [... previous code ...]
 ag := agent.New(&amp;amp;llm, getUserMessage,
  []agent.ToolDefinition{
   agent.SurfTheWebDefinition,
  })
 // [... previous code ...]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;go mod tidy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let's run our project.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ go run main.go
Chat with Herctually (use 'ctrl-c' to quit)
You: Hi
Herctually: Hello. What\'s on your mind?
You: What is https://oreoluwabs.com/ about
tool: web_search({&quot;url&quot;:&quot;https://oreoluwabs.com/&quot;})
Herctually: The website &quot;Oreoluwa Salami&quot; presents a software developer based in Lagos, Nigeria. With over five years of experience, Oreoluwa is skilled in HTML, CSS, JavaScript, and various frameworks. The focus is on creating responsive and user-friendly web applications, with an emphasis on engaging user experiences. The site highlights their expertise in scalable and accessible web and mobile application development. It includes sections for projects, articles, and contact information, aiming to showcase professional capabilities and invite collaborations. If you need further specific details, check the website directly.
You:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that she can search the web, let's allow her to be able to write and read reports&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// agent/tools.go

type EditFileInput struct {
 Path   string `json:&quot;path&quot; jsonschema_description:&quot;The path to the file&quot;`
 OldStr string `json:&quot;old_str&quot; jsonschema_description:&quot;Text to search for - must match exactly and must only have one match exactly&quot;`
 NewStr string `json:&quot;new_str&quot; jsonschema_description:&quot;Text to replace old_str with&quot;`
}

var EditFileDefinition = ToolDefinition{
 Name: &quot;edit_file&quot;,
 Description: `Make edits to a text file.
 Replaces 'old_str' with 'new_str' in the given file. 'old_str' and 'new_str' MUST be different from each other.
 If the files specified with the path don't exist, they will be created.`
 InputSchema: EditFileInputSchema,
 Function:    EditFile,
}
var EditFileInputSchema = GenerateSchema[EditFileInput]()

func EditFile(input json.RawMessage) (string, error) {
 editFileInput := EditFileInput{}
 err := json.Unmarshal(input, &amp;amp;editFileInput)
 if err != nil {
  return &quot;&quot;, err
 }

 if editFileInput.Path == &quot;&quot; || editFileInput.OldStr == editFileInput.NewStr {
  return &quot;&quot;, fmt.Errorf(&quot;invalid input parameters&quot;)
 }

 content, err := os.ReadFile(editFileInput.Path)
 if err != nil {
  if os.IsNotExist(err) &amp;amp;&amp;amp; editFileInput.OldStr == &quot;&quot; {
   return createNewFile(editFileInput.Path, editFileInput.NewStr)
  }
 }

 oldContent := string(content)
 newContent := strings.Replace(oldContent, editFileInput.OldStr, editFileInput.NewStr, -1)

 if oldContent == newContent &amp;amp;&amp;amp; editFileInput.OldStr != &quot;&quot; {
  return &quot;&quot;, fmt.Errorf(&quot;old_str not found in file&quot;)
 }

 err = os.WriteFile(editFileInput.Path, []byte(newContent), 0644)
 if err != nil {
  return &quot;&quot;, nil
 }

 return &quot;Ok&quot;, nil
}

func createNewFile(filePath, content string) (string, error) {
 dir := path.Dir(filePath)
 if dir != &quot;.&quot; {
  err := os.Mkdir(dir, 0755)
  if err != nil {
   return &quot;&quot;, fmt.Errorf(&quot;failed to create a directory: %w&quot;, err)
  }
 }

 err := os.WriteFile(filePath, []byte(content), 0644)
 if err != nil {
  return &quot;&quot;, fmt.Errorf(&quot;failed to create file: %w&quot;, err)
 }

 return fmt.Sprintf(&quot;Successfully created file %s&quot;, filePath), nil
}



type ListFilesInput struct {
 Path string `json:&quot;path&quot; jsonschema_description:&quot;Optional relative path to list files from. Defaults to current directory if not provided.&quot;`
}

var ListFilesDefinition = ToolDefinition{
 Name:        &quot;list_files&quot;,
 Description: &quot;List files and directories at a given path, if no path is provided, list files in the current directory.&quot;,
 InputSchema: ListFilesInputSchema,
 Function:    ListFiles,
}
var ListFilesInputSchema = GenerateSchema[ListFilesInput]()

func ListFiles(input json.RawMessage) (string, error) {
 listFilesInput := ListFilesInput{}
 fmt.Println(string(input))
 err := json.Unmarshal(input, &amp;amp;listFilesInput)
 if err != nil {
  panic(err)
 }
 dir := &quot;.&quot;
 if listFilesInput.Path != &quot;&quot; {
  dir = listFilesInput.Path
 }

 var files []string
 err = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
  if err != nil {
   return err
  }

  relPath, err := filepath.Rel(dir, path)
  if err != nil {
   return err
  }

  if relPath != &quot;.&quot; {
   if info.IsDir() {
    files = append(files, relPath+&quot;/&quot;)
   } else {
    files = append(files, relPath)
   }
  }
  return nil
 })

 result, err := json.Marshal(files)
 if err != nil {
  return &quot;&quot;, err
 }

 return string(result), nil
}


var ReadFileDefinition = ToolDefinition{
 Name:        &quot;read_file&quot;,
 Description: &quot;Read the contents of a given relative file path.&quot;,
 InputSchema: ReadFileInputSchema,
 Function:    ReadFile,
}

type ReadFileInput struct {
 Path string `json:&quot;path&quot; jsonschema_description:&quot;The relative path of a file in the working directory.&quot;`
}

var ReadFileInputSchema = GenerateSchema[ReadFileInput]()

func ReadFile(input json.RawMessage) (string, error) {
 readFileInput := ReadFileInput{}
 err := json.Unmarshal(input, &amp;amp;readFileInput)
 if err != nil {
  panic(err)
 }

 content, err := os.ReadFile(readFileInput.Path)
 if err != nil {
  return &quot;&quot;, err
 }

 return string(content), nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ go run main.go
Chat with Herctually (use 'ctrl-c' to quit)
You: Hi name is Ore! How is it going?
Herctually: Hello, Ore! My name is Herctually. I'm here to help you reason through things logically. How can I assist you today?
You: Analyse and write a report on https://twothreads.app/. Write the report in two threads.md file!
tool: web_search({&quot;url&quot;:&quot;https://twothreads.app/&quot;})
tool: edit_file({&quot;path&quot;:&quot;twothreads.md&quot;,&quot;old_str&quot;:&quot;&quot;,&quot;new_str&quot;:&quot;# Two Threads - AI-Powered Wardrobe Management &amp;amp; Outfit Recommendations\n\n## Overview\n\n**Website:** [Two Threads](https://twothreads.app/)\n\nTwo Threads is an AI-powered application aimed at revolutionizing wardrobe management and outfit recommendations. It addresses the common frustration of \&quot;having nothing to wear\&quot; by providing AI-driven solutions tailored to individual styles, weather conditions, and occasions.\n\n### Key Features\n- **Wardrobe Management:** Allows users to catalog and organize their clothing with photos, tags, and details.\n- **AI Outfit Recommendations:** Provides personalized suggestions that consider the weather, occasion, and user style preferences.\n- **Style Tracking:** Monitors fashion evolution and gives insights to refine tastes.\n- **AI Styling Assistant \&quot;Andi\&quot;:** Offers real-time fashion advice and inspiration.\n\n## User Experience\n\nThe platform aims to streamline the decision-making process linked to daily dressing routines. Whether it's cataloging current wardrobe items or getting fresh outfit ideas, the service promises to transform how users engage with their clothing.\n\n### How It Works\n1. **Add Your Wardrobe:** Upload photos and details of clothing items to build a personalized closet.\n2. **Get Recommendations:** Receive tailored outfit suggestions based on input details like mood or occasion.\n3. **Track &amp;amp; Refine:** Log wardrobe usage and give feedback for refined future suggestions.\n4. **Style Evolution:** Review fashion habits and adapt wardrobe choices over time.\n\n## Pricing\n\n- **Free:** Allows up to 50 clothing items and provides basic AI recommendations, community support.\n- **Pro:** Priced at $3/month, offering unlimited clothing entries, unlimited outfit suggestions, AI-powered styling assistance, and priority support.\n\n## Conclusion\nTwo Threads is targeting individuals looking to make their wardrobe choices more efficient and stylish. It combines practical workability with a personal styling assistant for users seeking to overcome fashion fatigue.&quot;})
Herctually: The report on Two Threads has been written and saved to a file named `twothreads.md`. If you need any more details or further analysis, feel free to ask!
You:
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Now we have Herctually not just a chatbot, but an assistant that can think, search, read, and write. The goal wasn’t to build something huge, but to make the boring weekly research process faster. Now it can search, read, and write reports on its own and as an addon to understand how agents actually work: a loop between a model, tools, and your input.&lt;/p&gt;
&lt;p&gt;From here, you can add memory, better web reading, reporting templates or whatever you want.&lt;/p&gt;
</description>
<category>AI</category>
<guid isPermaLink="true">https://coderlegion.com/7067/building-herctually-an-ai-research-agent-written-in-go</guid>
<pubDate>Thu, 06 Nov 2025 22:33:54 +0000</pubDate>
</item>
<item>
<title>Building a Kubernetes Operator in Go with Kube-Shift</title>
<link>https://coderlegion.com/4158/building-a-kubernetes-operator-in-go-with-kube-shift</link>
<description>&lt;p&gt;Kubernetes has become the de-facto standard for container orchestration. As its adoption grows, so does the need for extending its capabilities to manage complex, stateful applications. This is where Kubernetes Operators come in. In this article, we'll explore how to build a custom Kubernetes Operator from scratch using Go and the &lt;code&gt;controller-runtime&lt;/code&gt; library. We'll use kube-shift, a custom operator for database schema migrations, as a real-world example to guide us through the process.&lt;/p&gt;
&lt;h3 id=&quot;the-why-what-problem-do-operators-solve-&quot;&gt;The &quot;Why&quot;: What Problem Do Operators Solve?&lt;/h3&gt;
&lt;p&gt;Before we dive into the code, let's understand the problem operators solve. Managing applications on Kubernetes often involves more than just deploying a set of pods. Many applications require complex lifecycle management, including installation, upgrades, and handling failures. Operators automate these tasks by extending the Kubernetes API with Custom Resource Definitions (CRDs) and a custom controller to manage them.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;kube-shift&lt;/code&gt; project is a perfect example. It introduces a &lt;code&gt;DatabaseMigration&lt;/code&gt; resource to Kubernetes, allowing you to manage database schema changes as part of your deployment process, just like any other Kubernetes resource.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10478430094407607469&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;setting-the-stage-project-scaffolding-with-kubebuilder&quot;&gt;Setting the Stage: Project Scaffolding with Kubebuilder&lt;/h3&gt;
&lt;p&gt;The kube-shift operator, like many Go-based operators, is built using the Kubebuilder framework. Kubebuilder provides a set of tools to quickly scaffold a new operator project, including the boilerplate for CRDs, controllers, and other necessary components.&lt;/p&gt;
&lt;p&gt;The entry point of our operator is &lt;code&gt;cmd/main.go&lt;/code&gt; This file is responsible for setting up and running the controller manager.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// cmd/main.go
package main
 
import (
// ... imports
&quot;github.com/mohit-nagaraj/kube-shift/api/v1alpha1&quot;
&quot;github.com/mohit-nagaraj/kube-shift/internal/controller&quot;
// ...
)
 
func main() {
// ... flag parsing and setup

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
// ... options
})
if err != nil {
setupLog.Error(err, &quot;unable to start manager&quot;)
os.Exit(1)
}
 
if err = (&amp;amp;controller.DatabaseMigrationReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, &quot;unable to create controller&quot;, &quot;controller&quot;, &quot;DatabaseMigration&quot;)
os.Exit(1)
}
 
// ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we create a new manager and register our &lt;code&gt;DatabaseMigrationReconciler&lt;/code&gt; with it. The manager is responsible for running the controller and managing its lifecycle.&lt;/p&gt;
&lt;h3 id=&quot;defining-our-custom-resource-the-databasemigration-crd&quot;&gt;Defining Our Custom Resource: The DatabaseMigration CRD&lt;/h3&gt;
&lt;p&gt;The first step in creating our operator is to define the custom resource it will manage. This is done in &lt;code&gt;api/v1alpha1/databasemigration_types.go&lt;/code&gt;. This file defines the Go types that represent our DatabaseMigration CRD.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// api/v1alpha1/databasemigration_types.go
package v1alpha1
 
import (
metav1 &quot;k8s.io/apimachinery/pkg/apis/meta/v1&quot;
)
 
// DatabaseMigrationSpec defines the desired state of DatabaseMigration
type DatabaseMigrationSpec struct {
Database  DatabaseConfig  `json:&quot;database&quot;`
Migration MigrationConfig `json:&quot;migration&quot;`
}
 
// DatabaseMigrationStatus defines the observed state of DatabaseMigration
type DatabaseMigrationStatus struct {
Phase   MigrationPhase `json:&quot;phase,omitempty&quot;`
Message string         `json:&quot;message,omitempty&quot;`
}
 
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
 
// DatabaseMigration is the Schema for the databasemigrations API
type DatabaseMigration struct {
metav1.TypeMeta   `json:&quot;,inline&quot;`
metav1.ObjectMeta `json:&quot;metadata,omitempty&quot;`
 
Spec   DatabaseMigrationSpec   `json:&quot;spec,omitempty&quot;`
Status DatabaseMigrationStatus `json:&quot;status,omitempty&quot;`
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;DatabaseMigration&lt;/code&gt; struct embeds &lt;code&gt;metav1.TypeMeta&lt;/code&gt; and &lt;code&gt;metav1.ObjectMeta&lt;/code&gt;, which are common to all Kubernetes objects. The &lt;code&gt;Spec&lt;/code&gt; field defines the desired state of our resource, while the &lt;code&gt;Status&lt;/code&gt; field represents its current state.&lt;/p&gt;
&lt;h3 id=&quot;the-heart-of-the-operator-the-controller-and-reconciliation-loop&quot;&gt;The Heart of the Operator: The Controller and Reconciliation Loop&lt;/h3&gt;
&lt;p&gt;With our CRD defined, we need a controller to act on it. The controller's logic is implemented in &lt;code&gt;internal/controller/databasemigration_controller.go&lt;/code&gt;. The core of the controller is the &lt;code&gt;Reconcile&lt;/code&gt; function. This function is called every time a &lt;code&gt;DatabaseMigration&lt;/code&gt; resource is created, updated, or deleted.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// internal/controller/databasemigration_controller.go
 
// DatabaseMigrationReconciler reconciles a DatabaseMigration object
type DatabaseMigrationReconciler struct {
client.Client
Scheme *runtime.Scheme
Log    logr.Logger
// ... other fields
}
 
// +kubebuilder:rbac:groups=database.mohitnagaraj.in,resources=databasemigrations,verbs=get;list;watch;create;update;patch;delete
// ... other rbac markers
 
func (r *DatabaseMigrationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues(&quot;databasemigration&quot;, req.NamespacedName)
 
// your logic here
 
return ctrl.Result{}, nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Reconcile&lt;/code&gt; function receives the name and namespace of the &lt;code&gt;DatabaseMigration&lt;/code&gt; resource that triggered the event. It's the reconciler's job to fetch the resource and take the necessary actions to bring the system to the desired state defined in the resource's &lt;code&gt;Spec&lt;/code&gt;. This could involve creating jobs, pods, or other resources to perform the database migration.&lt;/p&gt;
&lt;h3 id=&quot;advanced-reconciliation-beyond-the-basics&quot;&gt;Advanced Reconciliation: Beyond the Basics&lt;/h3&gt;
&lt;p&gt;A robust operator does more than just create resources. kube-shift demonstrates several advanced concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Status Updates: The controller updates the Status field of the DatabaseMigration resource to reflect the current state of the migration. This provides valuable feedback to users.&lt;/li&gt;
&lt;li&gt;Finalizers: To ensure graceful cleanup, kube-shift uses a finalizer. The DatabaseMigrationFinalizer is added to the resource on creation. This prevents the resource from being deleted until the controller has performed any necessary cleanup tasks.&lt;/li&gt;
&lt;li&gt;Leader Election: In a high-availability setup, you might run multiple instances of your operator. Leader election ensures that only one instance is active at a time, preventing conflicts. This is configured in cmd/main.go.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;testing-your-operator&quot;&gt;Testing Your Operator&lt;/h3&gt;
&lt;p&gt;Testing is crucial for any production-grade software. kube-shift includes both unit and integration tests.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unit Tests: These tests focus on individual components of the operator in isolation.&lt;/li&gt;
&lt;li&gt;Integration Tests: The project uses the envtest package to run integration tests against a real Kubernetes API server. You can see this in internal/controller/suite_test.go, which sets up a test environment for the controller tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building a Kubernetes Operator is a powerful way to extend Kubernetes and automate complex application management tasks. By leveraging tools like Kubebuilder and controller-runtime, you can create robust, production-ready operators. The kube-shift project is an excellent example of how to apply these concepts to solve a real-world problem.&lt;/p&gt;
&lt;p&gt;To learn more, I encourage you to explore the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mohit-nagaraj/kube-shift&quot;&gt;kube-shift repository&lt;/a&gt; on GitHub. You'll find the complete source code, along with more details on how to build, deploy, and use the operator. Happy coding!&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/4158/building-a-kubernetes-operator-in-go-with-kube-shift</guid>
<pubDate>Sun, 20 Jul 2025 07:50:26 +0000</pubDate>
</item>
<item>
<title>Gopher It! Your Go-To Guide for Glorious Go Backends</title>
<link>https://coderlegion.com/4157/gopher-it-your-go-to-guide-for-glorious-go-backends</link>
<description>&lt;p&gt;Hey there, fellow code wranglers and digital alchemists! Ever felt like your backend code is a tangled mess of spaghetti, duct tape, and existential dread? Yeah, we’ve all been there. But what if I told you there's a shimmering, concurrent, and surprisingly fun path to backend nirvana? Enter Golang, or simply &quot;Go,&quot; the language that’s making developers say, &quot;Gopher it!&quot; (Pun absolutely intended).&lt;br&gt;
Now, before you scoff and retreat to your comfort zone of Node.js callbacks or Java's verbose rituals, give Go a chance. It's like that super-efficient friend who also happens to tell surprisingly good jokes. Let's dive deep, but keep it breezy, into why Go is becoming a heavyweight champion in the backend arena.&lt;/p&gt;
&lt;h3 id=&quot;the-go-philosophy-simple-fast-and-no-shenanigans&quot;&gt;The Go Philosophy: Simple, Fast, and No Shenanigans&lt;/h3&gt;
&lt;p&gt;Imagine a language designed by folks who truly understand the pain points of large-scale systems. They said, &quot;Let's ditch the inheritance hierarchies that make your head spin, let's make concurrency easy, and let's compile to a single, tiny binary that can run almost anywhere.&quot; And thus, Go was born.&lt;br&gt;
It's not object-oriented in the classical sense, which can throw off some OOP purists. But honestly, sometimes less is more. Go favors composition over inheritance, which often leads to more flexible and understandable code. Think of it as building with LEGOs instead of trying to sculpt a masterpiece from a single, giant block. You get the picture.&lt;br&gt;
Goroutines and Channels: Your Concurrency Superpowers (No Capes Required)&lt;br&gt;
This is where Go truly shines brighter than a thousand suns (or at least, a thousand poorly optimized threads). Concurrency in Go is handled by goroutines and channels. Forget callback hell, mutexes that make you want to pull your hair out, or the sheer terror of deadlocks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Goroutines&lt;/strong&gt;: These are lightweight, independently executing functions. Think of them as incredibly efficient, miniature threads managed by the Go runtime, not the operating system. You can spin up thousands, even millions, of them without breaking a sweat. It's like having an army of tiny, dedicated workers, each doing their part, and they're all super polite and don't trip over each other.&lt;br&gt;
package main&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import (
&quot;fmt&quot;
&quot;time&quot;
)

func fetchData(url string, ch chan string) {
// Simulating some network latency
time.Sleep(2 * time.Second)
ch &amp;lt;- fmt.Sprintf(&quot;Data from %s: All good in the hood!&quot;, url)
}

func main() {
urls := []string{&quot;api.example.com/users&quot;, &quot;api.example.com/products&quot;, &quot;api.example.com/orders&quot;}
results := make(chan string)

for _, url := range urls {
go fetchData(url, results) // Launch a goroutine for each URL
}

for i := 0; i &amp;lt; len(urls); i++ {
fmt.Println(&amp;lt;-results) // Receive results from the channel
}

fmt.Println(&quot;All data fetched! Time for a coffee break.&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this snippet, we're fetching data from multiple URLs concurrently. Each fetchData call runs in its own goroutine, and results are sent back via a channel. No waiting around! It’s so efficient, it almost feels like cheating.&lt;br&gt;
Channels: These are the pipes through which goroutines communicate. They allow goroutines to send and receive values safely, preventing race conditions and making concurrent programming a breeze. Imagine them as a well-organized pneumatic tube system for your data. No more yelling across the room to share information!&lt;/p&gt;
&lt;p&gt;This &lt;strong&gt;&quot;Communicating Sequential Processes&quot; (CSP) model&lt;/strong&gt;, inspired by Tony Hoare, is incredibly powerful. It simplifies the mental model of concurrency from &quot;sharing memory by communicating&quot; to &quot;communicating by sharing memory.&quot; Wait, that's not right. It's &quot;Don't communicate by sharing memory; instead, share memory by communicating.&quot; Yeah, that's the one! It radically reduces the complexity of building highly concurrent systems.&lt;br&gt;
Batteries Included: The Standard Library (It's Glorious!)&lt;/p&gt;
&lt;p&gt;One of Go's unsung heroes is its incredibly rich and well-documented standard library. Need to build a web server? net/http has you covered. Working with JSON? encoding/json is your friend. Cryptography? crypto. File I/O? os. It’s like Go hands you a fully loaded toolkit the moment you start. You rarely find yourself hunting for third-party libraries for fundamental tasks.&lt;/p&gt;
&lt;p&gt;This means less dependency hell, fewer &quot;left-pad&quot; moments (if you know, you know), and more time actually building your application instead of managing an ecosystem of packages. It’s practical, pragmatic, and designed for getting things done.&lt;br&gt;
Error Handling: Explicit, Not Exceptions&lt;br&gt;
Go's error handling might seem a bit... verbose at first glance. Instead of exceptions that jump around like a startled cat in a room full of rocking chairs, Go uses multiple return values, with the last one typically being an error interface.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func getUser(id int) (User, error) {
if id &amp;lt; 1 {
return User{}, fmt.Errorf(&quot;invalid user ID: %d&quot;, id)
}
// ... database lookup logic ...
return User{ID: id, Name: &quot;Gopher McGoFace&quot;}, nil
}

func main() {
user, err := getUser(0)
if err != nil {
fmt.Printf(&quot;Error fetching user: %v\n&quot;, err)
return
}
fmt.Printf(&quot;User found: %+v\n&quot;, user)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While some might grumble about the if err != nil boilerplate, it forces you to explicitly handle errors. This leads to more robust and predictable code. No more &quot;silent&quot; errors that only surface at 3 AM when production is burning. It’s like Go nudges you and says, &quot;Hey, buddy, you might want to check if that went okay.&quot; And honestly, that little nudge saves a lot of headaches.&lt;/p&gt;
&lt;h3 id=&quot;tooling-go-s-secret-weapon-okay-not-so-secret-&quot;&gt;Tooling: Go's Secret Weapon (Okay, Not So Secret)&lt;/h3&gt;
&lt;p&gt;Go comes with an amazing set of built-in tools that make development a joy:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;go fmt&lt;/strong&gt;: Automatically formats your code. No more debates about tabs vs. spaces! Your codebase will look consistent across all developers. It's like a universal peace treaty for code style.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;go run&lt;/strong&gt;: Compiles and runs your Go program in one go (pun again!).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;go build&lt;/strong&gt;: Compiles your code into a single, statically linked binary. Deploying a Go app often means just copying one file. Yes, one file. No elaborate runtime dependencies, no Docker images the size of a small moon (unless you want them). It's incredibly refreshing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;go test&lt;/strong&gt;: Built-in testing framework. Writing tests in Go is straightforward and encouraged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;go mod&lt;/strong&gt;: Superb module management for dependencies. It’s intuitive and works like a charm.&lt;/p&gt;
&lt;p&gt;This comprehensive tooling ecosystem means you spend less time configuring your environment and more time actually coding. It's truly a developer's dream.&lt;/p&gt;
&lt;h3 id=&quot;why-go-for-your-next-backend-project-&quot;&gt;Why Go for Your Next Backend Project?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Performance: Go is compiled to machine code, making it blazing fast. For high-throughput services, it's a serious contender.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concurrency: As we gushed about, goroutines and channels make concurrent programming approachable and powerful. Building scalable services becomes a joy, not a chore.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simplicity &amp;amp; Readability: The language itself is small and easy to learn. Its explicit nature makes Go code incredibly readable, even for complex systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deployment: Single binaries are a deployment dream. CI/CD pipelines become simpler and faster.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Community &amp;amp; Ecosystem: Go has a vibrant and growing community, with excellent libraries and frameworks for web development (like Gin, Echo), database access, and more.&lt;br&gt;
A Few &quot;Gotchas&quot; (Because Nothing's Perfect)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No Generics (until Go 1.18): This was a common complaint, leading to some boilerplate for generic data structures. However, with Go 1.18 and later, generics are finally here, making the language even more versatile! Hallelujah!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Error Handling Verbosity: As mentioned, if err != nil can get repetitive, but it’s a design choice for explicit error management. Embrace the checks, they're your friends.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Strictness: Go is opinionated. If you declare a variable and don't use it, the compiler will yell at you. This keeps your code clean but can be annoying for quick experiments. Consider it tough love from the compiler.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;Conclusion: Give Go a Whirl!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you're looking for a language that combines performance, ease of concurrency, and straightforward development, Go is absolutely worth your time. It encourages good practices, keeps things simple, and lets you build robust, scalable backends without the usual headaches.&lt;/p&gt;
&lt;p&gt;So, next time you're about to embark on a new backend adventure, don't just stand there. Gopher it! Your future self, and your server logs, will thank you.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Happy coding, and may your goroutines always be efficient!&lt;/p&gt;
&lt;/blockquote&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/4157/gopher-it-your-go-to-guide-for-glorious-go-backends</guid>
<pubDate>Sun, 20 Jul 2025 05:59:41 +0000</pubDate>
</item>
<item>
<title>Controlling Memory and CPU Utilization for Go Apps in Kubernetes: GOMEMLIMIT and GOMAXPROCS</title>
<link>https://coderlegion.com/3745/controlling-memory-and-cpu-utilization-for-go-apps-kubernetes-gomemlimit-and-gomaxprocs</link>
<description>&lt;h3 id=&quot;controlling-memory-and-cpu-utilization-for-go-apps-in-kubernetes-gomemlimit-and-gomaxprocs&quot;&gt;Controlling Memory and CPU Utilization for Go Apps in Kubernetes: &lt;code&gt;GOMEMLIMIT&lt;/code&gt; and &lt;code&gt;GOMAXPROCS&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;While running Go apps in Kubernetes, we have to control both CPU and memory to avoid instability, performance issues, or unintentional pod restarts. Go gives us two potent environment variables to achieve this: &lt;code&gt;GOMEMLIMIT&lt;/code&gt; and &lt;code&gt;GOMAXPROCS&lt;/code&gt;. This article details how to use them in Kubernetes correctly, what to look out for, and production deployment best practices.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;gomemlimit-memory-usage-control&quot;&gt;&lt;code&gt;GOMEMLIMIT&lt;/code&gt;: Memory Usage Control&lt;/h4&gt;
&lt;h5 id=&quot;what-is-it-&quot;&gt;What is it?&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;GOMEMLIMIT&lt;/code&gt; is an environment variable introduced in Go 1.19. This specifies the &lt;strong&gt;maximum memory limit&lt;/strong&gt; that the Go GC will try to remain below.&lt;/p&gt;
&lt;h5 id=&quot;setting-it-up-in-kubernetes&quot;&gt;Setting it up in Kubernetes&lt;/h5&gt;
&lt;p&gt;It can also be defined directly as an environment variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;env:
- name: GOMEMLIMIT
  value: &quot;512MiB&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you can automatically bind it to the container memory limit with the Downward API:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;env:
- name: GOMEMLIMIT
  valueFrom:
    resourceFieldRef:
      resource: limits.memory
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;You &lt;strong&gt;must&lt;/strong&gt; have &lt;code&gt;resources.limits.memory&lt;/code&gt; specified in order for this to function. Otherwise, the variable will be empty.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Full example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;apiVersion: v1
kind: Pod
metadata:
  name: go-app
spec:
  containers:
  - name: go-container
    image: your-go-image
    resources:
      limits:
        memory: &quot;512Mi&quot;
    env:
    - name: GOMEMLIMIT
      valueFrom:
        resourceFieldRef:
          resource: limits.memory
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;caveats-and-risks&quot;&gt;Caveats and Risks&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Often GC cycles&lt;/strong&gt;: If the threshold is too narrow then GC will occur frequently and lead to higher CPU activity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slower performance&lt;/strong&gt;: Application performance slows down because GC is executed frequently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OOMKill still an option&lt;/strong&gt;: In the event memory spikes quicker than GC can catch up, the container can nonetheless be terminated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unbounded non-heap memory&lt;/strong&gt;: Non-heap memory is not monitored: memory from cgo allocations, &lt;code&gt;mmap&lt;/code&gt; allocations, or big slices is unmonitored&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;recommendations&quot;&gt;Recommendations&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;GOMEMLIMIT&lt;/code&gt; to a little below your container’s memory limit (e.g., &lt;code&gt;450MiB&lt;/code&gt; if the limit is &lt;code&gt;512MiB&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Test under load to monitor GC behavior.&lt;/li&gt;
&lt;li&gt;Make use of Go metrics (&lt;code&gt;runtime/metrics&lt;/code&gt;) and &lt;code&gt;pprof&lt;/code&gt; to examine memory use.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;gomaxprocs-cpu-concurrency-control&quot;&gt;&lt;code&gt;GOMAXPROCS&lt;/code&gt;: CPU Concurrency Control&lt;/h4&gt;
&lt;h5 id=&quot;what-is-it--1&quot;&gt;What is it?&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;GOMAXPROCS&lt;/code&gt; determines the &lt;strong&gt;max number of OS threads to execute Go code concurrently&lt;/strong&gt;. Go defaults to the number of visible CPU cores — but this can be misleading in containers.&lt;/p&gt;
&lt;h5 id=&quot;issues-in-kubernetes&quot;&gt;Issues in Kubernetes&lt;/h5&gt;
&lt;p&gt;Even if your container enforces a CPU limit (e.g., &lt;code&gt;1 CPU&lt;/code&gt;), Go can uncover more cores (e.g., the entire host’s number of CPUs) and cause &lt;code&gt;GOMAXPROCS&lt;/code&gt; to become too large, leading to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Overcommitted CPU usage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unfair scheduling&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Throttling by kubelet or kernel&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;putting-it-in-place&quot;&gt;Putting it in place&lt;/h5&gt;
&lt;p&gt;Do not hardcode &lt;code&gt;GOMAXPROCS&lt;/code&gt; from container limits directly,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;resources:
  limits:
    cpu: &quot;0.5&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will not directly convert to an integer and Go will need &lt;code&gt;GOMAXPROCS&lt;/code&gt; to be an integer.&lt;/p&gt;
&lt;p&gt;Instead, use &lt;a rel=&quot;nofollow&quot; href=&quot;https://pkg.go.dev/go.uber.org/automaxprocs&quot;&gt;Uber’s &lt;code&gt;automaxprocs&lt;/code&gt;&lt;/a&gt;, which automatically reads the cgroup CPU limit and sets &lt;code&gt;GOMAXPROCS&lt;/code&gt; accordingly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;import _ &quot;go.uber.org/automaxprocs&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;risks&quot;&gt;Risks&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU throttling&lt;/strong&gt; if Go tries to use more CPUs than have been assigned.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-deterministic performance&lt;/strong&gt; when multiple Go processes are vying for limited CPU.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;recommendations-1&quot;&gt;Recommendations&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Always include &lt;code&gt;resources.limits.cpu&lt;/code&gt; in your pod spec.&lt;/li&gt;
&lt;li&gt;Auto-adjust safely with &lt;code&gt;automaxprocs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Do not hand-tune except when absolutely necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;can-a-container-be-restarted-without-resource-limits-&quot;&gt;Can a Container Be Restarted Without Resource Limits?&lt;/h4&gt;
&lt;p&gt;Yes — a container &lt;strong&gt;can recover even without memory or CPU limits&lt;/strong&gt;, as Kubernetes resumes containers due to several reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Process crashes (non-zero exit code)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OOMKilled by the kernel&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failed liveness/readiness probes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node or kubelet failures&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Even if you &lt;strong&gt;do not set limits&lt;/strong&gt;, a container still can potentially be OOM Killed by the kernel if the node is low on memory. Kubernetes will catch this and will restart the container (if &lt;code&gt;restartPolicy: Always&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Use this command to debug:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl describe pod &amp;lt;pod-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Last State: OOMKilled&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Exit Code&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Reason&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h4&gt;
&lt;p&gt;By using &lt;code&gt;GOMEMLIMIT&lt;/code&gt; and &lt;code&gt;GOMAXPROCS&lt;/code&gt; in Kubernetes configuration, you can readily improve the stability and performance associated with your Go programs. Misusing them or not using them at all can lead to performance hotspots, program crashes, or uncontrolled use of system resources. Couple these settings with proper resource limits and observability tools to support seamless operations in production environments.&lt;/p&gt;
</description>
<category>Containerization</category>
<guid isPermaLink="true">https://coderlegion.com/3745/controlling-memory-and-cpu-utilization-for-go-apps-kubernetes-gomemlimit-and-gomaxprocs</guid>
<pubDate>Mon, 07 Jul 2025 00:19:13 +0000</pubDate>
</item>
<item>
<title>✅ Dynamic Captcha Loader – Online Tool</title>
<link>https://coderlegion.com/2852/dynamic-captcha-loader-online-tool</link>
<description>&lt;p&gt;This tool allows you to perform integration tests for captcha challenge verification on the server side.&lt;/p&gt;
&lt;p&gt;It currently supports Google, Cloudflare, and hCaptcha.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tool: &lt;a rel=&quot;nofollow&quot; href=&quot;https://captcha.javad.dev/&quot;&gt;https://captcha.javad.dev/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ja7ad/captcha&quot;&gt;https://github.com/ja7ad/captcha&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Dev Tools</category>
<guid isPermaLink="true">https://coderlegion.com/2852/dynamic-captcha-loader-online-tool</guid>
<pubDate>Thu, 15 May 2025 09:51:50 +0000</pubDate>
</item>
<item>
<title>Implement Your Own Request Middleware for Go HTTP Server</title>
<link>https://coderlegion.com/2854/implement-your-own-request-middleware-for-go-http-server</link>
<description>&lt;p&gt;I believed this topic is a lot in the internet but I want to write it anyway, because documenting your own learning journey and share it to the world is good right?&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Request middleware performs some specific function on the HTTP request or response at a specific stage in the HTTP pipeline before the business logic / app handler.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Currently &lt;code&gt;middleware&lt;/code&gt; is a common term but in different programming language or framework sometimes it called &lt;code&gt;filter&lt;/code&gt;. For illustration could be like this&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=6746352252606455485&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, middleware could be layered for more than one before hitting app handler or business logic then later it will return the response. HTTP Request comes in to first layer or middleware, check the necessary and if all conditions are passed then it allowed to continue the journey. If failed, it could also returned the failed response anyway.&lt;/p&gt;
&lt;h3 id=&quot;basic-middleware&quot;&gt;Basic Middleware&lt;/h3&gt;
&lt;p&gt;Let say we have simple HTTP API server&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func rootHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := defaultResponse{
Status:      &quot;OK&quot;,
Description: &quot;Success&quot;,
}

j, _ := json.Marshal(resp)
w.Header().Add(&quot;Content-Type&quot;, &quot;application/json&quot;)
w.Write(j)
})
}

func main() {
mux := http.NewServeMux()

mux.Handle(&quot;GET /&quot;, rootHandler())

s := &amp;amp;http.Server{
Addr:    &quot;:8080&quot;,
Handler: mux,
}

// ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;if we run the program &lt;code&gt;go run main.go&lt;/code&gt; and visit &lt;code&gt;http://localhost:8080&lt;/code&gt; it will return 200 success with JSON response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;status&quot;: &quot;OK&quot;,
  &quot;description&quot;: &quot;Success&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now lets create our first middleware. Our middleware is to write a simple log that indicating the request is passing through. The middleware should be a function like this&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func logMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Print(&quot;Executing logMiddleware&quot;)
next.ServeHTTP(w, r)
})
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we will modify the &lt;code&gt;mux&lt;/code&gt; handler to use the middleware&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func main() {
mux := http.NewServeMux()
    mux.Handle(&quot;/&quot;, logMiddleware(rootHandler()))
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result is when we’re visiting the API, it also wrote the log that already specified in the middleware logic.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11947892336783986420&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;adding-more-middleware&quot;&gt;Adding More Middleware&lt;/h3&gt;
&lt;p&gt;Interesting. Now try to add more middleware. This time we will check if the API request header contains &lt;code&gt;x-signature&lt;/code&gt;, if not exists then we will return error.&lt;/p&gt;
&lt;p&gt;Lets create the middleware function&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func requestSignatureMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Print(&quot;Executing requestSignatureMiddleware&quot;)
h := r.Header.Get(&quot;x-signature&quot;)
if h == &quot;&quot; {
resp := errorResponse{
Status:  http.StatusUnauthorized,
Error:   &quot;INVALID_MISSING_SIGNATURE&quot;,
Message: &quot;missing request signature&quot;,
}

j, _ := json.Marshal(resp)
w.Header().Add(&quot;Content-Type&quot;, &quot;application/json&quot;)
w.WriteHeader(http.StatusUnauthorized)
w.Write(j)
return
}
next.ServeHTTP(w, r)
})
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want to call &lt;code&gt;logMiddleware&lt;/code&gt; first then continue to &lt;code&gt;requestSignatureMiddleware&lt;/code&gt;. Our &lt;code&gt;mux&lt;/code&gt; handler would be look like this now&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func main() {
mux := http.NewServeMux()
    mux.Handle(&quot;/&quot;, logMiddleware(requestSignatureMiddleware(rootHandler())))
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s the result of the combined middlewares&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10959795884677726041&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First request without &lt;code&gt;x-signature&lt;/code&gt; header will returned &lt;code&gt;401&lt;/code&gt; HTTP status&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second request with additional &lt;code&gt;x-signature&lt;/code&gt; header passed middleware logic and returned &lt;code&gt;200&lt;/code&gt; HTTP status.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cleaning-up&quot;&gt;Cleaning Up&lt;/h3&gt;
&lt;p&gt;You may notice that our &lt;code&gt;mux&lt;/code&gt; handler with multiple middlewares is little bit messy because it is calling function into function. What if we have many middlewares?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;logMiddleware(requestSignatureMiddleware(authMiddleware(rateLimitMiddleware(rootHandler)))))&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Hard to see.&lt;/p&gt;
&lt;p&gt;Here’s what we can do, create a &lt;code&gt;chain&lt;/code&gt; function to loop all middleware functions. You can named the function whatever you want.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// Middleware type for cleaner middleware chaining
type Middleware func(http.Handler) http.Handler

// Chain creates a single middleware from multiple middlewares
func Chain(middlewares ...Middleware) Middleware {
return func(next http.Handler) http.Handler {
for i := len(middlewares) - 1; i &amp;gt;= 0; i-- {
next = middlewares[i](next)
}
return next
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Initiate the &lt;code&gt;Chain&lt;/code&gt; with our middlewares and finally call it in our &lt;code&gt;mux&lt;/code&gt; handler.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func main() {
mux := http.NewServeMux()

// Create a chain of middlewares
middlewareChain := Chain(
func(next http.Handler) http.Handler { return logMiddleware(next) },
func(next http.Handler) http.Handler { return requestSignatureMiddleware(next) },
// Add more middlewares here as needed
)

mux.Handle(&quot;GET /&quot;, middlewareChain(rootHandler()))
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now it’s more manageable and it will still got same result as before.&lt;/p&gt;
&lt;p&gt;So here’s the full code of this experiment &lt;a rel=&quot;nofollow&quot; href=&quot;https://gist.github.com/didikz/98de868f20887285f5aabc80f4dddcfa&quot;&gt;https://gist.github.com/didikz/98de868f20887285f5aabc80f4dddcfa&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;
</description>
<category>Backend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/2854/implement-your-own-request-middleware-for-go-http-server</guid>
<pubDate>Sun, 04 May 2025 23:33:56 +0000</pubDate>
</item>
</channel>
</rss>