Skip to content

Iteration 1 review

Goal: MMDEP runs one task end to end from its own store. A registry agent works on a git branch, a person approves through the inbox without anything blocking, and the merge and every step are in the ledger.

Verdict: met in code and tests, partly met live. The full loop (park → approve → merge, and reject → cancel) passes offline against real git repositories. Live, a real agent (DeepSeek) did the work and the task parked with an approve item and a clean diff. The live approve-and-merge step hasn't happened yet; see "What slipped".

What shipped

All merged to main after an approve item:

Story Item What it gives
Plan #1 docs/iterations/iteration-01.md
1 tasks #4 tasks table (additive), lifecycle from the spec diagram, mmdep task add/list/show
2 registry #5 agents.yaml + adapters (Ollama over HTTP, Gemini, Claude CLI, ChatGPT placeholder, Human, Fake), mmdep agents
3 workspaces #6 MMDEP origin per project, a hook refusing pushes to main, a worktree per task, merge without checkout; spec 0.2
4 isolation #7 Role map (deny by default), worktree-only runner, daily cap from the ledger (Claude 0), tamper guard with rollback, only an item response releases a parked task
5 run-task #9 mmdep run <project>: recovery, release, one slot, fallback DeepSeek → Qwen, approve/review/manual items, merge; UTF-8 CLI; line endings stored as LF
Spec 0.3 #11 "Protecting main from agents" (from #8)
Close-out fix #12 (pending) stdin=DEVNULL for every subprocess, which fixes the multi-minute get_item over MCP

Tests went from 6 + 23 legacy (baseline) to 96 + 23 legacy on the fix branch. legacy/ is unchanged.

What slipped

  • The live approve-and-merge step. The live run parked DeepSeek's change as demo item 1, and you decided to reject it (#10). The rejection isn't in the demo hub or in your hub, so the most likely explanation is that MMDEP_HOME wasn't set in the shell where you ran respond. I didn't record it for you. I re-queued the task once, as you asked, adding one sentence asking DeepSeek not to repeat the docstring. That run took 22 min 44 s (the previous one took 35 s), and its script changed nothing, so the dispatcher marked the task Done with no item. There is therefore no live merge yet. The fix is below: release first, then repeat the demo in your real hub, where the item appears in the normal inbox.
  • Test Runner role. Moved to iteration 2 in the plan, as agreed.

Assumptions

