<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged swarm</title>
<link>https://coderlegion.com/tag/swarm</link>
<description>Powered by Question2Answer</description>
<item>
<title>How to Run SafeLine WAF in Docker Swarm: A Deployment Checklist</title>
<link>https://coderlegion.com/31918/how-to-run-safeline-waf-in-docker-swarm-a-deployment-checklist</link>
<description>&lt;p&gt;Running SafeLine WAF in Docker Swarm requires careful planning because the supplied SafeLine deployment guide documents Docker Compose, not a native Docker Swarm installation. The safest approach is to treat Swarm as an orchestration decision that needs validation against the official deployment model, rather than assuming that a Compose file can be converted without changes.&lt;/p&gt;
&lt;p&gt;SafeLine is a self hosted web application firewall that works as a reverse proxy between clients and an application. It filters, monitors, and blocks malicious HTTP or HTTPS traffic before forwarding acceptable traffic to the original application. The official introduction describes protection against threats including SQL injection, XSS, code injection, path traversal, SSRF, XXE, brute force attempts, and HTTP floods. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Begin with an environment inventory for every possible Swarm node. The official deployment requirements list Linux, x86_64 or arm64 architecture, Docker 20.10.14 or newer, Docker Compose 2.0.0 or newer, at least one CPU core, 1 GB of memory, 5 GB of disk space, and SSSE3 CPU support. These requirements are written for the documented SafeLine setup, so verify how they map to your Swarm nodes before scheduling workloads. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next, decide where persistent SafeLine configuration and data will live. The guide creates /data/safeline and states that SafeLine writes its configuration and data there. In a Swarm design, this raises practical questions about node placement, storage availability, recovery, and whether a task can restart on a node that contains the required data. Resolve those questions before exposing production traffic.&lt;/p&gt;
&lt;p&gt;The documented installation also uses environment values for the SafeLine directory, image tag, management port, PostgreSQL password, internal subnet prefix, image prefix, architecture suffix, release channel, region, and management proxy layers. Review each value and preserve secrets securely. The official guide states that ARM deployments require ARCH_SUFFIX=-arm and that ARM installations require a Pro license, while the Personal Edition is not supported on ARM. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After the platform design is validated, confirm how the documented Compose lifecycle maps to your Swarm operating procedures. The official guide uses docker compose up -d, opens the management console at &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;, and retrieves the initial administrator account with docker exec safeline-mgt resetadmin. Do not assume these commands have identical behavior after a Swarm conversion. Test management access, data persistence, restart behavior, logging, and secret handling in a nonproduction environment first.&lt;/p&gt;
&lt;p&gt;Once SafeLine is available, configure protection through its Web Admin Console. The application guide instructs administrators to add an application with its domain, listening port, SSL setting when applicable, and upstream address. DNS should then resolve the application domain to the server where SafeLine is located. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As of September 30, 2026, SafeLine surpassed 700,000 installations worldwide. SafeLine Pro annual promotional pricing is $650-$880 versus the $1,000 regular price.&lt;/p&gt;
&lt;h4 id=&quot;does-the-official-guide-provide-a-docker-swarm-command-&quot;&gt;Does the official guide provide a Docker Swarm command?&lt;/h4&gt;
&lt;p&gt;No. The supplied deployment guide documents Docker and Docker Compose. It does not provide a Docker Swarm stack file, Swarm-specific service commands, overlay network instructions, or a supported Swarm conversion procedure. Treat Swarm deployment as an adaptation that requires independent validation.&lt;/p&gt;
&lt;h4 id=&quot;what-should-be-checked-before-scheduling-safeline-on-swarm-nodes-&quot;&gt;What should be checked before scheduling SafeLine on Swarm nodes?&lt;/h4&gt;
&lt;p&gt;Check Linux compatibility, CPU architecture, Docker version, available memory, disk capacity, SSSE3 support, persistent storage, management port access, and secure handling of the PostgreSQL initialization password. These checks come from the documented SafeLine requirements and configuration model.&lt;/p&gt;
&lt;h4 id=&quot;how-does-safeline-receive-application-traffic-&quot;&gt;How does SafeLine receive application traffic?&lt;/h4&gt;
&lt;p&gt;SafeLine acts as an HTTP or HTTPS reverse proxy. In the Web Admin Console, define the application domain, listening port, SSL option when needed, and upstream address. Then point the domain to the SafeLine server so traffic reaches the proxy before the original application. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;how-can-a-swarm-deployment-be-accepted-for-production-&quot;&gt;How can a Swarm deployment be accepted for production?&lt;/h4&gt;
&lt;p&gt;Create a repeatable test that verifies installation, administrator access, application routing, TLS behavior, persistence after task movement, recovery from node failure, and review of attack events. Use the official SafeLine documentation as the reference for product behavior, and document every Swarm-specific change separately.&lt;/p&gt;
&lt;p&gt;Use the official documentation to validate your architecture and invite your operations team to review the storage, networking, and recovery plan before production deployment. Project: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt; Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; Promotion: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31918/how-to-run-safeline-waf-in-docker-swarm-a-deployment-checklist</guid>
<pubDate>Wed, 21 Oct 2026 04:27:52 +0000</pubDate>
</item>
<item>
<title>How to Run SafeLine WAF in Docker Swarm Without Skipping the Deployment Checklist</title>
<link>https://coderlegion.com/31917/how-to-run-safeline-waf-in-docker-swarm-without-skipping-the-deployment-checklist</link>
<description>&lt;p&gt;Running a web application firewall in Docker Swarm starts with a deployment decision: determine which parts of the documented SafeLine installation can be carried into your orchestration model, then verify every assumption before exposing an application. The supplied SafeLine installation guide documents Linux, Docker, Docker Compose, and a Compose based launch with &lt;code&gt;docker compose up -d&lt;/code&gt;. It does not document a Docker Swarm stack file or claim Swarm support. Therefore, treat Swarm deployment as an evaluation exercise rather than assuming that the documented Compose configuration is a supported Swarm integration.&lt;/p&gt;
&lt;p&gt;SafeLine is a self hosted web application firewall that operates as a reverse proxy. It receives traffic for an original application, filters malicious requests, and forwards safe traffic to the upstream application. The official application guide describes the required application settings as domain, listening port, optional SSL, and upstream address. After configuration, the application domain should resolve to the server running SafeLine. Sources: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Begin with a compatibility inventory. The official deployment guide lists Linux, x86_64 or arm64 architecture, Docker 20.10.14 or later, Docker Compose 2.0.0 or later, and a minimum environment of 1 CPU core, 1 GB memory, and 5 GB disk. It also calls for SSSE3 CPU support. Check the host architecture, Docker version, Compose version, processor information, memory, and disk before planning a migration. These checks establish whether the documented SafeLine prerequisites are present, but they do not establish that a multi node Swarm deployment is supported. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pay close attention to persistent state. The guide creates &lt;code&gt;/data/safeline&lt;/code&gt;, downloads &lt;code&gt;compose.yaml&lt;/code&gt;, and places configuration and data there. In a Swarm design, you must decide how that directory is made available to the task that runs SafeLine and how it is backed up. The supplied documents do not specify shared storage, node constraints, failover behavior, rolling updates, or Swarm aware backup procedures. Record each of those as an operational requirement that needs validation in your environment.&lt;/p&gt;
&lt;p&gt;The documented environment variables also deserve a review before any translation. They include the SafeLine directory, image tag, management port, PostgreSQL password, internal subnet prefix, image prefix, architecture suffix, release selection, and management proxy layers. The guide says to use &lt;code&gt;ARCH_SUFFIX=-arm&lt;/code&gt; on ARM and explains that ARM installation requires a Pro license, while the Personal Edition is not supported on ARM. It also documents an LTS setting and notes that the LTS line will no longer be maintained starting from 9.1.0-LTS. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A sensible proof of concept should keep one controlled node, a test hostname, a noncritical upstream, and a rollback path. Confirm that the management console is reachable at the configured management port, retrieve the initial administrator account using the documented &lt;code&gt;docker exec safeline-mgt resetadmin&lt;/code&gt; command, and log in through the Web UI. Then add the test application in the Applications page and verify that DNS points to the SafeLine host. Do not label the result production ready until traffic routing, persistent data, certificate handling, node placement, restart behavior, and recovery have all been tested.&lt;/p&gt;
&lt;p&gt;SafeLine documentation describes protections including web attack blocking, rate limiting, CAPTCHA challenges, authentication challenges, and dynamic HTML and JavaScript protection. Validate only the protections you enable and review the attack list in the console after controlled testing. The application guide provides examples for SQL injection, XSS, path traversal, code injection, and XXE testing. Run such tests only against an application you own or are authorized to assess. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As of September 30, 2026, SafeLine surpassed 700,000 installations worldwide. SafeLine Pro annual promotional pricing is $650-$880 versus the $1,000 regular price. Treat those figures as the stated promotional information for this article and verify current availability through the supplied promotion link before making a purchase decision.&lt;/p&gt;
&lt;h4 id=&quot;is-docker-swarm-officially-documented-for-safeline-&quot;&gt;Is Docker Swarm officially documented for SafeLine?&lt;/h4&gt;
&lt;p&gt;The supplied deployment material documents Docker Compose commands and a Compose file. It does not provide a Swarm deployment procedure or confirm Swarm compatibility. Use the documented Compose path as the reference installation and validate any Swarm adaptation separately.&lt;/p&gt;
&lt;h4 id=&quot;what-should-i-verify-before-moving-safeline-into-swarm-&quot;&gt;What should I verify before moving SafeLine into Swarm?&lt;/h4&gt;
&lt;p&gt;Verify the documented host prerequisites, image architecture, persistent access to the SafeLine directory, management port reachability, PostgreSQL password handling, DNS routing, upstream connectivity, and recovery procedures. Also document which Swarm behavior is an assumption because the supplied SafeLine guides do not describe it.&lt;/p&gt;
&lt;h4 id=&quot;how-does-safeline-reach-the-protected-application-&quot;&gt;How does SafeLine reach the protected application?&lt;/h4&gt;
&lt;p&gt;SafeLine acts as an HTTP or HTTPS reverse proxy. In the Web Admin Console, add the application, provide its domain, listening port, SSL setting when applicable, and upstream address. Resolve the domain to the SafeLine server so client traffic reaches the proxy first.&lt;/p&gt;
&lt;h4 id=&quot;what-is-a-practical-first-test-&quot;&gt;What is a practical first test?&lt;/h4&gt;
&lt;p&gt;Use a disposable or low risk application, route a test hostname through SafeLine, confirm normal requests reach the upstream, and then review the console after authorized security tests. Check both blocked requests and legitimate application behavior before expanding the deployment.&lt;/p&gt;
&lt;p&gt;If you are evaluating Docker Swarm, use this checklist with the official deployment and application guides, document every unsupported assumption, and invite your operations team to review the proof of concept. Project: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt; Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; Promotion: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31917/how-to-run-safeline-waf-in-docker-swarm-without-skipping-the-deployment-checklist</guid>
<pubDate>Wed, 21 Oct 2026 04:27:21 +0000</pubDate>
</item>
<item>
<title>I Built a 35-Agent AI Coding Swarm That Runs Overnight</title>
<link>https://coderlegion.com/13713/i-built-a-35-agent-ai-coding-swarm-that-runs-overnight</link>
<description>&lt;p&gt;*Follow-up to *&lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;/blog/the-week-i-stopped-coding&quot;&gt;The Week I Stopped Coding&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;A month ago, I wrote about the moment I stopped coding and started orchestrating AI agents. That post was about the shift — the emotional and philosophical pivot from developer to director.&lt;br&gt;
This post is about what happened after.&lt;br&gt;
I built a system. Two physical machines on my home network, 14 containers, 35 concurrent AI coding sessions, and a 5-layer memory architecture that teaches agents not to repeat each other's mistakes. It scans my project management board every two minutes, picks up tickets, creates isolated git worktrees, spawns Claude Code sessions, writes code, creates pull requests, and updates ticket statuses — all without me touching a keyboard.&lt;br&gt;
It processes 20-40 tickets overnight. I wake up to PRs.&lt;/p&gt;
&lt;h3 id=&quot;i-ve-seen-this-before&quot;&gt;I've Seen This Before&lt;/h3&gt;
&lt;p&gt;In 2015, I was drawing CI/CD pipeline diagrams on whiteboards at Kohl's. Build → Test → Publish → QA → Deploy → Stabilization → Release. The full continuous delivery loop, managed by hundreds of people across multiple teams — developers, QA engineers, DevOps, project managers, release coordinators.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.sanity.io/images/dafshiq1/production/760016ade1a8c529cf93901176d7ff4b24bbd38c-1138x792.png&quot; alt=&quot;CI/CD lifecycle diagram from Kohl's MobileFirst project, 2015&quot;&gt;&lt;/p&gt;
&lt;p&gt;I look at that diagram now and I see my swarm's ticket lifecycle. The stages are identical. Linear scan is the ticket intake. The Claude Code session is the development cycle. Sub-agents run the build, test, and review gates. &lt;code&gt;gh pr create&lt;/code&gt; is the publish step. Vercel auto-deploys on merge. Prometheus watches the stabilization metrics. The only difference is who's executing each stage.&lt;br&gt;
In 2015, that loop required a branching strategy document that went through 11 revisions, a deployment runbook with IBM coordination steps, Chef cookbooks for node management, and a standing army of engineers. In 2026, it's one Node.js script and a vector database.&lt;br&gt;
I'm not saying the people don't matter — they built the institutional knowledge that's now encoded in CLAUDE.md files and Qdrant collections. But the execution layer has fundamentally changed. The same pipeline I designed for human teams a decade ago now runs autonomously on two machines in my house.&lt;/p&gt;
&lt;h3 id=&quot;the-hardware-two-machines-one-network&quot;&gt;The Hardware: Two Machines, One Network&lt;/h3&gt;
&lt;p&gt;No cloud compute for the swarm itself. Two machines on my home network — a Linux desktop and a Mac Studio I picked up for mobile builds.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.sanity.io/images/dafshiq1/production/2b3ec34875e37cab85d803ff829395ae52931ac8-900x420.svg&quot; alt=&quot;Physical topology — Dragon desktop and Hive Mac Studio connected over LAN&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dragon&lt;/strong&gt; (desktop) — Arch Linux, AMD ROCm GPU, 14 Podman containers. This is the primary orchestration node. It runs the director, the dashboard, Qdrant for vector search, Ollama for embeddings, and the full Prometheus/Grafana monitoring stack. 15 concurrent session slots.&lt;br&gt;
&lt;strong&gt;Hive&lt;/strong&gt; (Mac Studio) — The only machine with Xcode, iOS simulators, Android emulators, Fastlane, and Maestro for mobile QA. 20 concurrent session slots. Three macOS LaunchAgents handle its director, worker, and git-watcher processes.&lt;br&gt;
They talk over SSH and a shared Qdrant instance. The desktop pushes build tasks to the hive's Qdrant — I use the vector database as a task queue because macOS LaunchAgents get &lt;code&gt;EHOSTUNREACH&lt;/code&gt; when connecting back to the desktop IP. Flipping the direction solved it.&lt;br&gt;
Total capacity: 35 concurrent AI coding sessions across 6 repositories.&lt;/p&gt;
&lt;h3 id=&quot;the-three-layers&quot;&gt;The Three Layers&lt;/h3&gt;
&lt;p&gt;The architecture has three distinct layers, each operating independently.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.sanity.io/images/dafshiq1/production/887d3807a112b3459cae9377fc252b1c63bf317b-900x620.svg&quot; alt=&quot;Three-layer architecture diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 2: Ops Runner.&lt;/strong&gt; Infrastructure health. Zombie process detection every 15 minutes. TTL sweeps that mark tasks stuck over 30 minutes as failed. Metrics export to Prometheus — 43 metrics every 5 minutes. A React dashboard with 6 tabs that gives me visibility into what the swarm is doing. Without this layer, you don't know if the swarm is working or burning money.&lt;br&gt;
&lt;strong&gt;Layer 3: Directors.&lt;/strong&gt; This is the AI layer. Two directors — Dragon on the desktop handling backend and infrastructure (event-api, game-library, venues, monitoring, ops), Hive on the Mac Studio handling frontend and mobile (shindig, website). Each scans Linear every 2 minutes, routes tickets to the correct repo, creates git worktrees for isolation, and spawns Claude Code sessions.&lt;br&gt;
The director is about 2,400 lines of Node.js. It's the single most important piece of the system — and in major need of refactoring. Now that I'm wrapping back around to making the ops swarm self-sufficient, it needs to follow its own linting and testing protocols. The irony of an autonomous coding system that doesn't enforce code quality on itself is not lost on me.&lt;/p&gt;
&lt;h3 id=&quot;how-a-ticket-becomes-a-pr&quot;&gt;How a Ticket Becomes a PR&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.sanity.io/images/dafshiq1/production/b0e3b3a5b1817228ecd408b9fd90d92a1ab962a4-900x1400.svg&quot; alt=&quot;Ticket lifecycle flowchart — 8 steps from Linear scan to PR creation with success, failure, and rate limit paths&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Linear scan&lt;/strong&gt; (every 120 seconds). The director queries for tickets in Agent Queue, QA Queue, QA Testing, or In Review. It checks attempt history — each ticket gets 3 shots before it's skipped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ticket routing.&lt;/strong&gt; Title prefix &lt;code&gt;[website]&lt;/code&gt; goes to the website repo. No prefix? Keyword matching. The &lt;code&gt;detectRepo()&lt;/code&gt; function is blunt but effective:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function detectRepo(issue) {
  const text = `${issue.title} ${issue.description || ''}`.toLowerCase();
  // Check explicit repo names first
  for (const repo of ['shindig', 'venues', 'event-api', 'game-library', 'website', 'monitoring']) {
    if (text.includes(repo)) return repo;
  }
  // Mobile/app keywords → shindig
  const shindigKeywords = ['maestro', 'e2e test', 'testflight', 'app store', 'play store',
    'fastlane', 'ios', 'android', 'mobile', 'kotlin', 'swift', 'xcode', 'gradle',
    'composable', 'jetpack', 'kmp', 'multiplatform', 'simulator', 'emulator',
    'firebase auth', 'deep link', 'push notif', 'in-app purchase', 'storekit',
    'billing', 'aab', 'ipa', 'bundle id', 'provisioning', 'signing key',
    'app group', 'widget', 'crashlytics'];
  for (const kw of shindigKeywords) {
    if (text.includes(kw)) return 'shindig';
  }
  // Ops/infra keywords → ops
  const opsKeywords = ['swarm', 'ops', 'ci/cd', 'pipeline', 'deploy', 'config',
    'infrastructure', 'docker', 'podman', 'container', 'github action',
    'orchestrat', 'director', 'dashboard', 'grafana', 'prometheus',
    'script', 'automation', 'devops', 'agent', 'memory', 'qdrant',
    'terraform', 'gcp', 'cloud run', 'fly.io', 'vercel'];
  for (const kw of opsKeywords) {
    if (text.includes(kw)) return 'ops';
  }
  return 'unknown'; // skip rather than routing to wrong repo
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Fair warning: this is quick, dirty, get-it-running code. Not pretty. It works, it's being refactored, and I'm showing it because the pattern matters more than the polish.&lt;/em&gt;&lt;br&gt;
This was the first pass. It's not sophisticated. It doesn't need to be. The title prefix catches 80% of tickets. The keyword fallback catches most of the rest. Unknown tickets get skipped — better to miss a ticket than route it to the wrong repo.&lt;br&gt;
Since then, the routing has gone through a full arc. I added Linear labels, repo-specific tags, and more complex matching rules. It got more sophisticated — and harder to maintain. Now I'm simplifying back down: labels and tooling handle the routing upstream, so the director doesn't need 50 keywords to figure out where a ticket belongs. The lesson is the same one every system learns — the first simple version works, the complex version works harder, and the final version is simple again on purpose.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Worktree creation.&lt;/strong&gt; &lt;code&gt;git worktree add&lt;/code&gt; creates an isolated directory with its own branch: &lt;code&gt;agent/fir-{ticketId}&lt;/code&gt;. Multiple tickets for the same repo can run in parallel without stepping on each other. This is essential — without worktree isolation, concurrent agents create merge conflicts on every commit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session spawn.&lt;/strong&gt; The director launches Claude Code with the ticket description, all comments, the repo's CLAUDE.md rules, and RAG context from prior agent learnings — all injected into the system prompt. Model starts at Sonnet. If it fails twice, it auto-escalates to Opus.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution.&lt;/strong&gt; The Claude Code session reads code, plans an approach, and spawns its own sub-agents — a researcher (Haiku), a coder (Sonnet), a tester (Haiku), and a reviewer (Haiku). Each sub-agent gets a fresh context window. No context pollution from the parent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PR creation.&lt;/strong&gt; The session commits, pushes, creates a PR targeting the repo's base branch, and updates the Linear ticket status.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cleanup.&lt;/strong&gt; Worktree removed. Pool slot freed. Next scan picks up more work.&lt;br&gt;
The director also batches up to 4 tickets per repo into a single session, so the agent has broader context and can address related issues together. A typical ticket takes 5-60 minutes depending on complexity. The director fills available pool slots continuously. By morning, I have a stack of PRs to review.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;inside-the-director-processticket-&quot;&gt;Inside the Director: processTicket()&lt;/h3&gt;
&lt;p&gt;The heart of the system is &lt;code&gt;processTicket()&lt;/code&gt;. This is the function that takes a ticket from the queue and turns it into a coding session. Here's what happens under the hood — model escalation, worktree creation, rate limit detection, and the failure classification that took me weeks to get right:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;async function processTicket(ticket, config, state) {
  let worktree;
  try {
    // Model escalation: after N failed attempts, upgrade to stronger model
    const prev = state?.ticketsWorked?.[ticket.identifier];
    const failedAttempts = prev ? prev.attempts : 0;
    const threshold = config.escalationThreshold ?? ESCALATION_THRESHOLD;
    const escalated = failedAttempts &amp;gt;= threshold &amp;amp;&amp;amp; config.model !== config.escalationModel;
    const ticketConfig = escalated
      ? { ...config, model: config.escalationModel }
      : config;

    if (escalated) {
      log(`MODEL ESCALATION: ${ticket.identifier} failed ${failedAttempts}x ` +
          `on ${config.model} → upgrading to ${config.escalationModel}`);
    }

    acquireLock(ticket.identifier, ticket.repo);
    worktree = createWorktree(ticket.repo, ticket.identifier);

    const result = await runClaudeSession(ticket, worktree.worktreePath, worktree, ticketConfig);

    if (!result.success) {
      const tail = result.output.slice(-300);

      // Detect rate limit OR auth failure errors
      const isRateLimit = RATE_LIMIT_PATTERNS.some(p =&amp;gt; p.test(tail));
      const isAuthFailure = AUTH_FAILURE_PATTERNS.some(p =&amp;gt; p.test(tail));
      const isFastFail = result.duration_ms &amp;lt; FAST_FAIL_THRESHOLD_MS &amp;amp;&amp;amp; !result.timedOut;

      // Fast failures (&amp;lt; 10s) are never real work — don't burn attempts
      if (isFastFail) {
        result.rateLimited = true;
        log(`FAST FAIL: ${ticket.identifier} died in ` +
            `${Math.round(result.duration_ms / 1000)}s — not counting as attempt`);
      }

      if (isRateLimit || isAuthFailure) {
        if (!rateLimitState.detected) {
          rateLimitState.detected = true;
          // Parse reset time, default to 4am Central
          const resetMatch = result.output.match(
            /resets?\s+(\d+)\s*(am|pm)\s*\(([^)]+)\)/i
          );
          // ... calculate sleep duration, auto-pause director
          notifySlack(`:warning: Director hit rate limit. Auto-pausing until reset.`);
        }
      }
    }

    return { ticketId: ticket.identifier, success: result.success, ... };

  } finally {
    releaseLock(ticket.identifier);
    cleanupWorktree(worktree);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Same caveat as above — this is working code, not clean code. The director script is 2,400 lines of Node.js that grew organically as failures taught me what it needed to handle. Refactoring it is on the roadmap.&lt;/em&gt;&lt;br&gt;
The escalation logic is simple: attempts 1-2 run on Sonnet. Attempt 3 auto-upgrades to Opus. The rate limit detection is what took the most iteration — the system had to learn the difference between &quot;this ticket is hard&quot; and &quot;the infrastructure is down.&quot; That distinction saved me from permanently skipping dozens of viable tickets.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-solve-ai-agent-memory-loss-&quot;&gt;How Do You Solve AI Agent Memory Loss?&lt;/h3&gt;
&lt;p&gt;AI agents are amnesiac by default. When I started, a Claude Code session got 200K tokens of context. Now Opus 4.6 has a 1M token window. Bigger helps — but the fundamental problem is the same. The moment the session ends, everything it learned disappears. The next agent assigned a similar ticket starts from zero and makes the same mistakes.&lt;br&gt;
This is the fundamental problem of autonomous AI coding at scale. I solved it with 5 layers of memory, each serving a different persistence scope.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.sanity.io/images/dafshiq1/production/8cd988d832f6aa271fede5627a9682e4eabb2f27-900x520.svg&quot; alt=&quot;5-layer memory hierarchy&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 2: CLAUDE.md files.&lt;/strong&gt; Checked into git at each repo root. Every agent session in a repo automatically loads these rules — branch patterns, build commands, quality standards, things the agent must never do. This is institutional knowledge encoded as configuration.&lt;br&gt;
&lt;strong&gt;Layer 3: File memory.&lt;/strong&gt; Claude Code's auto-memory system at &lt;code&gt;~/.claude/projects/&lt;/code&gt;. Over 20 topic files covering build pipelines, secret management, deployment procedures, lessons learned. Survives across sessions on the local machine.&lt;br&gt;
&lt;strong&gt;Layer 4: Vector memory.&lt;/strong&gt; Qdrant stores nearly 16,000 knowledge points and all agent outcomes. Before starting work, agents query for relevant prior learnings using semantic search. After completing work, they record what happened — success or failure, with context. This is how the swarm learns. A failed Gradle build gets recorded. The next agent searching &quot;shindig android build&quot; finds it and doesn't repeat the same mistake.&lt;br&gt;
&lt;strong&gt;Layer 5: Linear tickets.&lt;/strong&gt; The human-agent interface. I embed decisions directly in ticket descriptions and comments. Agents read both before acting. This is the only layer where I actively participate in the memory system.&lt;br&gt;
The key insight: agents query Qdrant &lt;em&gt;before&lt;/em&gt; starting work and record outcomes &lt;em&gt;after&lt;/em&gt; finishing. A memory-agent container consolidates learnings every 6 hours. The swarm genuinely learns from its own history.&lt;/p&gt;
&lt;h3 id=&quot;the-failures&quot;&gt;The Failures&lt;/h3&gt;
&lt;p&gt;This is the part that matters. Anyone can describe an architecture. The real story is in how it broke.&lt;/p&gt;
&lt;h4 id=&quot;124-pull-requests-overnight&quot;&gt;124 Pull Requests Overnight&lt;/h4&gt;
&lt;p&gt;I woke up to 124 open PRs on one repo. About 90 were duplicates — the same tickets processed over and over because the director never checked whether a PR already existed. It would scan Linear, find tickets in Agent Queue, spawn sessions, create PRs — but never move tickets out of Agent Queue. Next scan cycle: same ticket, new branch, new PR.&lt;br&gt;
While I was closing duplicates, the hive director kept creating new ones. 7 more appeared during cleanup.&lt;br&gt;
&lt;strong&gt;The fix:&lt;/strong&gt; Dedup guard. Check for existing PRs before creating new ones. Move tickets to In Progress when work starts, In Review when the PR is created. Deterministic branch naming — no random suffixes.&lt;br&gt;
&lt;strong&gt;The lesson:&lt;/strong&gt; If your agent loop doesn't track what it already did, it will do everything twice.&lt;/p&gt;
&lt;h4 id=&quot;every-session-ran-on-opus&quot;&gt;Every Session Ran on Opus&lt;/h4&gt;
&lt;p&gt;I noticed rate limits being hit in hours instead of lasting all day. The director script never passed a &lt;code&gt;--model&lt;/code&gt; flag. Claude Code defaults to Opus — the most expensive model. 747+ sessions ran on Opus before I caught it.&lt;br&gt;
One missing CLI flag. Token consumption was 3-5x what it needed to be. A $15/day operation became $65/day.&lt;br&gt;
&lt;strong&gt;The fix:&lt;/strong&gt; Added &lt;code&gt;--model sonnet&lt;/code&gt; as default. Model escalation: attempts 1-2 use Sonnet, attempt 3 auto-escalates to Opus. Immediate 3-5x cost reduction.&lt;br&gt;
&lt;strong&gt;The lesson:&lt;/strong&gt; Defaults matter enormously at scale. A single missing flag multiplied by 1,000 sessions turned a cost-effective operation into a budget-burner.&lt;/p&gt;
&lt;h4 id=&quot;46-out-of-46-sessions-failed-in-one-cycle&quot;&gt;46 out of 46 Sessions Failed in One Cycle&lt;/h4&gt;
&lt;p&gt;The director launched 46 sessions. Every single one died within 1-20 seconds with &quot;You've hit your limit.&quot; The director treated these as normal failures, incremented attempt counters, and after 3 &quot;failures&quot; permanently skipped each ticket.&lt;br&gt;
By morning, 37 tickets were stuck in limbo and dozens were marked as &quot;max attempts exceeded&quot; — even though the failures had nothing to do with the tickets themselves.&lt;br&gt;
&lt;strong&gt;The fix:&lt;/strong&gt; Rate limit pattern matching. Auth failure detection. Fast-fail threshold — sessions dying in under 10 seconds don't count as real attempts. If &amp;gt;80% of a cycle's sessions fail fast, auto-pause the director and sleep until the rate limit resets.&lt;br&gt;
&lt;strong&gt;The lesson:&lt;/strong&gt; Your orchestrator must distinguish between &quot;the task failed&quot; and &quot;the infrastructure failed.&quot; Burning ticket attempts on rate limits is like marking a restaurant order as &quot;rejected by customer&quot; because the kitchen caught fire.&lt;/p&gt;
&lt;h4 id=&quot;the-agent-that-sent-me-on-a-wild-goose-chase&quot;&gt;The Agent That Sent Me on a Wild Goose Chase&lt;/h4&gt;
&lt;p&gt;A Haiku-model agent was assigned an Apple Sign-In bug. After a brief code scan, it confidently told me: &quot;The Firebase Console needs &lt;code&gt;com.firefly.shindig.dev&lt;/code&gt; added as an authorized domain.&quot;&lt;br&gt;
I spent 20 minutes checking the Firebase Console. It was already correct. The &lt;code&gt;.dev&lt;/code&gt; bundle ID doesn't even exist — the app uses &lt;code&gt;com.firefly.shindig.ios&lt;/code&gt;.&lt;br&gt;
The real problem was three code bugs the agent never found.&lt;br&gt;
&lt;strong&gt;The fix:&lt;/strong&gt; Verification protocol. Agents can't tell humans to change external config without file-and-line code evidence. Memory files take precedence — if memory says it's configured and working, the agent needs strong evidence before claiming otherwise. Config-level claims get escalated to Opus for verification.&lt;br&gt;
&lt;strong&gt;The lesson:&lt;/strong&gt; An agent that confidently sends you on a wild goose chase is worse than one that says &quot;I don't know.&quot; Cheaper models need guardrails that prevent authoritative claims about systems they can't inspect.&lt;/p&gt;
&lt;h4 id=&quot;oauth-in-containers-three-login-attempts-destroyed&quot;&gt;OAuth in Containers: Three Login Attempts Destroyed&lt;/h4&gt;
&lt;p&gt;Claude Code authenticates via OAuth. Tokens expire and refresh automatically. I tried three approaches to get auth working in Podman containers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;claude login&lt;/code&gt; inside the container. Works until restart — credentials wiped.&lt;/li&gt;
&lt;li&gt;Base64-encode credentials as an env var. The entrypoint script always decodes on startup, overwriting any token refresh.&lt;/li&gt;
&lt;li&gt;Copy host credentials at build time. Stale by the time the container runs.&lt;br&gt;
&lt;strong&gt;The fix:&lt;/strong&gt; Bind-mount the host credentials file directly into the container:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;volumes:
  - ${HOME}/.claude/.credentials.json:/home/swarm/.claude/.credentials.json:rw
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Login once on the host, container stays authenticated across restarts, rebuilds, and token rotations. Set the env var to empty string so the entrypoint skips injection.&lt;br&gt;
&lt;strong&gt;The lesson:&lt;/strong&gt; Live auth tokens must be shared, not copied. Any approach that snapshots a token creates a stale copy.&lt;/p&gt;
&lt;h4 id=&quot;five-zombie-detectors-all-broken&quot;&gt;Five Zombie Detectors, All Broken&lt;/h4&gt;
&lt;p&gt;After the 124-PR incident, I discovered agent processes that finished work but never exited. They consumed memory and held file locks, preventing new sessions.&lt;br&gt;
I had five separate zombie detection mechanisms. Not one caught the problem.&lt;br&gt;
The bash script only checked for duplicates, not resource thresholds. The task-level checker only looked at output file freshness. The cleanup script explicitly &lt;em&gt;skipped&lt;/em&gt; daemon processes. The ops runner only checked Qdrant task state. The container-based detector couldn't see outside its own namespace.&lt;br&gt;
&lt;strong&gt;The fix:&lt;/strong&gt; One detection mechanism that actually works end-to-end. Process-level zombie detection in the ops runner. Session timeout as a catch-all.&lt;br&gt;
&lt;strong&gt;The lesson:&lt;/strong&gt; Five bad detectors are not better than one good one. Each was built for a specific variant of the problem but had blind spots. The result was false confidence.&lt;/p&gt;
&lt;h3 id=&quot;the-numbers&quot;&gt;The Numbers&lt;/h3&gt;
&lt;p&gt;Over 7 weeks of operation:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.sanity.io/images/dafshiq1/production/66cb96c0b44fd7b85dbd9d0602ed7cc65ebfc6f5-900x420.svg&quot; alt=&quot;Daily agent runs over 7 weeks&quot;&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Total agent runs &lt;/td&gt;&lt;td&gt;6,500+&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Repos covered &lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Peak day (March 5)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13713/i-built-a-35-agent-ai-coding-swarm-that-runs-overnight</guid>
<pubDate>Mon, 30 Mar 2026 20:18:33 +0000</pubDate>
</item>
</channel>
</rss>