惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
Scott Helme
Scott Helme
NISL@THU
NISL@THU
Cisco Talos Blog
Cisco Talos Blog
C
Cybersecurity and Infrastructure Security Agency CISA
AWS News Blog
AWS News Blog
V
Vulnerabilities – Threatpost
J
Java Code Geeks
U
Unit 42
The GitHub Blog
The GitHub Blog
H
Help Net Security
T
Tenable Blog
aimingoo的专栏
aimingoo的专栏
Jina AI
Jina AI
Spread Privacy
Spread Privacy
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
L
Lohrmann on Cybersecurity
T
Threatpost
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Engineering at Meta
Engineering at Meta
A
About on SuperTechFans
I
InfoQ
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog
L
LINUX DO - 最新话题
K
Kaspersky official blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Threat Research - Cisco Blogs
C
Check Point Blog
T
The Blog of Author Tim Ferriss
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
Help Net Security
Help Net Security
Google DeepMind News
Google DeepMind News
A
Arctic Wolf
Y
Y Combinator Blog
N
News | PayPal Newsroom
M
MIT News - Artificial intelligence
Latest news
Latest news
H
Hacker News: Front Page
Blog — PlanetScale
Blog — PlanetScale
腾讯CDC
I
Intezer
爱范儿
爱范儿
F
Fortinet All Blogs
P
Palo Alto Networks Blog
C
CERT Recently Published Vulnerability Notes

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Building M31A: A Terminal-Native AI Coding Agent That Ships, Not Just Suggests
Eshan Roy (eshanized) · 2026-06-16 · via DEV Community

Most AI coding assistants are glorified autocomplete on steroids. They suggest code, maybe write a function or two, but leave you holding the bag when it comes to testing, verification, and actually shipping the changes.

M31A (M31 Autonomous) takes a different approach. It's a terminal-based AI coding agent written in Go that owns a six-phase workflow end-to-end: Initialize → Discuss → Plan → Execute → Verify → Ship. Every run ends with a verified git commit and a learning ledger entry. One static binary, zero telemetry, any POSIX shell.

In this post, I'll walk you through the architecture, design decisions, and technical highlights of this open-source project.

The Problem: AI Assistants That Don't Finish the Job

Here's the typical workflow with most AI coding tools:

  1. Ask the AI to write some code
  2. Copy-paste the suggestion into your editor
  3. Run tests manually
  4. Debug the inevitable issues
  5. Repeat until it works
  6. Commit the changes yourself

The AI "helped" with step 1, but you're still doing 80% of the work. And if something breaks three commits later? Good luck figuring out what the AI actually changed.

M31A flips this model. Instead of being a suggestion engine, it's an autonomous agent that:

  • Asks clarifying questions before planning
  • Generates a structured implementation plan
  • Executes tasks with proper dependency resolution
  • Runs verification (tests, syntax checks)
  • Commits verified changes to git
  • Records what it learned for future sessions

Architecture at a Glance

M31A is built with a clean six-layer architecture:

┌─────────────────────────────────────────────────────────────┐
│  TUI Layer (Bubble Tea)                                     │
│  29 screens, keyboard/mouse handling, streaming display     │
└─────────────────────────────────────────────────────────────┘
                           ↓
┌─────────────────────────────────────────────────────────────┐
│  Workflow Engine                                             │
│  Six-phase orchestration, LLM streaming, plan parsing       │
└─────────────────────────────────────────────────────────────┘
                           ↓
         ┌─────────────────┼─────────────────┐
         ↓                 ↓                 ↓
┌──────────────┐  ┌──────────────┐  ┌────────────────────┐
│  Providers   │  │  Tools       │  │  Domain Packages   │
│  OpenRouter  │  │  Bash        │  │  session, ledger   │
│  Zen         │  │  FileRead    │  │  rollback, bisect  │
│  Fallback    │  │  FileWrite   │  │  taskrunner        │
│              │  │  Glob, Grep  │  │  keychain          │
└──────────────┘  └──────────────┘  └────────────────────┘
         ↓                 ↓                 ↓
┌─────────────────────────────────────────────────────────────┐
│  Infrastructure Layer                                        │
│  git, config, tokens, codeintel, fileutil, logging          │
└─────────────────────────────────────────────────────────────┘

The key insight? Separation of concerns at every level. The TUI doesn't know about LLM APIs. The workflow engine doesn't know about terminal rendering. The tools don't know about workflow phases.