# Assumption Outcome
A1 Repos and worktrees under MMDEP_HOME Confirmed (#1). The folder is mmdep_hub/workspaces (#11; "isolated_workspaces" was older wording)
A2 Parking is state plus item id, no LangGraph Confirmed; nothing so far needs a graph
A3 Merge on the next pass, not in respond() Confirmed; the merge also takes the project slot (refinement, #4)
A4 Text agents return a script that runs in the worktree Holds, but opened a security gap: #8 leads to a separate Windows account in iteration 2
A5 Claude cap = calls per UTC day from the ledger Confirmed; the cap is 0 and mmdep agents shows it
A6 pyyaml as a core dependency Confirmed (#1, #5)

Also from #6: the origin's core.autocrlf=false was replaced by input in #9, after the live run showed CRLF from scripts.

Ledger numbers

From the mmdep project's items and ledger, 2026-09-25 17:28 to 2026-09-26 10:44 UTC:

  • Items raised by claude-code: 12 (8 approve, 3 specify, 1 manual). This review and the release item make 14.
  • Answered: 11 of 11 before #12. None was rejected.
  • Time to response:
  • median 19 min;
  • same-session items: 15–19 min (#1–3, #9–11);
  • 4–6 answered after about 1 h 35 min;

  • 7–8 raised in the evening and answered next morning (14 h).

  • Rework:
  • 3 story 1 tests rewritten in story 4, when the AwaitingHuman rule became strict;
  • 1 approved detail amended (the autocrlf setting from #6, changed in #9);
  • 1 expected merge conflict (cli.py, stories 1 and 2), resolved mechanically;
  • 1 plan scope change (the Qwen fallback, from #2).
  • Live agent calls (demo hub): DeepSeek 3 calls.
  • 63 s: a CRLF diff, which led to the fix.
  • 35 s: a clean diff but a duplicated docstring; you rejected it.
  • 22 min 44 s: no changes.
  • Qwen 0 calls, since DeepSeek never failed outright. Claude 0.

Findings

  1. get_item over MCP took 2+ minutes. git inherited the MCP server's stdin. It's fixed in #12, with a real stdio test that hung before the fix, and it reaches you with the release.
  2. No wall-clock limit on Ollama calls. urllib's timeout only covers idle socket reads, so a slow generation ran for 22 minutes. Agents need a hard total timeout, and the slot timeout doesn't help, because the process itself is stuck in the call.
  3. "No changes" counts as success. For a developer task, an agent that changes nothing has almost certainly failed. It should count as a failed attempt (fall back, then a review item), not Done.
  4. The agent's output isn't kept. For the 22-minute run I can't see what DeepSeek produced. The ledger should keep a digest, and the worktree folder the full text, until cleanup.
  5. The demo outside the inbox was error-prone. Answering in a second hub through environment variables failed silently. Demos should run in the real hub after a release.
  6. Model quality at temperature 0. DeepSeek repeats the module docstring and returns the same answer for the same prompt, so a re-run only helps if the prompt changes. This argues for the backlog's model review (for example, a newer coding model), measured through the ledger.
  7. My own process slip: three times, a patch script turned \n in string literals into real newlines. Each was caught by the tests; from now on I edit files directly.

Proposed changes to the spec and process

These are proposals only. Spec changes go on a branch for approval.

  • Spec, "Task lifecycle": record the rules built in stories 1, 4 and 5:
  • only a response to the linked item releases AwaitingHuman, and parking needs an open item;
  • approval queues a merge step that takes the slot;
  • a crashed slot is recovered after 30 minutes;
  • all agents down → a manual item; 3 failures → a review item. Replace "the graph calls interrupt()" with "parking is a task state until multi-step tasks need a graph".
  • Spec, agents: a hard total timeout per agent call, and "no changes" counts as a failed attempt.
  • Process: a release after every iteration review, so demos and your inbox use the same code. Live demos run only in the real hub.

Proposed iteration 2 (not started)

Goal: agents that run generated code are isolated from your account, the Test Runner role works, and a failed or silent agent can't pass as success. It's the minimum needed before the billing project's first code task.

# Story Size Why
1 Agent account isolation (#8): a low-privilege local Windows account that runs agent scripts and CLI agents, with access only to mmdep_hub/workspaces. It needs git on PATH, HTTP to Ollama on localhost (no file access to models), and its own CLI logins: Claude and Codex logins are per Windows profile, which may use separate seats. Check the provider terms for automated use before sharing a seat. The Gemini key comes from that account's environment. M–L Required before billing
2 Agent hard timeout + output kept (findings 2, 4) S The live run stalled for 22 min
3 "No changes" is a failed attempt (finding 3) S Silent failure counted as Done
4 Test Runner role: run the project's tests in the task worktree, record results, raise a review item on failure. This is the first role allowed on mmdep M Carried from iteration 1
5 mmdep doctor (backlog): Ollama, CLIs, key, git, hub DB, runtime vs main S Builds on health(), as you noted on #5

Moved back to the backlog: depends_on with the dependency check, mmdep status, MCP add_task, help text, and everything else under "Later". Each iteration ends with a release item.

Addendum (2026-09-27): live demo in the real hub, after release #15

This ran on the released runtime, with the setup as claude-code, dispatcher passes as mmdep, and no respond by the agent. - The ledger for sandbox, in order: project.add → task.backlog → task.ready → workspace.origin → workspace.create → task.running → agent.call (deepseek, 54 s) → workspace.commit → item.raise #16 → task.awaiting_human → item.reject (atul) → task.cancelled → workspace.cleanup. - DeepSeek's change was 3 lines inserted inside one(), which is an IndentationError. Atul rejected it: "MMDEP must validate the agent's output, not just its script." That became iteration 2 story 4. - get_item #16 over MCP returned immediately, so release #15 fixed the stdin hang live. - The reject → cancel path now works live. There is still no live merge; iteration 2's done-when covers it.