One DAG engine, three ways to run it.
Every node's behavior is composed at three layers: a base plugin system prompt, an ordered stack of user-selected custom-skill modifier blocks injected between that prompt and the incoming content, and an optional LogicSpec reasoning contract from @logic-md/core declaring a strategy, typed I/O, and quality gates.
One codebase runs two execution paths in production plus a third for local development, all dispatching through the same DAG engine so the deployed runtime never diverges from what resolves on a laptop.
Enqueue and return
The web tier enqueues a BullMQ job on Upstash Redis and returns immediately, avoiding serverless execution limits.
No serverless timeout
A Render worker executes the DAG with no serverless timeout and streams events back over Redis to a Vercel SSE endpoint.
No Redis dependency
Locally, the same DAG runs in-process through an inline executor, no Redis dependency required.
Every figure below traces to a file, a command, or a recorded test run.
| Metric | Value | Evidence |
|---|---|---|
| Total commits (main) | 1,084 | git rev-list --count HEAD |
| First commit | 2026-02-24, 8f7315d | git log --reverse |
| Last commit | 2026-08-01, 29ccdb9 | git log -1 |
| Commit authors | 2 emails, 1 person | git log --format=%ae |
| Annotated milestone tags | 9, v1.1 to v1.5.0 | git tag |
| TypeScript / TSX source | 402 files, 67,654 lines | find + wc -l |
| Test files | 58 | find *.test.ts(x) |
| Test cases | 459 across 127 describe() | grep -rEo |
| Test suite result | 492 passed, 2 todo | STATUS.md |
| Specialist plugin directories | 165 | find src/plugins |
| Plugin manifests total | 204 (165 native + 39 dual-exported) | find -name plugin.json |
| Plugins with COVENANT.md | 162 of 165 | comm -23 |
| Plugin sub-packs | 13 | packs.ts |
| Native connectors | 46 | connectors/registry.ts |
| Custom skills | 91 (60 hand-authored + 31 generated) | custom-skills.ts |
| One-click workflow templates | 25 | templates/index.ts |
| REST API route handlers | 34 | find src/app/api |
| DAG runner size | 910 lines | dag-runner.ts |
| Connector executor size | 56 lines, dispatch table only | connector-executor.ts |
| Embedding model | nv-embedqa-e5-v5, 1024-dim, pgvector cosine | embeddings.ts |
| Deploy targets | Vercel, Render free-tier worker, Docker self-host | vercel.json, render.yaml |
| Live URL | singlesource.co.za/m9/ | README.md |
Database row counts are not verifiable from this repository: PostgreSQL lives on Neon, remote, with no local .db or .sqlite file in the tree.
One shared DAG engine, three callers, no parallel logic to maintain.
The canvas (src/lib/canvas/store.ts, 285 lines, Zustand) holds three node types, triggers, specialists, and connectors, plus a knowledge-base node. A specialist node's rendering and per-node settings live in src/components/canvas/nodes/specialist-node.tsx (721 lines). Execution is dispatched through src/lib/execution/dag-runner.ts (910 lines), extracted so the BullMQ worker (src/workers/execution.worker.ts, 568 lines) and the local inline executor (src/lib/execution/inline-executor.ts, 203 lines) both call one code path instead of maintaining parallel logic. The 2026-07-23 changelog entry for that extraction records the worker shrinking from 1,263 to 561 lines and the inline executor from 857 to 197.
All model calls route through src/lib/execution/llm-gateway.ts (294 lines). Model identifiers are centralized in src/lib/execution/default-model.ts (114 lines): resolution is selectedModel ?? user.defaultModel ?? DEFAULT_MODEL, with no other file in the tree hardcoding a model id. Errors are classified by HTTP status, never response text, in src/lib/execution/error-normalizer.ts (222 lines), which reads both the NVIDIA path's status field and the Vercel AI SDK's statusCode field.
Connector dispatch (src/lib/execution/connectors/registry.ts) is a typed Record<ConnectorPluginId, ConnectorExecutor> of 46 entries, a missing entry is a compile error. This replaced a single 5,038-line switch statement in connector-executor.ts, now 56 lines, with the 46 executor bodies moved into 16 domain modules: github, workplace, google-workspace, email, social, web, media, cli, platform, google-apis, seo, and others.
The plugin registry (src/lib/plugins/registry.ts, 516 lines) discovers, Zod-validates, and loads each of the 165 directories under src/plugins/. 39 of those plugins additionally carry a .claude-plugin/plugin.json, a dual export making the same specialist installable as a standalone Claude Code plugin. The author fields on those manifests show 13 authored natively as "Modular9", 7 from Composio, and the rest from named individuals or Anthropic, a mixed build-and-adapt sourcing strategy rather than 165 fully original prompts.
Two monorepo packages implement the standalone LOGIC.md tooling that Modular9 also runs internally: packages/cli (9 commands, 16 .logic.md templates) and packages/mcp (7 MCP tools, stdio and HTTP transport). A third package, packages/modular9-mcp, exposes Modular9's own workflows, templates, packs, plugins, and knowledge bases as 10 MCP tools for external agent clients.
Nine choices with load-bearing consequences.
Fan-in keyed by edge id, not target node id
So N parents converging on one child all survive instead of the last writer overwriting the rest. Called out as load-bearing for the Adversarial Verification and Tournament Bracket templates.
Provider selection is asymmetric by call type
The Composer resolves reliability-first, Anthropic, then OpenAI, then NVIDIA NIM, because one-shot structured-JSON generation is quality-critical and must not stall on a saturated shared endpoint. Utility calls, summaries, prompt improvement, resolve free-first and walk a cross-provider fallback chain instead.
Errors are classified by HTTP status, never sniffed from text
Closes a class of bug where an AI SDK error's statusCode field was missed because only status was read. Fixed in commits f6342d7 and 3b50643.
Connector dispatch moved from a runtime switch to a compile-time registry
5,038 lines to 56, split into 16 domain modules, verified as a pure move: 65 of 69 declarations byte-identical, the 4 differences intentional.
KB and connector node failures share one policy path
recordNodeFailure: connector failures halt the run by default; the knowledge-base node can opt into continuing so downstream specialists are not silently left to answer ungrounded unless the workflow author explicitly allows it.
Webhook signatures moved to timestamped HMAC with a hard cutoff
Legacy body-only signatures are accepted with a deprecation warning until 2026-11-01T00:00:00Z, then rejected with HTTP 403 before signature comparison, closing a replay window instead of leaving an open-ended deprecation.
TypeScript 7.0.2 was attempted and reverted
tsc itself typechecks clean in about 0.6 seconds, but typescript-eslint@8.65 pins a peer range excluding TypeScript 7 and next build 16.2.4 rejects the TS7 package shape. The blocker is upstream, not in this codebase.
Self-host is a first-class deployment path
A full Docker Compose stack, app, worker, postgres with pgvector, redis, caddy, exists for regulated or air-gapped environments, not an afterthought.
Row-Level Security is deliberately not yet implemented
Tenant scoping is enforced in the application layer today, with an automated test suite covering it. The gap is stated rather than obscured.
477 commits in April, the LOGIC.md milestone month.
Project initialization (8f7315d, "docs: initialize project", 2026-02-24) through early build-out.
The heaviest single month and the LOGIC.md milestone sequence: M3 reasoning middleware (v1.1, 2026-04-03), M4 CLI extension (v1.2, 2026-04-03/04), M5 MCP server (v1.3, 2026-04-05/14), M6 Claude Code plugin (v1.4/v1.5.0, 2026-04-05/17).
LLM gateway correctness pass, provider stamping, the statusCode fix, cross-provider fallback chains, inline/worker parity fixes, plus an open-source presentation pass: README rewrite, ARCHITECTURE.md, CONTRIBUTING.md, root SECURITY.md.
Smart Skill Router (91-skill embedding index), connector shell-out hardening (NotebookLM and Claude Code executors moved off /bin/sh -c to argv execFile), a full security and architecture audit (M9_FABLE_REPORT.md) followed by a 9-phase remediation plan, engine extraction into shared dag-runner.ts, and webhook replay protection.
A ranked-risk cycle, P1 through P5, eight commits (350dd57 to 7fc9d17), pinning the toolchain, taking lint and typecheck from decorative to CI-gated (135 warnings and 246 tsc errors to zero), fixing a path-traversal vulnerability in getDocBySlug, splitting the connector executor, and shipping usage metering end to end.
What this page does not cover.
Evidence: compiled from the modular9 repository on 2026-08-03. Every number on this page traces to a file path or command output in the source tree. Last updated: 2026-08-03.