Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
313 changes: 313 additions & 0 deletions docs/competitive/qm-2026-08.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
# Competitive analysis: qm (yc-software/qm)

**Date:** 2026-08-01 · **Analyzed revision:** `7f2c916` (repo HEAD at time of analysis)
**Subject:** <https://github.com/yc-software/qm> — "A multiplayer agent harness for work. In Slack and on the web." (MIT)

> **TL;DR** — qm is a serious, unusually mature competitor in exactly omadia's
> category (self-hosted multiplayer org agent, BYO LLM key). It is not a launch
> prototype but visibly operated production code that YC published as a snapshot.
> It is strong where omadia is thin (per-user isolation, code-execution sandbox,
> Slack, credential lifecycle) and weak where omadia is strong (privacy, answer
> verification, deterministic workflows, UI, plugin distribution, EU/GDPR
> positioning). Eight concepts were filed for potential adoption:
> [#575](https://github.com/byte5ai/omadia/issues/575)–[#582](https://github.com/byte5ai/omadia/issues/582)
> (label `from-qm`).

---

## 1. Snapshot

| | |
|---|---|
| Published | 2026-07-29 (three days before this analysis) |
| Traction | ~3,300 stars, ~300 forks, 41 open issues after 3 days |
| License | MIT |
| Size | ~75k LOC source, ~90k LOC tests (`src/` 342 files, `test/` 372 files), plus ~70k LOC CLI/plugins/scripts |
| Stack | Node 24 TypeScript run directly (type-stripping, no build step), Fastify, Postgres (+ pg-boss), Slack via Bolt, web UI via Vite + Lit |
| History | A single squash commit — the public repo is a snapshot of an internally operated system |
| Positioning | "Designed for startups": each employee gets an isolated workspace; collaboration happens in Slack channels, group DMs, and projects |

qm's `package.json` self-description is telling: *"Headless core for the shared
org agent."* The core is headless; Slack, web UI, admin, and portal are plugins.

## 2. Quality assessment

This is the most striking part of the analysis. Signals of genuine production
maturity, rare for a three-day-old repo:

- **Test ratio 1.2 : 1** (90k test LOC vs. 75k source LOC), CI sharded across 5
runners with a shard-plan check, separate Postgres suite, e2e suite, live
Slack suite with screenshot gallery, 8 smoke scripts.
- **Zero `@ts-ignore` / `@ts-expect-error` / `eslint-disable`** in the entire
source tree; 13 comment lines total (their AGENTS.md mandates zero comments —
and it is actually enforced, not just claimed). `strict` +
`noUncheckedIndexedAccess`.
- **Interface-first throughout**: every store exists as an in-memory and a
Postgres implementation behind one interface; harness, sandbox, session
store, and memory are all swappable via one wiring file.
- **Operational scar tissue**: blue-green drain via build-SHA heartbeats, leader
leases with lost-lease handling, DNS-rebinding-aware egress authorization,
shadow-mode screening for classifier cutover, pre-migration
capability-loss computation for sandboxes, fail-open-with-audit patterns.
These are details written after incidents, not before launch.
- **An unusually honest `SECURITY.md`** that concretely names twelve known
limitations (command policy is bypassable, sandbox credentials are plaintext
while in use, audience-floor filtering has gaps, etc.).
- Supply-chain hygiene: npm `min-release-age=7` cooldown; per-user and per-org
spend budgets and rate limits built into the core config surface.

Weaknesses exist but are not structural: a 2,800-line orchestrator god-file, no
schema migration framework (expand-only DDL spread over 23 store files), a
74-method config-store interface with a sync-cache-vs-durable dualism, and a few
RAM-only components that contradict their own "durable by default" rule.

## 3. Architecture

### 3.1 Scopes — the single isolation primitive

Everything hangs off `ScopeId = "<kind>:<ref>"` with
`kind ∈ {personal, channel, group, team, org}`. Every conversation resolves
deterministically to exactly one scope. Per scope: memory, files, keychain
view, permissions, crons, deployed apps, and a durable sandbox. Resources are
"artifacts" with an owner scope; sharing happens exclusively via grants (no
transitive re-sharing).

The standout idea is the **audience floor**: in shared rooms, effective
permissions are the *intersection across everyone present* — egress allowlist =
intersection, denylist = union; a shared file or credential handle only works if
every participant can reach its owner scope; rendered context is filtered per
recipient, with non-entitled tool results replaced by a stub that preserves
tool-call IDs so transcripts stay valid. (Filed as
[#575](https://github.com/byte5ai/omadia/issues/575).)

### 3.2 Harness abstraction

qm runs **Pi, OpenCode, Codex, and Claude Code** against the same core through a
four-bucket `Harness` interface (profile / turns / models / tools). The
**tool surface belongs to the core, not the harness**; adapters only present it
in their transport form, and policy callbacks (screening, approval gating) are
injected per turn. Capability differences are declared, not implicit. Pi is
clearly the first-class path (~4.5k LOC vs. ~1k for the others), and their own
SECURITY.md admits adapter gaps (OpenCode key handling, one path bypassing the
model gateway).

### 3.3 Durable per-scope sandbox

Each scope owns a persistent "computer" where installed tools stay installed;
the agent reaches it through an `execute` tool. Three interchangeable backends:
Fly.io Sprites, AWS Firecracker microVMs, local Docker. Read-only layers (org
files, skills) are materialized by content-hash fingerprint. A per-scope routing
table supports two live backends and migration, with capability-loss computed
before a move. Sandbox egress goes through a CONNECT proxy whose per-sandbox
capability JWT carries the egress policy itself as a claim; the authorizer
re-resolves DNS and re-checks IPs. (Filed as
[#576](https://github.com/byte5ai/omadia/issues/576).)

### 3.4 Security model

Three org-level postures reduced to two orthogonal axes (inbound screening ×
tool approvals): `dangerous` (neither), `auto` (default: screening of
provenance-labelled external content), `strict` (no screening, human approval on
every tool call — screening is redundant when a human gates everything). Scopes
may only tighten against the org floor.

Screening is built on **provenance labelling**: everything that is not direct
human input is bundled into `{source, content}` pairs with typed source tags
(`sender`, `prior-turn:user:<name>`, `overheard:<name>`, `attachment:<name>`,
`tool_result:<tool>`, surface tags), judged either by an LLM with a prompt that
explains each tag's semantics, or by an external screening proxy (chunked,
shadow-mode capable). A `strict` verdict quarantines the turn before it runs;
unscreenable input **fails open with evidence** — an audit event plus a visible
"NOT security-screened" marker in the prompt. (Filed as
[#579](https://github.com/byte5ai/omadia/issues/579).)

The **command policy** is a shell-*normalizing* parser, not naive regex:
quote/ANSI-C unwrapping, recursion into `$(…)`, backticks, pipes-to-shell,
here-strings; an org floor hard-denies recursive deletes, force-pushes,
destructive SQL, fork bombs in every posture including `dangerous`. Their
framing is honest: "a speed bump against mistakes and injection, not a sandbox
boundary." (Filed as [#580](https://github.com/byte5ai/omadia/issues/580).)

### 3.5 Memory

Deliberately minimal: one `MEMORY.md` notebook per scope with a 40-line "memory
line grammar" (dated bullets, normalization as dedupe key, 300-fact cap,
recency-biased recall cap). Postgres variant is an append-only revision log with
history and restore. Recall/capture policy per scope (`off | writable |
visible`). Cross-scope CC mirrors channel facts into the speaker's personal
memory with **trusted-provenance tagging — the model cannot forge provenance**
(model-supplied provenance markers are neutralized on write). Consolidation runs
on an explicit `UPDATE/DELETE/ADD` action grammar.

### 3.6 Background work and operations

Crons (5-field cron or interval, pg-boss with singleton keys, fresh thread per
fire with continuity only via workspace disk + fire log — explained to the model
verbatim), process **monitors/watches** over sandbox process sessions, a
31-line wake router (`engage | steer | drop`), and a run queue with
lease/claim/reap and heartbeat-based worker failover. Deliveries carry
idempotency keys and provenance. Multi-instance coordination via advisory-lock
leader leases; blue-green handover via build-SHA heartbeats that flip the old
instance into drain mode.

## 4. Surfaces and ecosystem

### 4.1 Plugins

Strict two-tier coupling: Slack runs in-process; web UI, admin, portal, and auth
are **separate processes speaking only signed HTTP** to the core (HMAC
source-auth, replay nonces). A 279-line zero-dependency `chassis` package is the
only shared layer. Admin authority lives in exactly one durable store
(`admin_grants`), re-checked everywhere, fail-closed. Magic-link claims and rate
limits are durable in core Postgres so blue-green deploys cannot resurrect a
used link. The public portal is a from-scratch reverse proxy (OIDC + PKCE,
allowlisted headers, no cookie forwarding, path-traversal rejection).

### 4.2 Slack depth

The deepest surface: DMs, channel threads, and group DMs; thread-follow after a
single mention with a turn-detection that can stay silent or react with an
emoji; bidirectional reactions (base36-encoded message ids, full emoji
normalization); **approval flows as Block Kit buttons** (Allow once / session /
always / Deny) wired to exact-command grants; a channel-agent → personal-agent
handoff (`ask-agent` DMs the person for consent, then runs with *their* setup);
out-of-band file blobs (~1 GB) with SSRF refusal for externally hosted files;
guest/external-audience detection with ephemeral-only replies. Identity maps
Slack and web onto the same principal via verified work email, with a
refuse-to-start probe when email identity cannot be established.

### 4.3 Skills

Scope-owned artifacts with lifecycle (`draft → reviewed → published →
archived`), HMAC-signed manifests, scope-ordered resolution with shadowing
(personal beats org), grant-based sharing, **admin-gated promotion** into org
scope (direct creation there is refused), a guard that blocks automations from
editing shared skills, and **skill packs imported from git repos** (pinned or
tracked, trust tiers, SSRF-guarded fetcher, archived-not-deleted removals).
Eighteen seed skills ship in the box; the strongest teach the agent *processes*
(publish-and-verify, interactive login survival, email voice profiles,
broker-mediated credential use) rather than facts. (Filed as
[#577](https://github.com/byte5ai/omadia/issues/577).)

### 4.4 Credentials

Three distinct paths: (a) OAuth connectors for seven providers with per-provider
setup guides, one-time consent links minted by the core (the agent never builds
the URL, tokens never reach the browser); (b) a **keychain** with encrypted
credentials, grants (`once | standing`, audience scope, purpose, expiry,
revocation), "asks" (the agent requests a credential from its owner), and a
**broker** for org-wide service credentials — the agent names credential +
request and the core stamps the secret onto the wire, so the agent never holds
it; (c) resident sandbox logins (`gh`/`glab`/`gcloud` probes, device-flow
credential capture into the keychain and restore onto fresh machines,
credential paths kept out of workspace backups, Playwright storage state per
principal for persistent browser logins, secret-drop browser pages instead of
pasting secrets into chat). (Filed as
[#578](https://github.com/byte5ai/omadia/issues/578).)

### 4.5 Internal web apps

A single core primitive `publish({dir, entrypoint, name})` turns a workspace
directory into a running internal app: immutable versions, stable link, pointer
rollback, `$DATA_DIR`-only durable state, grant-based sharing, a dedicated apps
domain, and an injected edit widget that reopens the authoring UI and prompts on
new versions. The seed skill forbids passing off a file as a running app and
requires curl-verifying before declaring success. (Filed as
[#581](https://github.com/byte5ai/omadia/issues/581).)

### 4.6 Deployment model

A committed, portable **deployment directory** governed by an explicit contract
(`qm.config.jsonc`, `contract: 1`) with the CLI as its only interpreter: one
public URL from which all service URLs derive; secrets *computed* from enabled
services via typed specs and routed to exactly the consuming task; gate order
`check → doctor → build → plan → up → live drift check`; immutable image/config
pins; DB snapshot before AWS deploys with the restore point printed on
rollback. The headline move: **`qm init` materializes a deployment *skill*** so
an agent performs and verifies the deployment ("do not stop at infrastructure
health: complete the acceptance checks"). Orgs customize via private forks
(plain clone, never GitHub fork) where everything org-specific lives under
`deploy/layers/<org>/` and two skills maintain the upstream boundary in both
directions. (Filed as [#582](https://github.com/byte5ai/omadia/issues/582).)

## 5. Comparison with omadia

### 5.1 Shared ground

Same category, near-identical pitch: self-hosted org agent, several people
sharing the same agents in team channels, BYO LLM key, vendor neutrality,
plugins, skills, scheduled background work, memory, audit, budgets, admin
surface. Both projects are notably honest about security limitations in their
public docs.

### 5.2 qm has, omadia lacks

1. **A per-person/per-room isolation primitive.** omadia's multiplayer rides on
channel-native session scopes; there is no personal workspace, no per-scope
keychain view, and no mixed-audience permission semantics. qm's scope +
audience-floor model is the strongest single concept in the repo.
2. **A code-execution sandbox.** omadia has no `execute` path at all — native
tools are a closed set and `ctx.scratch` is a plugin temp dir. qm's durable
per-scope computer is the substrate for half of its feature set.
3. **Slack, deeply.** omadia's README lists Slack among its channels, but Slack
exists in the code only as a type placeholder — while it is qm's main stage,
including approvals, reactions, and agent-to-agent handoffs. Until our Slack
channel ships, this is a direct competitive exposure and the README claim
should be tightened.
4. **Credential lifecycle** (grants, asks, broker, device-flow persistence)
versus our comparatively static vault.

### 5.3 omadia has, qm lacks

1. **Privacy Shield v4** — raw tool results never reach the model, with
per-turn privacy receipts. qm sends everything through (screened, not
minimized); model providers see the data.
2. **Answer verification** — claim extraction, deterministic checks, evidence
judging against the knowledge graph. qm has no counterpart.
3. **Deterministic compute paths** — Office document generation computed
server-side from dataset rows, not hallucinated through the model.
4. **Conductor** — a deterministic workflow engine with role batons, human
steps, deadlines, and fallbacks. qm's crons/monitors are simpler primitives.
5. **A plugin store and remote registries** (hub) with an install UX; qm has no
plugin distribution story beyond the deployment directory.
6. **The canvas UI** (omadia-ui, 24-primitive wire protocol) versus qm's
conventional chat-plus-sidebar web app.
7. **Subscription-based Claude CLI harness** (Pro/Max instead of API keys) and
the EU/GDPR positioning (AVV disclosure, "made in the EU").

### 5.4 Honest mirror

The analysis of qm also sharpened three self-observations, recorded here so we
act on them: our "Slack/Discord" channel claims are ahead of the code (see
5.2.3); "signed plugin distribution" is sha256 + TLS integrity pinning, not
artifact signatures, and our wording should say so; and our plugins run
in-process in the kernel while qm's surfaces are separate signed-HTTP processes
— a real isolation gap to weigh against our install UX.

## 6. Adoption candidates

Filed as issues, label `from-qm`, ordered by architectural urgency:

| Issue | Concept | Why |
|---|---|---|
| [#575](https://github.com/byte5ai/omadia/issues/575) | Scope model + audience floor | Makes the multiplayer promise enforceable; composes with Privacy Shield |
| [#576](https://github.com/byte5ai/omadia/issues/576) | Durable per-scope sandbox + `execute` | Our biggest functional gap; substrate for real work |
| [#577](https://github.com/byte5ai/omadia/issues/577) | Skills as scope-owned artifacts | User/team-authored skills without plugin packaging |
| [#578](https://github.com/byte5ai/omadia/issues/578) | Keychain grants + credential broker | Extends the Privacy Shield philosophy to credentials |
| [#579](https://github.com/byte5ai/omadia/issues/579) | Security postures + provenance screening | Injection-screening tier we currently lack |
| [#580](https://github.com/byte5ai/omadia/issues/580) | Shell-normalizing command policy | Prerequisite for any `execute` tool |
| [#581](https://github.com/byte5ai/omadia/issues/581) | `publish` primitive for internal apps | Durable micro-apps, complementary to the canvas |
| [#582](https://github.com/byte5ai/omadia/issues/582) | Deployment-directory contract, agent-run deploys | Reproducible deployments operated by omadia agents |

#575 and #576 touch architecture decisions that should be settled early; the
rest can be adopted incrementally.

## 7. Method

Analysis performed 2026-08-01 against a fresh shallow clone of
`yc-software/qm@7f2c916` by three parallel code-exploration passes (core
architecture; surfaces/plugins/skills/deployment; omadia feature map as the
comparison baseline), plus direct reading of `README.md`, `SECURITY.md`,
`AGENTS.md`, and repository metadata. Claims about qm cite its source layout as
of that revision; claims about omadia were verified against this repository and
its sibling repos (omadia-ui, omadia-hub) at their then-current state.
Loading