The Six-Phase Workflow Engine

The heart of M31A is the workflow engine, implemented in internal/workflow/engine.go. Let's break down each phase:

Phase 1: Initialize

The agent detects your project type (Go, Python, Node, etc.), initializes git if needed, and creates a .m31a/ planning directory with:

  • PROJECT.md — project metadata
  • STATE.md — current workflow state
  • TASKS.md — task list (populated later)
// From internal/workflow/initialize.go
func (e *Engine) runInitialize(ctx context.Context) error {
    // Detect project type, framework, language
    project := e.detectProject()

    // Initialize git repo if needed
    if !e.git.IsRepository() {
        e.git.Init()
    }

    // Create planning directory
    os.MkdirAll(e.planningDir, 0755)

    // Write PROJECT.md, STATE.md
    e.writeProjectState(project)
}

Phase 2: Discuss

Before jumping into code, the agent asks clarifying questions via LLM streaming. This prevents the classic "I built exactly what you asked for, but not what you wanted" problem.

The discuss phase uses embedded prompt templates (loaded via //go:embed prompts/*.md) to guide the LLM toward asking useful questions about scope, constraints, and edge cases.

Phase 3: Plan

The agent generates a structured implementation plan in markdown format. A custom parser (internal/workflow/plan_parser.go) extracts:

  • Task titles and descriptions
  • Dependencies between tasks
  • Files that will be modified
  • Review notes and questions
// From internal/workflow/plan_parser.go
type Plan struct {
    Title      string
    Tasks      []Task
    Questions  []string
    Notes      string
}

type Task struct {
    ID           int
    Action       string
    Description  string
    Files        []string
    Dependencies []int
}

The plan parser supports refinement with retry logic (max 3 retries, max 5 refinements) and classifies prompt complexity: trivial → simple → moderate → complex.

Phase 4: Execute

This is where the rubber meets the road. The task runner (pkg/taskrunner/runner.go) uses Kahn's algorithm for topological sorting to determine execution order:

// From pkg/taskrunner/runner.go
func (r *Runner) Schedule() ([][]int, error) {
    // Build adjacency list and in-degree count
    inDegree := make(map[int]int)
    dependents := make(map[int][]int)

    for _, t := range r.tasks {
        for _, dep := range t.Dependencies {
            inDegree[t.ID]++
            dependents[dep] = append(dependents[dep], t.ID)
        }
    }

    // Find all tasks with no dependencies
    var queue []int
    for _, t := range r.tasks {
        if inDegree[t.ID] == 0 {
            queue = append(queue, t.ID)
        }
    }

    // Process tasks in topological order
    var groups [][]int
    for len(queue) > 0 {
        groups = append(groups, queue)
        var next []int
        for _, id := range queue {
            for _, dep := range dependents[id] {
                inDegree[dep]--
                if inDegree[dep] == 0 {
                    next = append(next, dep)
                }
            }
        }
        queue = next
    }

    return groups, nil
}

Tasks within a group can run with bounded parallelism (default: 4 concurrent tasks via semaphore). The executor includes a self-heal loop that retries recoverable failures up to 2 times.

Phase 5: Verify

The agent runs verification checks:

  • File existence validation
  • Syntax checking (language-specific)
  • Test execution
  • Smart file truncation for LLM context

If verification fails, the agent can rollback the commit chain using git-bisect integration.

Phase 6: Ship

The final phase:

  1. Creates a git commit with all verified changes
  2. Writes a ledger entry (cross-session learning record)
  3. Archives the session
  4. Generates a demonstration summary

Provider System: Multi-LLM with Automatic Fallback

M31A supports two LLM providers out of the box:

  • OpenRouter — primary gateway with access to Claude, GPT-4, etc.
  • Zen — secondary provider (OpenCode Zen)

The provider layer (internal/provider/) includes some clever engineering:

Automatic Fallback

When a provider degrades (429 rate limit, 503 service unavailable), M31A automatically switches to a healthy provider. The fallback logic uses parallel health checks to minimize latency:

// From internal/provider/fallback.go
func FindFallbackProvider(registry *Registry, current string) (string, *FallbackEvent, error) {
    // Collect candidate providers
    candidates := registry.ListAll()

    // Parallel health checks (10s timeout)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    ch := make(chan result, len(candidates))
    for _, c := range candidates {
        go func(c candidate) {
            status := c.provider.HealthCheck(ctx)
            ch <- result{name: c.name, status: status}
        }(c)
    }

    // Return first healthy provider in priority order
    for i := 0; i < len(candidates); i++ {
        r := <-ch
        if r.status.Status == "live" || r.status.Status == "slow" {
            registry.TrySetActive(r.name)
            return r.name, &FallbackEvent{...}, nil
        }
    }
}

Model Arbitrage

M31A includes a model arbitrage system (pkg/arbitrage/) that automatically switches to the cheapest model that meets the task's capability threshold:

// From pkg/arbitrage/arbitrage.go
func (s *Scorer) Score(task Task) (ComplexityLevel, int) {
    level := classifyText(task.Action, task.Description)

    // Boost complexity when task touches many files
    if len(task.Files) > 3 {
        level = boostLevel(level, 1)
    }

    // Boost when task has many dependencies
    if len(task.Dependencies) > 3 {
        level = boostLevel(level, 1)
    }

    input, output := s.EstimateTokens(level, task)
    return level, input + output
}

The scorer uses keyword analysis to classify tasks as simple, moderate, or complex, then recommends the cheapest model that can handle that complexity level.

Tool System: Deliberately Small, Aggressively Sandboxed

M31A ships with 5 core tools:

  1. Bash — shell command execution
  2. FileRead — read files with size limits (50MB max)
  3. FileWrite — atomic file writes (temp + rename)
  4. Glob — file pattern matching (doublestar, 1000 result limit)
  5. Grep — content search (ripgrep when available, pure-Go fallback)

The tool surface area is intentionally small. Each tool is aggressively sandboxed with:

Permission Gating

Every tool call is gated by a permission modal with configurable timeout (default 300s):

// From internal/tools/permissions.go
type PermissionMode string

const (
    ModeAsk         PermissionMode = "ask"
    ModeAllowAll    PermissionMode = "allow_all"
    ModeDenyAll     PermissionMode = "deny_all"
)

func (d *Dispatcher) RequestPermission(ctx context.Context, tool Tool, input ToolInput) error {
    if d.mode == ModeAllowAll {
        return nil
    }

    // Send permission request to TUI
    ch := make(chan PermissionResponse)
    d.emitter.Emit(PermissionRequestMsg{...})

    // Wait for user response with timeout
    select {
    case resp := <-ch:
        if !resp.Approved {
            return ErrPermissionDenied
        }
    case <-time.After(d.timeout):
        return ErrPermissionTimeout
    }
}

Security Guards

  • Path traversal guards: symlink resolution + workDir prefix check
  • Output capping: MaxToolOutputChars (10,000) / BashOutputLimit (50,000)
  • SSRF protection: DNS pinning, TOCTOU prevention, redirect checking (WebFetch)
  • Process lifecycle: SIGINT/SIGKILL grace period, pipe cleanup

Risk Levels

Each tool declares its risk level:

type RiskLevel string

const (
    RiskSafe        RiskLevel = "safe"
    RiskMedium      RiskLevel = "medium"
    RiskDangerous   RiskLevel = "dangerous"
    RiskDestructive RiskLevel = "destructive"
)

Bash is dangerous, FileWrite is medium, FileRead is safe. The permission system uses these levels to determine whether to prompt the user.

Cross-Session Learning Ledger

One of M31A's most interesting features is the cross-session learning ledger (pkg/ledger/). Every session writes a structured record to a markdown file:

| Session | Model | Tasks | Failed | Cost | Duration | Framework |
|---------|-------|-------|--------|------|----------|-----------|
| a1b2c3d4 | claude-3.5-sonnet | 5 | 1 | $0.12 | 8min | react |
| e5f6g7h8 | gpt-4-turbo | 3 | 0 | $0.08 | 4min | go |

The ledger tracks:

  • Session ID and timestamp
  • Model and provider used
  • Task count and failures
  • Cost estimate
  • Duration
  • Project type and framework
  • Goal keywords (with stop-word filtering)

Over time, the agent can query the ledger to learn from past sessions:

// From pkg/ledger/ledger.go
type LedgerStats struct {
    TotalSessions      int
    AvgTaskCount       float64
    AvgCost            float64
    AvgDurationMinutes float64
    TotalFailedTasks   int
    TopFailures        []string
    TopFrameworks       []string
    ByProjectType      map[string]int
}

This creates a feedback loop where the agent gets sharper over time, learning which frameworks are common, what types of tasks fail, and how long things typically take.

AutoDream: Context Window Consolidation

Long conversations blow the context window. M31A solves this with AutoDream (pkg/autodream/), an automatic context consolidation system:

// From pkg/autodream/autodream.go
func (c *Consolidator) Consolidate() (ConsolidationResult, error) {
    // Protect system prompts and recent messages
    protected := c.protectedIndices()
    candidates := c.candidateIndices(protected)

    // Summarize oldest 50% of non-protected messages
    midpoint := len(candidates) / 2
    toCompress := candidates[:midpoint]

    // Build summary prompt
    summary := c.summarize(toCompress)

    // Replace old messages with summary
    c.messages = c.replaceWithSummary(toCompress, summary)

    return ConsolidationResult{
        MessagesRemoved: len(toCompress),
        TokensSaved:     c.estimateTokensSaved(toCompress, summary),
    }
}

AutoDream triggers at 60% context usage by default. It uses role-sampled summarization (system prompts are never compressed) and preserves recent messages for continuity.

TUI: 29 Screens Built with Bubble Tea

The terminal UI is built with Bubble Tea, following the Elm architecture. Screen routing uses an enum-based dispatcher:

// From internal/tui/app_state.go
type Screen int

const (
    ScreenREPL Screen = iota
    ScreenGoalInput
    ScreenPhaseModelPicker
    ScreenPlan
    ScreenDiscuss
    ScreenExecute
    ScreenVerify
    ScreenShip
    ScreenModelSelector
    ScreenSettings
    // ... 19 more screens
)

func (m AppState) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case SwitchScreenMsg:
        m.screen = msg.Screen
        return m, nil
    }

    // Route to active screen's Update function
    switch m.screen {
    case ScreenREPL:
        m.repl, cmd = m.repl.Update(msg)
    case ScreenPlan:
        m.plan, cmd = m.plan.Update(msg)
    // ...
    }
}

