{"slug": "i-planned-a-multi-agent-coding-system-here-are-the-7-decisions-that-matter", "title": "I Planned a Multi-Agent Coding System. Here Are the 7 Decisions That Matter", "summary": "An engineer at Zyntari, an Israeli engineering studio, has published the technical specification behind a multi-agent coding system in which AI agents take tasks, write code, run tests and open pull requests under human approval. The design keeps task state in PostgreSQL with transitions gated through a single pure function, so agents can only request state changes, and uses Temporal workflows with side effects confined to Activities so crashed runs resume from history. Agent completion claims are treated as requests for verification, validated against a Zod schema and confirmed by tests, reviews and evidence rather than prose.", "body_md": "At Zyntari, my engineering studio in Israel, I am building a system where multiple AI coding agents work on real software projects: taking tasks, writing code, running tests, opening pull requests, and waiting for approvals. Before writing code, I wrote a full technical specification with every architecture decision, every tradeoff, and every thing we deliberately will not build.\n\nA spec is usually private. But the decisions in it apply to anyone trying to run more than one coding agent on anything real, so here are the seven that matter most, why we made them, and how to steal them.\n\nThe short version of the whole philosophy: agents are allowed to reason, but they are not allowed to decide what is true. The system owns the truth. That single rule drives almost everything below.\n\nMost agent systems today are a chat that remembers things. The agent narrates what it is doing, and whatever it says becomes the state of the project. That works until the agent gets confused, contradicts itself, or the process dies mid-sentence.\n\nOur rule: task state lives in PostgreSQL, and agents can only request state changes. A task moves through explicit states (proposed, planned, running, awaiting_review, merging, completed), and transitions happen through one pure function that either returns the new state with the events it emits, or returns a rejection.\n\nNo caller, human or agent, updates state directly in the database.\n\nAn agent finishing its work does not get to say \"done.\" It must return structured output validated against a schema:\n\n``` js\nconst AgentResultSchema = z.object({\n  status: z.enum([\"completed\", \"blocked\", \"failed\", \"needs_review\"]),\n  summary: z.string().max(4000),\n  commitSha: z.string().regex(/^[0-9a-f]{40}$/).optional(),\n  changedFiles: z.array(z.string()).max(500),\n  acceptanceCriteria: z.array(z.object({\n    id: z.string(),\n    status: z.enum([\"passed\", \"failed\", \"not_tested\"]),\n    evidenceRefs: z.array(z.string()),\n  })),\n});\n```\n\nThe important line is the design rule behind it: a model saying status: \"completed\" is not a verdict. It is a request to be verified. Tests, reviews, and evidence decide completion. Prose never can.\n\nAn agent run is not a web request. It can take twenty minutes, wait two days for a human approval, and survive a crashed server. You can hand-roll that, or you can use a workflow engine built for exactly this. We chose Temporal, and Temporal's own documentation describes the same shape we use: the agent loop is a Workflow, each model call and tool call is an Activity, and a crash resumes the conversation instead of restarting it.\n\nThat architecture comes with one strict rule, and it is the rule people break first: workflow code must be deterministic, because Temporal replays it from history after failures. So no LLM calls, database queries, Git commands, or random IDs inside workflow code.\n\nAll side effects live in Activities, which are recorded once and replayed from history. The moment you put a direct API call inside a workflow, recovery stops working and you will not notice until the first crash.\n\nFor long agent runs, Activities heartbeat their progress: which session is running, which phase it is in, and how much it has spent so far. A heartbeat timeout is how you learn that a Claude process or a whole host died, instead of finding out from a silent dashboard.\n\nThe obvious way to automate a coding agent is to run it in a terminal and script the keystrokes. It is also a dead end: no structured events, no reliable cost data, no way to resume a session after a crash.\n\nThe [Claude Agent SDK](//code.claude.com/docs/en/agent-sdk) exposes the same agent loop that powers Claude Code programmatically, in TypeScript and Python: messages, session IDs, tool permissions, hooks, budget limits, and structured output. \n\nEvery run records its session ID before it is allowed to touch anything, so a crashed run can be resumed instead of restarted. Hooks let us enforce policy in the host process before any tool executes, which matters because a policy check does not consume model context and cannot be talked out of by a prompt.\n\nOne detail worth copying even in small projects: model recommendations in tool metadata, things like \"this tool is read-only,\" are hints for scheduling and display, not security controls. Enforce permissions yourself, in code, before execution.\n\nTwo agents should never share a writable checkout. Every implementation task gets its own Git worktree, and the system enforces a conservative write set: the paths the task owns, plus the files those paths implicate, like lockfiles and shared configuration. Tasks with overlapping write sets do not run at the same time. Lockfile changes and database migrations take a repository-wide lock, because those are the changes that break everyone else's in-flight work.\n\nAround the worker process, a boring security profile on Linux: rootless Docker, non-root user, write access only to its own worktree, no Docker socket, a default-deny network with an allowlist for exactly the endpoints it needs, and [a default seccomp profile](//docs.docker.com/engine/security/seccomp/) that is never disabled for convenience. Nothing here is exotic. \n\nIt is the same discipline you would apply to any process that executes machine-generated code, which is what an agent is.\n\nWhen you have many tasks and a few agents, someone has to decide what runs next. The tempting 2026 answer is \"let an agent decide.\" Ours does not.\n\nThe task graph is validated deterministically: every dependency exists, no cycles (Kahn's algorithm, cheap and absolute), every objective maps to an acceptance criterion. Priority is a weighted formula combining business priority, critical path, wait age, and expected cost. Agents may classify inputs, like labeling a task as security-sensitive, but those classifications are constrained enums with a confidence score.\n\nThe final number comes from the formula.\n\nWhy so rigid? Because a scheduler that is a formula can be tested, logged, and debugged. A scheduler that is an LLM judgment call cannot answer the question \"why didn't my urgent task run\" without another LLM judgment call.\n\nBlind retry is how a small problem becomes a large bill. We classify every failure first, and each class gets a different response:\n\n| Failure class | Example | Response | \n|---|---|---|\n| Transient | network reset, provider 5xx | Retry with backoff and jitter | \n| Rate limit | quota hit | Wait until the supplied reset time | \n| Worker loss | crashed process | Resume the session, or start a clean attempt | \n| Validation | malformed output | One repair attempt, then fail | \n| Deterministic | reproducible compile error | Send back for replanning | \n| Policy violation | forbidden command | Never retry. Escalate | \n| Budget reached | cost ceiling hit | Pause and ask for a decision | \n\nBackoff is exponential with full jitter, and automatic retries stop at two. If three attempts failed, the task definition is the problem, not luck.\n\nEvery run reserves predicted cost from its parent budgets before it starts, and reconciles actual cost when it ends. Circuit breakers pause automatic spawning when a project reaches 80% of daily budget, when an attempt costs twice its prediction, or when token burn rate spikes.\n\nI wrote about this pattern for regular applications in a previous article about spending limits; in a multi-agent system it stops being a best practice and becomes load-bearing, because the system can spend money while you sleep by design.\n\nAnd one classic distributed-systems lesson that agent systems rediscover the hard way: fencing tokens. Every lease on a task or worktree carries a number that only goes up. If a worker's lease expires and the task is handed to someone else, late results from the old worker are rejected even if it wakes up and politely reports success.\n\nWithout this, two workers eventually write to the same place, and they will do it while everything looks healthy.\n\nThe spec has a longer section on non-goals than on most features: no Kubernetes, no Kafka, no Redis, no vector database, no learned routing model in version one, and no agents sharing a writable checkout. Temporal already provides durable queues, timers, and retries. PostgreSQL provides transactional state.\n\nEvery additional component is a bottleneck you have not measured yet, dressed as preparation.\n\nThe same discipline applies to agent memory: versioned Markdown files with PostgreSQL full-text indexes, not an embeddings stack, until keyword retrieval demonstrably fails. Code memory is dominated by exact symbols, paths, and commands, where lexical search usually beats semantic similarity anyway.\n\nIf you are building anything in this space, the order matters more than the parts. Build the deterministic skeleton first, state machines and event logs, and prove that killing every process neither loses nor duplicates a task. Then add the agent runtime. Then the operator surface. Only after one real bug travels from intake to a protected-branch merge with zero manual copying do you earn a second department of agents.\n\nAgents reason. The system decides. That is the entire architecture in one sentence.\n\nAlon Trifonov is the founder of Zyntari (zyntari.com), an independent engineering studio in Israel that builds production-grade software and AI systems. He works in Python, TypeScript, and everything that has to keep running after the demo ends. [LinkedIn](https://www.linkedin.com/in/alon-trifonov-546061234) [Github](https://github.com/A-Pex97/)", "url": "https://wpnews.pro/news/i-planned-a-multi-agent-coding-system-here-are-the-7-decisions-that-matter", "canonical_source": "https://dev.to/apex97/i-planned-a-multi-agent-coding-system-here-are-the-7-decisions-that-matter-38kp", "published_at": "2026-10-29 14:44:58+00:00", "updated_at": "2026-10-29 14:49:53.180128+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "mlops", "ai-infrastructure"], "entities": ["Zyntari", "Temporal", "PostgreSQL", "Claude Agent SDK", "Claude Code", "Anthropic"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-planned-a-multi-agent-coding-system-here-are-the-7-decisions-that-matter", "markdown": "https://wpnews.pro/news/i-planned-a-multi-agent-coding-system-here-are-the-7-decisions-that-matter.md", "text": "https://wpnews.pro/news/i-planned-a-multi-agent-coding-system-here-are-the-7-decisions-that-matter.txt", "jsonld": "https://wpnews.pro/news/i-planned-a-multi-agent-coding-system-here-are-the-7-decisions-that-matter.jsonld"}}