gludd
Autonomous Software Development
An AI agent that writes, tests, and ships code
— explained, with exact code paths
v0.1.0-beta.2 · MIT License · built 2026-07-14
What is gludd?
gludd is an AI agent that writes code for you.
You describe what you want built. gludd writes the code, tests it, reviews it, and saves it to your project — automatically.
Think of it like a junior developer that never sleeps.
You hand them a task. They write it, test it, have a senior dev check it, and commit it. Then they grab the next task.
Not a chatbot. Not a code-completion tool. A full agent that does the whole job — start to finish.
This deck walks through the exact code paths and database tables behind every step.
The problem
Software development is slow and repetitive.
- Writing boilerplate code takes hours
- Writing tests for every change takes more hours
- Reviewing code takes yet more hours
- Fixing bugs found in review takes even more
- And then you do it all again tomorrow
What if a machine could do all of that — while you focus on the hard problems?
That's what gludd does. You describe the goal. gludd handles the grind.
Architecture at a glance
daemon.py boots a FastAPI process and wires an EventLoop (event_loop/loop.py) that ticks forever.
- Every tick opens one DB session and runs 16 named phases in a fixed order
- A todo moves through a state machine: BACKLOG → SCHEDULED/QUEUED → ACTIVE → done/reconciled
- Every state change, dispatch, review verdict, and commit is a database row — nothing lives only in memory
graph TD
User["You"] -->|POST /api/todos| Daemon["daemon.py + EventLoop
event_loop/loop.py"]
Daemon -->|dispatch| Model["Model gateway
models/gateway.py"]
Daemon -->|execute| Runner["Ansible / worker
ansible/runner.py, worker/app.py"]
Model --> Review["Reviewer
review/reviewer.py"]
Runner --> Review
Review -->|approve| Git[("git_automation/repo.py
commit + push")]
Daemon -.->|next tick, ~1s| Daemon
Source: src/general_ludd/daemon.py, src/general_ludd/event_loop/loop.py
Daemon overview — the central orchestrator
src/general_ludd/daemon.py (3,126 lines) is the single entry point. It boots a FastAPI process, wires every subsystem, and runs the EventLoop forever.
graph TD
subgraph Boot["Lifespan startup (daemon.py)"]
A1["init DB + session factory
:1188"] --> A2["wire controllers
(SpendLimiter, BudgetGuard,
Hibernation, Pause)"]
A2 --> A3["wire agents
(AgentDispatcher, ModelGateway,
AnsibleRunnerAdapter)"]
A3 --> A4["register all routers
(todos, review, admin, MCP,
self-improve, compute...)"]
A4 --> A5["start EventLoop
asyncio.create_task"]
end
subgraph Runtime["Per-tick (event_loop/loop.py)"]
B1["open DB session"] --> B2["run 16 PHASE_ORDER phases"]
B2 --> B3["commit + close session"]
end
subgraph Shutdown["Lifespan teardown"]
C1["drain background tasks"] --> C2["close MCP subprocesses"]
C2 --> C3["close DB connections"]
end
Boot --> Runtime
Runtime -.->|"~1s tick"| Runtime
Runtime --> Shutdown
- Wired at boot: 24 model providers, 109 Ansible roles, 14 enforcement plugins, 27 DB tables
- Routers registered: todos, review, admin, MCP, self-improve, compute, pause, remediation, eval, security, account, memory, worktree, stream, files, facts, ornith, benchmark, model-performance
- Daemon state: single source of truth (<code>app.state</code>) — DB factory, controllers, agent registry, dispatcher, all in one process
Source: src/general_ludd/daemon.py:1-3126, src/general_ludd/event_loop/loop.py
The flagship flow: todo → AI implements → review → commit
Nine stages, each backed by a named function and a database table. The next slides walk each one.
flowchart TD
n1["1 Submit
routers/todos.py"] --> n2["2 Tick loop
event_loop/loop.py"]
n2 --> n3["3 Schedule
event_loop/scheduler.py"]
n3 --> n4["4 Claim
db/repository.py"]
n4 --> n5["5 Dispatch
loop.py _dispatch_execute_job"]
n5 --> n6["6 Model call
models/gateway.py"]
n6 --> n7["7 Execute
ansible/runner.py or worker/app.py"]
n7 --> n8["8 Review
review/reviewer.py"]
n8 --> n9["9 Reconcile + commit
git_automation/repo.py"]
n8 -.->|reject: retry| n6
Source: code-trace audit, verified against src/general_ludd/ on 2026-07-12T10:39:48Z
The todo state machine
Every todo moves through one explicit lifecycle. Each arrow is a guarded transition (VALID_TRANSITIONS, schemas/todo.py:62) written to todo_events.
stateDiagram-v2
[*] --> BACKLOG
BACKLOG --> SCHEDULED: has schedule
BACKLOG --> QUEUED: ready now
SCHEDULED --> QUEUED: due / cron fire
QUEUED --> ACTIVE: claim CAS
ACTIVE --> AWAITING_RESULT: dispatched
AWAITING_RESULT --> REVIEWING_RETURN: result persisted
REVIEWING_RETURN --> COMPLETE: approve
REVIEWING_RETURN --> NEEDS_MORE_WORK: changes
REVIEWING_RETURN --> FAILED: reject
NEEDS_MORE_WORK --> QUEUED: retry
ACTIVE --> BUDGET_EXCEEDED: spend gate
ACTIVE --> BLOCKED_ON_HUMAN: needs approval
COMPLETE --> [*]
Source: src/general_ludd/schemas/todo.py:12-106 (TodoStatus + VALID_TRANSITIONS)
Flagship flow · stage 1 of 9
1Submit
Everything starts with a todo — a short description of what you want done, submitted over HTTP.
flowchart TD
A["POST /api/todos
routers/todos.py:170"] --> B["TodoRepository.create
db/repository.py"]
B --> C["INSERT todos
status = BACKLOG"]
- POST /api/todos is handled by api_add_todo — src/general_ludd/routers/todos.py:170
- The router calls TodoRepository.create — src/general_ludd/db/repository.py
- One row lands in the todos table (db/models.py:180), status =
BACKLOG
- No todo_events row yet at this point — the first event is appended later, when the todo is claimed (see stage 4)
Like writing a sticky note and putting it on the board — except the board is a SQL table with a foreign key.
Flagship flow · stage 2 of 9
2The tick loop — 17 phases
daemon.py wires an EventLoop (event_loop/loop.py). Every tick runs PHASE_ORDER (loop.py:88) — the same 17 phases, in the same order, inside one DB session.
flowchart TD
subgraph G1["Config + review intake"]
A1["load_config_snapshot"] --> A2["claim_unreviewed_
task_returns"]
A2 --> A3["dispatch_return_
review_jobs"]
A3 --> A4["evaluate_pid_
controllers"]
end
subgraph G2["Scheduling + buckets"]
B1["refill_task_buckets"] --> B2["run_scheduler"]
B2 --> B3["claim_runnable_todos"]
B3 --> B4["evaluate_rules"]
end
subgraph G3["Execution + reconcile"]
C1["dispatch_execute_jobs"] --> C2["reconcile_completed_
decisions"]
C2 --> C3["refresh_model_
performance"]
end
subgraph G4["Compute + credits"]
D1["check_compute_
utilization"] --> D2["check_service_credits"]
D2 --> D3["remediate_blocked_
tasks"]
end
subgraph G5["Self-improve + metrics"]
E1["self_improve"] --> E2["poll_issue_sources"]
E2 --> E3["emit_tick_metrics"]
end
G1 --> G2
G2 --> G3
G3 --> G4
G4 --> G5
Source: src/general_ludd/event_loop/loop.py:88-106 (PHASE_ORDER, 17 entries)
One tick, in order
The load-bearing phases of PHASE_ORDER, in the exact order they run inside a single tick's DB session.
flowchart TD
A["load_config
snapshot"] --> B["claim + dispatch
return review"]
B --> C["run_scheduler
promote due todos"]
C --> D["claim_runnable
todos (CAS)"]
D --> E["evaluate_rules"]
E --> F["dispatch_execute
jobs (spend gate)"]
F --> G["reconcile_completed
decisions + commit"]
G --> H["self_improve"]
H --> I["emit_tick_metrics"]
I -.->|next tick ~1s| A
Source: src/general_ludd/event_loop/loop.py:88-106 (PHASE_ORDER)
Flagship flow · stage 3 of 9
3Schedule promotion
The run_scheduler phase calls TodoScheduler.tick() — src/general_ludd/event_loop/scheduler.py:107 (class TodoScheduler at scheduler.py:88).
flowchart TD
A["TodoScheduler.tick()"] --> B{"one-shot
or cron?"}
B -->|one-shot, due| C["SCHEDULED -> QUEUED"]
B -->|cron fire| D["spawn QUEUED child
advance next_run_at"]
- A one-shot todo promotes
SCHEDULED → QUEUED once now ≥ scheduled_at
- A cron todo (
todos.cron, croniter grammar) stays a SCHEDULED template forever — each fire spawns a QUEUED child clone and advances next_run_at
- The due-todo lookup is index-backed: ix_todos_scheduled_lookup on
(status, schedule_paused, next_run_at, scheduled_at) — db/models.py:257
Source: src/general_ludd/event_loop/scheduler.py, src/general_ludd/db/models.py:227-269
Flagship flow · stage 4 of 9
4Claim — version-guarded CAS
gludd won't let two workers grab the same todo. The claim_runnable_todos phase (EventLoop._phase_claim_runnable_todos, loop.py:1365) calls a compare-and-swap.
flowchart TD
A["claim_runnable_todos
phase"] --> B["TodoRepository.claim_runnable
CAS: QUEUED -> ACTIVE"]
B --> C["acquire_lease()
bucket_leases row"]
C --> D["todo_events row
actor=claim_runnable"]
- db/repository.py:424 guards the UPDATE with the
todos.version column; losing the race just leaves the row QUEUED for next tick
- event_loop/lease.py:15 takes an exclusive lease on the todo's resource bucket
- The lease is a real row in bucket_leases (db/models.py:495):
bucket_key, holder_id, expires_at — a stale lease just expires
Flagship flow · stage 5 of 9
5Dispatch — the spend gate
_dispatch_execute_job (event_loop/loop.py:1844) runs from the dispatch_execute_jobs phase — but it checks the budget before it checks anything else.
flowchart TD
A["_dispatch_execute_job"] --> B{"SpendLimiter.try_charge
(in-memory gate)"}
B -->|accepted| C["proceed to model call"]
B -->|rejected| D["defer todo
(retry later tick)"]
C --> E["periodic flush ->
spend_records"]
SpendLimiter.try_charge (controllers/spend_limiter.py:278) checks a rolling-window budget before a single token is spent — an in-memory check-and-record, not a DB write
- Accepted charges drain via a periodic flush (SpendRepository.add, db/repository.py:1773) into spend_records (db/models.py:633) — the in-memory ledger is the source of truth for the live gate, the table is the durable record
- A rejected charge defers the todo silently: no model call, no cost incurred
The gate runs first. gludd cannot overspend by dispatching, then discovering it should not have.
Flagship flow · stage 6 of 9
6Model call — gateway + tool loop
flowchart TD
A["invoke_model_for_generation"] --> B["ModelGateway.call_model
(LangChain + tenacity retries)"]
B --> C["INSERT model_call_logs
+ model_performance rollup"]
B --> D["ToolCallLoop.run_with_tools
(if tool use needed)"]
D --> E["DynamicDispatcher
fails closed on unknown tool"]
- models/job_invocation.py:41 calls models/gateway.py:642 — a LangChain provider registry with tenacity retries
- Every call is billed and logged to model_call_logs (db/models.py:962) and rolled up into model_performance (db/models.py:1005)
- Autonomous tool use hands off to execution/tool_loop.py:139: model proposes a tool call, gludd executes it, feeds the result back, repeats
- Tool calls route through dispatch/dynamic_dispatcher.py:198 — a capability lattice that fails closed on any tool it doesn't recognize
Flagship flow · stage 7 of 9
7Execute — playbook or worker HTTP
Two execution paths converge on the same persistence call.
flowchart TD
A["local: run_playbook
ansible/runner.py"] --> C["_persist_task_return
loop.py:3016"]
B["remote: POST /jobs/execute
worker/app.py:315"] --> C
C --> D["INSERT task_returns
(unreviewed)"]
- Local: runner.write_vars (ansible/runner.py:140) + run_playbook (ansible/runner.py:166) hand the job to an Ansible playbook
- Remote/isolated: POST /jobs/execute — worker/app.py:315 — runs the same job over HTTP against a worker process
- Either path's result flows into EventLoop._persist_task_return — event_loop/loop.py:3016
- One row lands in task_returns (db/models.py:306) — the raw, unreviewed result
Submit → execute, and what each step writes
flowchart TD
s1["1 POST /api/todos
(You -> routers/todos.py)"] --> s2["2 INSERT todos
(BACKLOG)"]
s2 --> s3["3 claim QUEUED -> ACTIVE
+ todo_events (EventLoop)"]
s3 --> s4["4 SpendLimiter.try_charge
(gate)"]
s4 --> s5["5 call_model
(models/gateway.py)"]
s5 --> s6["6 INSERT model_call_logs"]
s6 --> s7["7 execute job
(runner / worker)"]
s7 --> s8["8 INSERT task_returns
(unreviewed)"]
Source: routers/todos.py api_add_todo, event_loop/loop.py (_dispatch_execute_job, _persist_task_return), models/gateway.py call_model
Flagship flow · stage 8 of 9
8Review — a second AI, adversarially
gludd doesn't let the same model that wrote the code approve it.
flowchart TD
A["ReturnReviewer.review_return()"] --> B{"output parseable?"}
B -->|no| C["decision = failed"]
B -->|yes| D["decision = approve /
changes / reject"]
C --> E["apply_decision()
task_decisions + audit_events"]
D --> E
- review/reviewer.py:49 runs an adversarial scan against the
task_return, not just "did it run"
- Model output is parsed JSON-fence tolerant (strips
```json fences) — an unparseable verdict becomes decision=failed, never a silent pass
- review/decision_applier.py:28 writes the verdict
- Results land in task_decisions (db/models.py:344, 1:1 with
task_returns) and audit_events (db/models.py:413)
The review loop, in detail
flowchart TD
A["Execute (stage 7)
job result"] --> B["INSERT task_returns
(loop.py:3016)"]
B --> C["ReturnReviewer.review_return()
adversarial scan + JSON-fence-tolerant parse"]
C --> D{"output parsed?"}
D -->|no| E["decision = failed"]
D -->|yes| F["decision = approve /
changes / reject"]
E --> G["apply_decision()
INSERT task_decisions + audit_events"]
F --> G
G --> H["reconcile_completed_decisions
(loop.py:3066)"]
Source: src/general_ludd/review/reviewer.py:49, src/general_ludd/review/decision_applier.py:28
Flagship flow · stage 9 of 9
9Reconcile + commit
flowchart TD
A["reconcile_completed_decisions
loop.py:3066"] --> B["_try_commit_completed_work
loop.py:3355"]
B --> C["GitAutomation
commit + push (repo.py:180)"]
C --> D["optional: PRDelivery
_maybe_open_pr"]
- _phase_reconcile_completed_decisions — event_loop/loop.py:3066 — reads the latest
task_decisions and updates todo status
- _try_commit_completed_work — event_loop/loop.py:3355 — the git-delivery path: the only point where a todo's affected files are actually known (worktree changes don't exist before this)
- GitAutomation — git_automation/repo.py:180 — commits and pushes to a per-todo branch
gludd-<todo_id>
- Optional PR delivery via PRDelivery — git_automation/pr_delivery.py — used from EventLoop._maybe_open_pr (loop.py:3448)
Everything is traceable: what was asked, what was written, what was reviewed, what was committed — each step is a database row, not a log line.
Flagship flow · the whole path in one view
The complete code path
Every hop from HTTP submission to a git commit, with the file that implements it.
flowchart TD
A["POST /api/todos
routers/todos.py:170"] --> B["todos table
db/models.py:180"]
B --> C["EventLoop tick
PHASE_ORDER (event_loop/loop.py)"]
C --> D["claim_runnable + bucket_leases
db/repository.py"]
D --> E["dispatch_execute_jobs
event_loop/loop.py"]
E --> F["two-phase model call
models/job_invocation.py + execution/tool_loop.py"]
F --> G["task_returns
_persist_task_return (loop.py:3068)"]
G --> H["review -> task_decisions
review/reviewer.py"]
H --> I["reconcile + commit
loop.py:3066 -> git_automation/repo.py:180"]
- Submit — routers/todos.py:170 inserts one
todos row (db/models.py:180)
- Claim — version-guarded CAS in db/repository.py (claim_runnable), leased via
bucket_leases
- Dispatch — event_loop/loop.py (dispatch_execute_jobs) builds the
JobSpec and runs the model
- Two-phase model call — tool-free generation (models/job_invocation.py via models/gateway.py), then the autonomous tool loop (execution/tool_loop.py)
- Return & review — worker output lands in
task_returns (loop.py:3068); a second model reviews it (review/reviewer.py) and writes task_decisions
- Reconcile + commit — loop.py:3066 applies the decision; git_automation/repo.py:180 commits + pushes
Source: code-trace audit against src/general_ludd/ on 2026-07-12T10:39:48Z
Flagship flow · subsystem map
Exact code paths — every subsystem, one slide
| Subsystem | Key file(s) | What happens |
| HTTP API | routers/todos.py:170 | POST /api/todos → INSERT todos (BACKLOG) |
| Event Loop | event_loop/loop.py:88 | 16-phase tick, ~1s cycle, one DB session |
| Scheduler | event_loop/scheduler.py:107 | SCHEDULED → QUEUED promotion, cron fire |
| Claim (CAS) | db/repository.py:424 | QUEUED → ACTIVE, version-guarded |
| Lease | event_loop/lease.py:15 | bucket_leases row, holder_id + expires_at |
| Spend Gate | controllers/spend_limiter.py:278 | try_charge (in-memory), defer on reject |
| Model Call | models/gateway.py:642 | LangChain + tenacity retries, 24 providers |
| Tool Loop | execution/tool_loop.py:139 | Model proposes tool → execute → feed back |
| Execute | ansible/runner.py:166 / worker/app.py:315 | Local playbook or remote worker HTTP |
| Persist | event_loop/loop.py:3016 | INSERT task_returns (unreviewed) |
| Review | review/reviewer.py:49 | Adversarial scan, JSON-fence-tolerant parse |
| Decision | review/decision_applier.py:28 | INSERT task_decisions + audit_events |
| Reconcile | event_loop/loop.py:3066 | Apply decision, update todo status |
| Commit | git_automation/repo.py:180 | git commit + push to gludd-<todo_id> branch |
| PR (optional) | git_automation/pr_delivery.py | _maybe_open_pr, via loop.py:3448 |
Source: code-trace audit against src/general_ludd/ on 2026-07-12T10:39:48Z
The database — 27 tables, zero drift
Every table used above (and 15 more) is defined once, in one file.
- All 27 tables are declared in src/general_ludd/db/models.py
- Migrations are a single linear chain: alembic/versions/ 001 → 024, no branches
- ORM ↔ migration parity is enforced by a test, not a promise: tests/unit/test_alembic_orm_parity.py
flowchart TD
DB[("27 tables
db/models.py")] --> WC["Work-cycle spine
(4 tables, next slide)"]
DB --> SC["Scheduling + safety
+ cost (4 tables)"]
DB --> MM["Models + memory
+ compute (5 tables)"]
Source: src/general_ludd/db/models.py (27 tables) · alembic/versions/001-024 · tests/unit/test_alembic_orm_parity.py
Key tables (1 of 3) — the work-cycle spine
| Table | Line | What it stores |
todos | 180 | One row per submitted task; state machine BACKLOG → ... → done |
todo_events | 280 | Status-transition log per todo (FK todos.todo_id, CASCADE) |
task_returns | 306 | Raw result of one dispatched job (stage 7 output, unreviewed) |
task_decisions | 344 | Reviewer verdict for one task_return (1:1, FK CASCADE) |
flowchart TD
todos[("todos
180")] --> todo_events[("todo_events
280")]
todos --> task_returns[("task_returns
306")]
task_returns --> task_decisions[("task_decisions
344")]
Source: src/general_ludd/db/models.py
Key tables (2 of 3) — scheduling, safety, cost
| Table | Line | What it stores |
queues | 388 | Named queue config: caps, allowed playbooks/models, retry policy |
audit_events | 413 | System-wide event log (decisions, leases, spend, more) |
bucket_leases | 495 | Exclusive CAS lease per resource bucket (holder_id, expires_at) |
spend_records | 633 | One row per spend event for the rolling-window cost limiter |
flowchart TD
todos[("todos")] --> queues[("queues
388")]
todos --> bucket_leases[("bucket_leases
495")]
todos --> spend_records[("spend_records
633")]
task_decisions[("task_decisions")] --> audit_events[("audit_events
413")]
Source: src/general_ludd/db/models.py
Key tables (3 of 3) — models, memory, compute
| Table | Line | What it stores |
model_call_logs | 962 | Per-LLM-call record: tokens, latency, provider |
model_performance | 1005 | Aggregated per-model / per-task-type performance stats |
human_todos | 857 | Todos awaiting human approval or input |
memory_records | 739 | Long-term agent memory entries |
slurm_jobs | 1093 | HPC/Slurm job tracking for compute-heavy tasks |
flowchart TD
todos[("todos")] --> model_call_logs[("model_call_logs
962")]
model_call_logs --> model_performance[("model_performance
1005")]
todos --> human_todos[("human_todos
857")]
todos --> memory_records[("memory_records
739")]
todos --> slurm_jobs[("slurm_jobs
1093")]
Source: src/general_ludd/db/models.py
Behaviors → DB tables — what writes where
Every meaningful action in gludd writes a row to a specific table. Here is the complete mapping.
| Behavior | Table | Line | What is recorded |
| Submit a task | todos | 180 | Description, queue, priority, schedule, cost est. |
| Status transition | todo_events | 289 | old_status → new_status, actor, reason |
| Claim a todo | bucket_leases | 504 | Exclusive CAS lease (holder_id, expires_at) |
| Spend gate pass | spend_records | 634 | Unix-ts cost event (rolling-window math) |
| Model API call | model_call_logs | 962 | Tokens in/out, latency, provider, cost |
| Model perf rollup | model_performance | 1005 | Aggregated success/latency/cost per model |
| Job executes | task_returns | 315 | Raw result: exit code, summary, artifacts |
| Review verdict | task_decisions | 353 | approve / changes / reject, confidence |
| Any audit event | audit_events | 422 | System-wide event log (all action types) |
| Needs human | human_todos | 857 | Tasks awaiting human approval or input |
| Agent memory | memory_records | 741 | Key-value store, TTL, per-agent namespace |
| Queue config | queues | 397 | Caps, allowed playbooks/models, retry policy |
| Role execution | role_runs | 665 | Accounting ledger per role invocation |
| Benchmark run | benchmark_results | 685 | Completion/quality/adherence/efficiency scores |
| Task embedding | task_embeddings | 776 | Cosine-similarity routing vectors (RAG) |
Source: src/general_ludd/db/models.py (27 total tables; 15 shown above are the behavioral spine)
MCP integration — the tool-call boundary
gludd implements the Model Context Protocol (MCP) as the boundary between AI reasoning and real-world execution. The model proposes a tool; MCP validates, executes, and returns the result.
graph TD
subgraph MCP["src/general_ludd/mcp/"]
A["client.py
MCPClient
stdin/stdout subprocess"] --> B["transport.py
Transport launcher
(python, node, uvx, npm)"]
B --> C["loader.py
MCPToolLoader
register + load"]
C --> D["registry.py
ToolRegistry
register + lookup"]
D --> E["builtins.py
Built-in tools
(run_project_check...)"]
C --> F["catalog.py
ToolCatalog
discover + list"]
end
subgraph Model["Model side"]
M1["execution/tool_loop.py
model proposes tool call"] --> M2["DynamicDispatcher
capability lattice"]
M2 --> M3["MCPClient.call_tool()"]
M3 --> M4["return result to model"]
end
Model -.->|"validate + execute"| MCP
- Transport: Python subprocess, Node subprocess, uvx, npm — all argv-validated against a per-interpreter allowlist
- Secrets: mcp/secrets.py — MCP-specific secret resolution, scoped per server
- Config: mcp/config.py — server discovery, allowlist, version pins
- DynamicDispatcher fails closed on unknown tools — no tool the system doesn't recognize ever executes
Source: src/general_ludd/mcp/ (8 modules), src/general_ludd/execution/tool_loop.py:139, src/general_ludd/dispatch/dynamic_dispatcher.py
Self-improve pipeline — gludd audits itself
Every tick, the EventLoop runs a self-improve phase that audits gludd's own code, proposes fixes, gates them through approval, and tracks outcomes.
graph TD
subgraph SI["src/general_ludd/self_improve/"]
A["harness.py
SelfImproveHarness
gap-finding scan"] --> B["gate.py
SelfImproveGate
auto_queue + promote"]
B --> C["approval.py
ApprovalController
human-in-the-loop gate"]
C --> D["outcomes.py
OutcomeTracker
track success/failure"]
end
subgraph Loop["EventLoop phase (loop.py)"]
E1["self_improve phase"] --> E2["Scan source tree
(lint, typecheck, audit)"]
E2 --> E3["Propose fixes
(model-generated patches)"]
E3 --> E4["Gate approval
(manual or auto)"]
E4 --> E5["Apply approved fix
(hot-reload or commit)"]
E5 --> E6["Track outcome
(test pass/fail, regressions)"]
end
Loop -.->|"reads + mutates"| SI
- Harness: scans for gaps using SAST (semgrep, bandit), typecheck (mypy), lint (ruff)
- Gate: auto_queue=True bypasses approval (P1 item C13 tracks this)
- Approval: human-in-the-loop default; POST /admin/self-improve/run bypasses the gate
- Apply path: currently hardcoded to
make test-unit — generalization to external projects is D3 (P1)
Self-improve has 4 known gate bypasses (item C13). It works, but the approval path needs hardening.
Source: src/general_ludd/self_improve/ (4 modules), src/general_ludd/routers/self_improve.py, src/general_ludd/event_loop/loop.py self_improve phase
Security — built in, not bolted on
gludd has three layers of protection on every action:
- Layer 1 Permissions — hard rules on what gludd is allowed to do
- Layer 2 Runtime guards — 11 plugins that watch every action in real time
- Layer 3 Prompt rules — the AI is instructed to follow safe practices
graph TD
L1["Layer 1 - Permissions
hard rules in config files"] --> L2["Layer 2 - Runtime Guards
11 plugins watch every action"]
L2 --> L3["Layer 3 - Prompt Rules
AI instructed to follow safe practices"]
Security findings: 14 of 15 fixed — one remains open and tracked.
13 BLOCKING enforcement plugins VERIFIED · 99 hook-runtime tests · 14/15 findings fixed · Source: .opencode/plugin/, docs/audit/
Guardrails architecture — 14 plugins, 3 layers
Every agent action passes through three enforceable layers. No single layer is trusted alone.
graph TD
subgraph L1["Layer 1 — Config (hard gate)"]
C1["opencode.json
permission block"]
C2["Make only: deny *,
allow make *"]
end
subgraph L2["Layer 2 — Runtime (real-time hooks)"]
P1["enforce-make.ts
Bash make-only"]
P2["enforce-floor.ts
agent-count floor"]
P3["enforce-stop.ts
premature-stop block"]
P4["enforce-delegate.ts
delegation budget"]
P5["enforce-no-wait.ts
anti-wait CI poll"]
P6["enforce-session-start.ts
dispatch-on-start"]
P7["enforce-verified-claims.ts
evidence-required"]
P8["enforce-clean-tree.ts
dirty-tree dispatch deny"]
P9["+5 more plugins
(13 BLOCKING total)"]
end
subgraph L3["Layer 3 — Prompt (instruction)"]
D1["AGENTS.md
policy sections"]
D2["TDD, commit-after-green,
guardrail integrity..."]
end
L1 --> L2 --> L3
- 13 BLOCKING plugins in .opencode/plugin/, all hot-reload capable, zero advisory-only
- 99 hook-runtime tests across 52 functional tests in 8 plugins (make test-hook-runtime)
- Fail-open on error: a broken hook never blocks legitimate work; the next layer still enforces
- All 13 verified active via make verify-plugin-manifest
Source: .opencode/plugin/ (13 BLOCKING), opencode.json, tests/unit/test_*_plugin*.py
The scale of the system
gludd is a substantial piece of software. Here's what's under the hood:
465+
source files
find src -name '*.py'
38,207
tests collected
make test-count
111
Ansible roles
make collection-roles
36
Ansible modules
collections/
27
DB tables
db/models.py
24
model providers
provider_presets.py
13
BLOCKING plugins
.opencode/plugin/
253
tasks at 99.6%
254 total, ~253 done
18
Terraform stacks
infra/terraform/
HTTP
Terraform backend
S3-compatible remote state
108
secrets e2e tests
tests/e2e/
SearX
model search
Terraform-provisioned
What gludd can do today (1 of 2)
Here's what the agent handles, right now:
- Write code — new features, bug fixes, refactors
- Write tests — unit tests, integration tests, end-to-end tests
- Review code — a separate AI checks every change (stage 8)
- Run quality gates — lint, typecheck, tests, security scans
- Commit to git — with full audit trails (stage 9)
What gludd can do today (2 of 2)
- Manage sprints — backlog grooming, sprint planning, retrospectives
- Security audits — threat modeling, secret scanning, supply chain checks
- Build complete apps — built 12 games from scratch, autonomously
- Track costs — per-project, per-model cost accounting (
spend_records)
- Push notifications — Slack, webhook, and CLI alerts trigger when human action is needed
- Provider smoke tests — low-cost API, model, compute, and multi-platform checks emit JSON evidence for repair agents
- Self-improve — audits its own code and proposes improvements
253 tasks at 99.6% VERIFIED · Source: 254 total tasks, ~253 complete
What gludd can't do yet
Honest gaps — we won't oversell.
- GAP No UI — gludd is command-line only, no graphical interface
- GAP Single-worker — one task at a time, no parallel execution across machines
- GAP SQLite only — no PostgreSQL, no horizontal scaling
- PARTIAL CI not fully green — local gate passes, GitHub Actions still has issues
- PARTIAL 1 security finding open — 14 of 15 fixed, one remains
- PARTIAL 254 tasks at 99.6% — ~1 remaining task
- PARTIAL No IDE integration — works in your terminal, not your editor
We'd rather tell you what's broken than pretend it's perfect.
Every gap here is tracked and being worked on.
How to try it
Three steps to get started:
-
Get the code
git clone https://github.com/sandboxcom/gludd.git
cd gludd
make init
-
Start the agent
uv run gludd daemon --port 8000
-
Submit your first task
uv run gludd todo add "Write a unit test for the login endpoint"
That's it. gludd picks up the task and runs the nine-stage flagship flow shown earlier.
Full guide: README.md · Prerequisites: Python 3.11+, uv, an API key for one model provider
What's next (1 of 2)
Near term:
- Green CI on GitHub Actions (close the local-vs-CI gap)
- Fix the last security finding (14/15 → 15/15)
- Complete Phase F (90% → 100%)
- Live verification of all claimed features
Medium term:
- Visual dashboard (TUI) — see tasks, costs, and progress live
- Greenfield builds — "build me a todo app from scratch"
- Multi-worker support — parallel task execution
- PostgreSQL support — horizontal scaling
What's next (2 of 2)
Long term:
- Fleet mode — multiple agents, shared work queue
- Sandboxed execution — safe running of untrusted code
- Deeper hardening — wider failure-mode coverage
Honest metrics
Every number below is from a machine-produced measurement.
No hardcoded counts — live tool output only.
v0.1.0-beta.2
current version
pyproject.toml
38,207
tests collected
make test-count
465+
source files
find src -name '*.py'
14/15
security findings fixed
docs/audit/
253
tasks at 99.6%
254 total
13 BLOCKING
enforcement plugins
make verify-plugin-manifest
18 stacks
Terraform infra
HTTP backend, SearX search
108
secrets e2e tests
tests/e2e/
These are truth, not aspiration. Every citation is a command you can run yourself.
Updated 2026-07-14.
Links & resources
- Source code
github.com/sandboxcom/gludd
- This presentation (live URL)
sandboxcom.github.io/gludd — deployed by .github/workflows/pages.yml
- This presentation (always-works fallback)
docs/presentation/deck/index.html, open directly or via make deck-serve
- Quick start guide
See README.md in the repo
- Code-path deep-dive
The flagship-flow and schema slides of this deck: the 9-stage flow and 27-table schema
- Security audit
See docs/audit/ in the repo
- Full feature list
Run make gen-status-table after cloning
- License
MIT — free to use, modify, and distribute
Thank you
Questions?
gludd — Autonomous Software Development
v0.1.0-beta.2 · MIT License · built 2026-07-14
github.com/sandboxcom/gludd
sandboxcom.github.io/gludd
Every number in this deck comes from a command you can run yourself.
Every code path was spot-checked against the tree it describes.
Nothing here was hand-authored or fabricated.