The TUI includes some nice touches:

  • Fuzzy model selector with per-token cost comparison
  • Permission modals with keyboard shortcuts (y/a/n/e for allow/allow always/deny/exit)
  • Streaming display for real-time LLM output
  • Dark/light themes with auto mode
  • Context warning banner at 80% window usage

Commit Rollback Chain

When verification fails, M31A can rollback the commit chain using git-bisect integration (pkg/bisect/):

// From pkg/rollback/rollback.go
func (r *Rollback) HardReset(commit string) error {
    // Create backup branch before destructive operation
    backupName := fmt.Sprintf("m31a/rollback-backup-%d", time.Now().Unix())
    r.git.CreateBranch(backupName)

    // Auto-stash uncommitted changes
    if r.git.HasUncommittedChanges() {
        r.git.Stash()
        defer r.git.StashPop()
    }

    // Hard reset to target commit
    return r.git.ResetHard(commit)
}

The rollback system maintains a commit chain with soft/hard/safe reset options. Safe reset creates backup branches before any destructive operation.

OS-Native Secure Key Storage

API keys are stored using OS-native keychain backends (pkg/keychain/):

  • Linux: D-Bus Secret Service + pass CLI fallback
  • macOS: /usr/bin/security CLI
  • Windows: Windows Credential Manager
// From pkg/keychain/keychain.go
type Keychain interface {
    Get(service string) (string, error)
    Set(service, value string) error
    Delete(service string) error
}

The keychain abstraction uses build tags to select the platform-specific implementation at compile time. Service names follow the pattern m31a/openrouter, m31a/zen.

Key resolution order:

  1. Environment variable: M31A_OPENROUTER_API_KEY
  2. Standard fallback: OPENROUTER_API_KEY
  3. OS keychain: m31a/openrouter
  4. Config file: provider.openrouter.api_key

Keys are never written to disk in plaintext when keychain is available.

Static Binary, Zero Telemetry

M31A is compiled with CGO_ENABLED=0, producing a fully static binary with no C dependencies:

# From Makefile
build:
    CGO_ENABLED=0 go build -ldflags "-s -w \
        -X main.Version=$(VERSION) \
        -X main.Commit=$(COMMIT) \
        -X main.Date=$(DATE)" \
        -o m31a ./cmd/m31a

The binary is typically 15-20MB (stripped with -s -w ldflags). Cross-compilation targets include linux/darwin/windows × amd64/arm64.

Zero telemetry: no analytics, no crash reporting, no usage pings. Your code never leaves your machine except when sent to the LLM provider for inference.

Session Persistence and Recovery

Sessions persist to <workDir>/.m31a/session.json, including:

  • Workflow state (goal, phase, questions)
  • Message history (separate messages.json)
  • Checkpoints (max 2 for undo/rollback)

If you hit Ctrl+C, lose network, or your laptop dies, you can resume mid-workflow:

$ m31a --resume
# Shows session browser with recent sessions
# Restores workflow state and continues from last checkpoint

Testing Strategy

M31A uses Go's standard testing package with no external mocking frameworks:

  • Unit tests: individual functions/methods
  • Integration tests: real git repos, temp dirs, HTTP test servers
  • Security tests: SSRF protection, timeout enforcement, path traversal
  • Table-driven tests: anonymous structs with t.Parallel()

Coverage targets:

  • Overall: 75% (currently ~74.7%)
  • Critical packages: 90% — pkg/taskrunner (89.9%), pkg/bisect (91.3%), pkg/rollback (89.1%)

The test suite includes some interesting patterns:

// Security test for SSRF protection
func TestWebFetch_BlocksPrivateIPs(t *testing.T) {
    tests := []struct {
        url      string
        wantErr  error
    }{
        {"http://127.0.0.1/admin", ErrPrivateIPBlocked},
        {"http://192.168.1.1/config", ErrPrivateIPBlocked},
        {"http://10.0.0.1/secret", ErrPrivateIPBlocked},
        {"http://169.254.169.254/metadata", ErrPrivateIPBlocked}, // AWS metadata
    }

    for _, tt := range tests {
        t.Run(tt.url, func(t *testing.T) {
            t.Parallel()
            _, err := WebFetch(tt.url)
            if !errors.Is(err, tt.wantErr) {
                t.Errorf("got %v, want %v", err, tt.wantErr)
            }
        })
    }
}

Getting Started

Installation is a one-liner:

# macOS (Homebrew)
brew install eshanized/tap/m31a

# Linux / macOS (curl)
curl -fsSL https://raw.githubusercontent.com/eshanized/M31A/main/install.sh | bash

# From source (any OS)
git clone https://github.com/eshanized/M31A.git
cd M31A
CGO_ENABLED=0 go build -o m31a ./cmd/m31a

On first launch, M31A prompts for your OpenRouter or Zen API key and stores it in the OS keychain.

Basic usage:

$ m31a
# TUI launches
# Type your goal: "refactor the auth middleware to use JWT with RS256"
# Agent runs through six phases
# Ends with verified git commit

Slash commands:

/help          list all commands
/workflow      kick off the six-phase flow
/model         open the model selector (fuzzy search)
/provider      switch provider
/ledger stats  show your cross-session ledger
/rollback      show the commit chain; --hard to reset
/compress      trigger AutoDream manually

What's Next

M31A is at v1.0.0 with the core feature set complete. The roadmap includes:

  • Ghost mode — headless runs producing structured diffs
  • Picture-in-picture — second agent in side pane for cross-review
  • Subagents — delegate sub-tasks to specialized agents (code, test, doc)
  • Deferred tools — queue tool calls requiring human approval for batch review

Lessons Learned

Building M31A taught me a few things:

  1. Workflow ownership matters more than code generation. The six-phase workflow is more valuable than any single code suggestion.

  2. Small tool surface area is a feature. Five well-sandboxed tools are easier to secure than twenty half-baked ones.

  3. Learning compounds. The cross-session ledger creates a feedback loop that makes the agent better over time.

  4. Terminal UIs can be delightful. Bubble Tea proves that terminal apps don't have to be ugly or hard to use.

  5. Static binaries are liberating. No runtime dependencies, no Docker required, just download and run.

Conclusion

M31A is an experiment in what AI coding assistants could be if they owned the entire workflow instead of just the fun part. It's not perfect — the TUI test coverage needs work (38.6%), and there are some known bugs around git status detection — but the architecture is sound and the core workflow is production-ready.

If you're interested in the intersection of AI, developer tools, and terminal UIs, I'd love your feedback. Star the repo, open an issue, or better yet, try it on your codebase and let me know what breaks.

Links:

- Research: https://github.com/eshanized/M31A/blob/master/RESEARCH.md

Thanks to the Bubble Tea, Lip Gloss, and Glamour teams for making terminal UIs enjoyable to build. And thanks to everyone who has tried M31A and reported bugs — your feedback makes it better.