agentleFS
dork-labsdork-labs/dorkos/AGENTS.md

dorkos

AGENTS.md11 starsChanged 47 days agoMITView on GitHub

DorkOS is a workspace for people and agents, built for founders. Vision (the internal north star, not marketing copy): millions of solopreneurs and founders use DorkOS every day to earn billions of dollars and compete with companies far bigger than them. Mission: the single interface a founder uses to run their whole business: talk to people and agents, and run the outside tools a business needs. The message stack, top to bottom: Read the north-star set in meta/ before you…

  • Reads credentials

The file

We split this AGENTS.md into the rules it gives an agent

2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Marketplace installs: use a file-scoped, git-free transaction (services/marketplace/transaction.ts): stage in tmpdir → backup target → …

    line 140
  2. Very specific:

    If compaction fires mid-/flow, preserve verbatim: the work item id + title, current stage + sub-step, gate state, artifact pointers (spec …

    line 173

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

AGENTS.md

Vision

DorkOS is a workspace for people and agents, built for founders. Vision (the internal north star, not marketing copy): millions of solopreneurs and founders use DorkOS every day to earn billions of dollars and compete with companies far bigger than them. Mission: the single interface a founder uses to run their whole business: talk to people and agents, and run the outside tools a business needs.

The message stack, top to bottom:

  1. You, Multiplied.
  2. Build and run your business with an agent team.
  3. Your agents join your team chat, take on real work, and build the custom tools your company runs on.

Read the north-star set in meta/ before you decide or write anything about what DorkOS is: VISION.md (what and why, who it is for, what sets us apart), PRINCIPLES.md (how we decide: trusted by default, proactive co-workers, ownership, DorkOS first and ready in under a minute, local first, claim only what works, build for the world that is coming), VOICE.md (the words we use and never use, headline rules) and ROADMAP.md (what is built, what is next, and the demo-claim gate for what any public surface may claim). They win over anything else in the repo. How our agents take initiative (proactive, persistent co-workers who wake on a regular beat, act inside their job and report up, while staying quiet in shared rooms) is meta/PROACTIVE-AGENTS.md; read it before designing anything an agent does on its own. The full story is meta/dorkos-litepaper.md; personas are in meta/personas/ (the founder first, Kai the developer second).

Trusted by default has shipped. Agents are trusted co-workers (full power, with an audit trail anyone can read, DOR-2738): PR3 removed the posting, notification and reaction caps on our own agents, and PR4 (DOR-2739) made Trusted the default preset, with a one-time notice for upgrading installs and a one-switch way back to Careful. Turn-counting loop guards are current behavior and stay as they are until DOR-2745 decides their replacement. PR5 (DOR-2739, #2865 and #2878, merged 2026-10-15) finished it: on Trusted, a connected app is shared with every agent, and an agent's irreversible action in an outside account waits 60 seconds, during which anyone but that agent can cancel it. Both first reach users in the release after v0.116.0, so public copy describes them as shipped only once that release is out. Agents run at the level their owner set, whatever the message's origin, including other people in a shared space and outside channels; this is the preview's rule, and separate protections come later (DOR-3443). Code from strangers that DorkOS didn't install (hooks, plugins and packages found in a folder) still needs a yes. Packages you installed through DorkOS follow the agent's level. Extensions an agent makes follow its level: at Full they start with a line in Activity; below Full they wait. The Owner-only front-door settings (login, remote access, credentials, package sources) stay gated. The DorkOS Community Space is not built yet: never claim it works or link to it. "Workspaces" is also the product noun for project checkouts (/workspaces), so write the category in full.

Never write "mission control" or "cockpit" in user-facing prose (site, docs, READMEs, UI copy, error messages, release notes): the operator retired both words, and the category phrase is "a workspace for people and agents" (the 2026-08 "one place" category line is retired too, though plain uses of the words are fine). Say "the DorkOS app", "the app" or "one window" instead. Two carve-outs: GitHub ships a product literally named "Mission Control", and the compiled changelog keeps its historical wording. Identifiers and code comments are out of scope and no sweep is owed for them, but they are where the habit starts, so rename one when you are already editing its file. Enforced by scripts/check-banned-words.sh in the typecheck workflow, which scans this file too. This paragraph is deliberately its own paragraph: the guard matches line by line, so the marker below exempts exactly the prose that has to name the two retired words and nothing else in the file. <!-- vocab-allow: states the prohibition, so it has to name both retired words -->

"Integration", "connector", "adapter" and "provider" are not user-facing nouns — ADR 260804-021140 retired all four and settled on Connections as the single umbrella. ADR 260927-033250 later retired that ADR's own "two named regions" clause: the Connections page is one list of apps, not a Messaging region and an Accounts region — a connected app carries a small "Chat" tag instead when it is one people reach you on. In app copy, error messages, toasts and marketing prose, a Telegram or Slack hookup is a connection; a service an agent acts on is a service under Composio & Nango. The architecture keeps its own names on purpose and is not swept: RelayAdapter, ConnectorProvider, /api/connectors/providers/*, the marketplace package types an author writes (the Adapters and Connectors facets), and the model-provider sense OpenCode uses — that split, with the viewport test that decides it, is the ADR's own table. Enforced by scripts/check-vocab-gate.ts in the typecheck workflow, which parses TypeScript/TSX source so it never fires on an identifier, and separately scans docs/**/*.mdx prose as text (fenced code, inline code and link targets excluded); every legitimate technical use is listed per file, with its reason, in scripts/vocab-gate/allowlist.json. Add a wave to scripts/vocab-gate/banned-terms.json to retire a word; add a scoped, reasoned entry to the allowlist to keep one.

Product state (pre-launch alpha, 2026-10): the web app via CLI install is the primary, launch-critical surface. The macOS desktop app is shipped and verified: signed, notarized, downloadable at dorkos.ai/download/mac, riding the unified vX.Y.Z releases (Apple Silicon), and matured by the desktop-resilience programme. The phone surface is an installable web app with push notifications, reached over the built-in tunnel. A Windows x64 build rides the same release train: an unsigned NSIS installer, downloadable at dorkos.ai/download/windows, but it is an early alpha: built and code-reviewed, not yet confirmed by a real end-user install on Windows, so it stays behind the demo-claim gate (labeled "alpha" in site copy). There is no Linux desktop build yet. The Obsidian plugin is retired; migration and source recovery are documented in docs/guides/obsidian-plugin.mdx. Multi-agent Mesh+Relay coordination now carries end-to-end coverage (rooms e2e specs plus the shared conformance suites) and has come off the gate: Rooms ships GA, and the reply-limit dials that make agent-to-agent traffic safe to leave on landed with it. The marketplace's install path is covered end to end, but its Claude-Code-superset compatibility is still the unverified part of that pillar. In user-facing copy, docs, and release notes, never state that a still-unverified or unbuilt surface works: the Windows desktop alpha, the marketplace's Claude-Code-superset compatibility, or anything on the spaces roadmap (the demo-claim gate in meta/ROADMAP.md). Launch scope (early February 2027, ADR 261015-042933, spec specs/everything-is-a-space/; moved from late January by ADR 261015-125850, which puts this computer and other devices first): web sign-up with the full app on the web, a private space per account, team spaces on DorkOS Cloud and cloud agent computers; none is built yet, so all stay behind the demo-claim gate. The Community Space soft-launches December 8, 2026.

Quality Standard

World-class UI/UX and world-class DX, neither negotiable. Every interaction is crafted; every surface works on mobile, tablet, and desktop. API surfaces are clean, types precise, errors helpful — internals must survive the scrutiny of an architect who reads source code before adopting tools.

Codebase excellence: study existing patterns before writing new code and follow them — consistency is a feature; diverging needs justification. Never leave things incomplete: no lingering TODOs, no half-finished migrations, no dead code, no tolerated legacy patterns — when something is superseded, remove it. Have the courage to refactor even when it's hard. If something weird must stay, comment why; otherwise refactor it. Simplicity is an active pursuit — the codebase gets cleaner over time.

Decision filters: describe what happens for the user, not how the system works internally ("get a Telegram message when your agent finishes"). Every element justifies its existence — if removing it wouldn't hurt the user, remove it. Be honest by design: no dark patterns, no hype language. Would the founder find it valuable, and does it respect Kai's flow? The product feels like a control panel, not a consumer app. (Design mentors — Jobs, Ive, Rams — in meta/archive/website-copy/process.md.) All user-facing prose (changelog, release notes, READMEs, docs guides, UI copy, error messages) follows the writing-for-humans skill: plain enough for a smart 9th grader who doesn't code. Copy the app renders adds the writing-app-copy skill on top: dry and calm, no "we", at most 15 words per block (pnpm check:copy-length). How an agent behaves in a room, DM or channel shared with other people and other agents follows meta/agent-etiquette.md: present, useful, and mostly quiet. Over-participation, not silence, is the failure mode users complain about. How we treat a person who reports a bug or asks for something, in any channel, follows meta/user-care.md: a real reply within one business day, a decision, and a note when it ships. Nobody gets silence. Replies are written with the writing-to-users skill.

DorkOS Cloud

DorkOS Cloud is the hosted, paid layer (see dorkos.ai/pricing); its control plane is closed-source and not in this repo. Everything the app needs to talk to it will be public here, as packages/cloud-api, and the app builds, tests and runs without the other side. Propose contract changes in an issue labelled cloud-contract. Never add prices, plan names, margins or supplier terms to this repo; the pricing page announces them first.

Monorepo Structure

Turborepo monorepo:

dorkos/
├── apps/
│   ├── client/           # @dorkos/client - React 19 SPA (Vite 6, Tailwind 4, shadcn/ui)
│   ├── space/            # @dorkos/community - The space server's package: image manifest, migrations, test tiers (code in server/src/services/spaces, pages in client/src/space)
│   ├── design-system/    # @dorkos/design-system - Standalone shared UI catalog
│   ├── server/           # @dorkos/server - Hono API on Node (tsc, NodeNext)
│   ├── site/             # @dorkos/site - Marketing site & docs (Next.js 16, Fumadocs)
│   ├── desktop/          # @dorkos/desktop - Electron shell
│   └── e2e/              # @dorkos/e2e - Playwright browser tests
├── packages/
│   ├── browser/          # @dorkos/browser - Private managed-browser engine contracts and validation
│   ├── cli/              # dorkos - Publishable npm CLI (esbuild bundle)
│   ├── shared/           # @dorkos/shared - Zod schemas, types, AgentRuntime interface
│   ├── db/               # @dorkos/db - Drizzle ORM schemas (SQLite)
│   ├── relay/            # @dorkos/relay - Inter-agent message bus
│   ├── mesh/             # @dorkos/mesh - Agent discovery & registry
│   ├── harness/          # @dorkos/harness - Projects .agents/ + plugins to every agent harness
│   ├── memory/           # @dorkos/memory - MEMORY.md store behind the MemoryProvider port
│   ├── decisions/        # @dorkos/decisions - DecisionModel bridges (rules, OpenAI-compatible, Jev) + the ladder
│   ├── keep-awake/       # @dorkos/keep-awake - Holds the computer awake while work runs (caffeinate, systemd-inhibit, Windows)
│   ├── a2a-gateway/      # @dorkos/a2a-gateway - A2A protocol gateway
│   ├── connector-providers/ # @dork-labs/connector-providers - Confined external connector SDK adapters
│   ├── extension-api/    # @dorkos/extension-api - Extension author API
│   ├── skills/           # @dorkos/skills - SKILL.md schemas, parser, writer, scanner
│   ├── operating-skills/ # @dorkos/operating-skills - First-party skill pack + version-stamped seeder
│   ├── marketplace/      # @dorkos/marketplace - Package schemas, parser, validator, scaffolder
│   ├── icons/            # @dorkos/icons - SVG icon & logo registry
│   ├── ui/               # @dork-labs/ui - Portable UI primitives and namespaced theme CSS
│   ├── evals/            # @dorkos/evals - Headless outcome-oracle eval harness
│   ├── ci-steward/       # @dorkos/ci-steward - CI Steward engine: census, ledger, collector, verdicts
│   ├── test-utils/       # @dorkos/test-utils - Mock factories, test helpers
│   └── eslint-config/, typescript-config/
├── ci/                   # CI Steward intent: gates, required checks, SLOs, ledger
├── meta/                 # Brand, personas, value architecture, litepaper
├── decisions/            # ADRs                          ├── plans/     # Implementation plans
├── docs/                 # User-facing MDX (Fumadocs)    ├── research/  # 350+ research reports
├── specs/                # Feature specs with manifest.json
└── contributing/         # 48 internal dev guides + capabilities/ contracts (see contributing/INDEX.md)

Commands

pnpm dev               # API server + Vite dev server (loads .env)
pnpm dev:dogfood       # Dev preview (:6241) + the built CLI app (:4242) — the default workflow
pnpm build             # Build all apps
pnpm verify            # Affected-only typecheck + lint + test — the pre-PR loop-closer
pnpm knip              # Dead-code detection (build dists first)
pnpm smoke:docker      # CLI Docker smoke test
pnpm smoke:integration # Full integration test in Docker
pnpm evals:local       # Agent evals against a real model, locally — needs only `claude auth login`
pnpm evals:sweep       # Clear eval sandboxes/containers an interrupted run left behind

Evals (packages/evals, README there): pnpm evals:local runs the core suite on a real model through the claude sign-in on your own machine, so it spends against your own Claude subscription. The real-provider tier is the exception to every sentence in this paragraph: it runs the cross-runtime chat suite through OpenCode on OpenRouter, bills an OpenRouter account rather than a Claude plan, needs DORKOS_EVALS_PAID_PROVIDER=1 beside OPENROUTER_API_KEY, and refuses --isolation docker because eval containers have no network. A --runtime codex leg is the same shape on a different bill: DORKOS_EVALS_PAID_CODEX=1 beside CODEX_API_KEY, and no docker either. Credentials resolve in a fixed order: ANTHROPIC_API_KEY, then CLAUDE_CODE_OAUTH_TOKEN, then your local sign-in; the run prints which one it used. No credential at all is a runner error, never a pass. The docker isolation tier is the exception: its container is sealed off from your home directory, so it needs one of the two variables and says so.

Targeted verification (prefer these — full runs waste minutes):

pnpm vitest run <path>                  # One test file (~1-2s). Works for EVERY package
pnpm test -- --run                      # Full suite via turbo. NEVER bare `pnpm vitest run`
                                        #   for full runs — it skips the per-package env turbo
                                        #   sets up, and has falsely failed tests in dev
pnpm --filter @dorkos/server typecheck  # One package (~4s vs ~28s full)
pnpm --filter @dorkos/server lint       # One package (~4s)

The targeted run only reaches a package the root vitest.config.ts registers as a project, and it lists all of them, packages/evals included. pnpm vitest run <path> answering "No test files found, exiting with code 1" means your path is wrong, never that the package is unreachable (DOR-670, when it meant both). scripts/__tests__/vitest-projects.test.ts fails if that list and the workspace drift apart. A project is named after its directory, so --project client (never --project @dorkos/client) is the filter, and a filter matching no project is a startup error with a non-zero exit — it cannot pass silently (DOR-1822).

Eight paths in the repo spend real money, and each needs its OWN decision beside its OWN key. The shape is always the same — the decision is one thing, the key is the instrument, and a key alone arms nothing, because plenty of people leave one exported and having a key is not the same as deciding to spend. Seven of the decisions are flags. The other, DorkOS credits, is a person's choice in the app's Runs on list (ADR 261001-000811; Codex and OpenCode per ADR 261002-221210): a session runs on credits only when a person picked them, or was told DorkOS picked them on a new link, and refuses rather than running on anything else when it cannot have them. Its environment variable can only switch credits off.

PathFlagKeyWhat it does
packages/evals/src/runner/__tests__/harness-server.test.tsDORKOS_EVALS_CREDENTIALED=1ANTHROPIC_API_KEYboots a server and drives one live turn
apps/server/src/services/runtimes/opencode/__tests__/conformance.test.tsDORKOS_OPENCODE_LIVE_PAID=1OPENROUTER_API_KEYruns the whole OpenCode conformance suite against a real sidecar on OpenRouter (its sibling DORKOS_OPENCODE_LIVE=1 is the FREE local-model smoke and can never arm the paid one)
pnpm evals -- --tier real-providerDORKOS_EVALS_PAID_PROVIDER=1OPENROUTER_API_KEYthe cross-runtime chat suite through OpenCode on OpenRouter, default ceiling $0.50
scripts/harness-smoke/run.sh <claude|codex|opencode>DORKOS_HARNESS_SMOKE=1one of ANTHROPIC_API_KEY (claude), OPENAI_API_KEY (codex), OPENROUTER_API_KEY (opencode)the H tier: a real harness binary reads a projected tree and reports what it found, one turn, --max-usd default $0.50
DorkOS credits as the inference source (apps/server/src/services/core/cloud/credits-inference.ts)none: a Runs on choice, or the Use credits answer for checks; DORKOS_CLOUD_CREDITS=0 switches both offthe cloud-link instance credential (cloud.instanceToken)runs a session set to dorkos-credits on the DorkOS inference endpoint (Claude Code per session; a new Codex conversation in its own credits home; OpenCode as a mode of its whole sidecar, never switched mid-reply; Codex and OpenCode only once the minted token lists their format); one with no live token is refused, and no other session carries it. Second: a beat's cheap check (heartbeat triage, apps/server/src/services/decisions/triage-credits.ts), only after a person answered Use credits (heartbeats.triageOnCredits, ADR 261010-092116), at most 2,000 requests to the endpoint per server per UTC day (a check asked twice counts two, and a restart starts from the beat record), and none for 15 minutes after the endpoint refuses the token
pnpm evals -- --runtime codex (any eval tier)DORKOS_EVALS_PAID_CODEX=1CODEX_API_KEYa Codex eval leg, billed to an OpenAI account: the sandbox pins CODEX_HOME to an empty dir, so a ChatGPT login is invisible and the forwarded key is the only way in
pnpm --filter dorkos test:community-live (packages/cli/scripts/test-community-deploy-live.ts)six DORKOS_COMMUNITY_LIVE_* arms: five =1, plus the exact charge-acknowledgement phraseyour signed-in fly and neonctl profiles (no key variable)installs the published dorkos at an exact version (or, with DORKOS_COMMUNITY_LIVE_PACKAGE_TARBALL instead of the version, an unreleased tarball from pnpm --filter dorkos pack:community-live, recorded in the receipt as not a release), deploys a throwaway Community on Fly, Neon and Tigris through the real launcher, proves owner setup and a second invited member, then deletes it; the optional DORKOS_COMMUNITY_LIVE_HOLD_MINUTES (1 to 45) keeps it running that long first, for an attended run; DORKOS_COMMUNITY_LIVE_BUDGET_USD (at most $25) is recorded in its receipt, not enforced
The Community's post checks (apps/server/src/services/spaces/decisions/settings.ts)COMMUNITY_DECISIONS_PAID=1COMMUNITY_DECISIONS_JEV_KEY (Jev, through OpenRouter unless COMMUNITY_DECISIONS_JEV_BASE_URL says otherwise) and COMMUNITY_DECISIONS_OPENAI_KEY (any OpenAI-format server), each arming only its own modelwatch-only spam and rule checks on each new Community post, run by the server after the post commits: one call per armed model for each post the free built-in rules did not already flag (two for an OpenAI-format server that returns no token probabilities), capped per model per UTC day by COMMUNITY_DECISIONS_DAILY_CAP (default 1000), past which it records "unsure" and calls nothing; without the flag the checks run the built-in rules only

Every one of them reads its flag at MODULE scope, so no other file's vi.stubEnv can blank it (credits read their kill switch the same way, and can only be chosen on a computer linked to a DorkOS account, which no test's temporary data directory is); the Community gate is a standalone executable no test imports, and checks every arm before its first process, profile or network call; the Community's post checks hand theirs to the worker only in the server entry (apps/server/src/services/spaces/main.ts), which no test starts. None of these twenty-one names reaches any task pnpm test, pnpm verify, pre-push or CI runs, and none may: turbo runs strict and strips whatever a task was not told to pass, which is the only reason those commands have never been exposed. There is exactly one deliberate exception — ANTHROPIC_API_KEY on the e2e task, which none of those commands runs — and packages/evals/src/runner/__tests__/paid-provider.test.ts pins it by path so the carve-out cannot grow, while walking the WHOLE parsed turbo.json for the other names (globalEnv, a package-scoped pkg#task override and test:watch expose a variable just as effectively as the three keys an earlier version of that test checked). Bare vitest strips nothing, so on those paths the flag is the whole protection. And a fake key is not a safe way to run the Anthropic one: the test server inherits your claude sign-in and bills that instead, measured with a deliberately invalid key. The harness smoke is the one path that can PROVE which credential paid — Claude Code reports apiKeySource on its session-init message, and a turn served by anything but the named key fails the run.

The gate follows the money, not the tier name, and it answers WHICH bill a run reaches so the refusal names the right key. --tier claude-code-cheap --runtime opencode --model openrouter/… spends on OpenRouter, so it demands DORKOS_EVALS_PAID_PROVIDER exactly as --tier real-provider does; --runtime codex on any tier spends at OpenAI, so it demands DORKOS_EVALS_PAID_CODEX instead (paidPathFor in packages/evals/src/runner/credentials.ts).

Gotchas: under a running pnpm dev/pnpm dev:dogfood, @dorkos/shared rebuilds itself automatically (tsc --watch runs as a persistent turbo dev pane), so a git pull mid-session no longer needs a manual rebuild there. Outside a running dev session — a fresh worktree, a one-off typecheck, or a test run — rebuild @dorkos/shared by hand if imports resolve stale (pnpm --filter @dorkos/shared build); stale dists cause false-red type errors. If a typecheck red starts with TS6053 on a @dorkos/typescript-config extends, node_modules is stale — run pnpm install (tsc otherwise falls back to ES5/non-strict defaults and sprays phantom errors across dependency sources). Ports: dev uses 6xxx (from .env), production defaults 4xxx, tests pin 4242/4241.

Architecture

Hexagonal architecture with a Transport interface (packages/shared/src/transport.ts) decoupling the React client from its backend: HttpTransport (browser, phone, and desktop), with mock transports in tests. See contributing/architecture.md.

Server (apps/server/src/)

Hono on @hono/node-server, on DORKOS_PORT (default 4242, dev 6242). Every request enters one app (http/hono-api.ts, served by http/front-door.ts) and runs one middleware chain (http/api-chain.ts): admission, CORS, the /api host guard, body parsing, the session gate, agent identity, audit; a route group is a new Hono<ApiEnv>() mounted in http/hono-api.ts, reads its body with readJsonBody(c) and its caller with honoRequestFacts(c), and paths match case-sensitively. Express runs only behind the extension bridge (services/extensions/http/express-bridge.ts), and an ESLint rule bans it everywhere else in apps/server/src (ADR 261009-192542). The AgentRuntime interface (packages/shared/src/agent-runtime.ts) abstracts agent backends; production runtimes live under services/runtimes/: claude-code (default), codex (SDK threads, ADR-0309), opencode (managed sidecar, ADR-0308), plus test-mode for e2e and connect/ for runtime credentials/delegated login. Routes resolve a session's runtime via runtimeRegistry (per-session binding, first-write-wins, ADR-0255); session listing aggregates across runtimes with per-runtime degradation (ADR-0310). Every runtime must pass the shared conformance suite (runtimeConformance in @dorkos/test-utils); authoring checklist: contributing/adding-a-runtime.md.

Service domains under services/ — a complete census, not a highlights list, so adding or removing a domain means editing this line and scripts/__tests__/agents-service-census.test.ts fails until you do. The full set, alphabetical: activity, audit, browser, canvas, communities, computers, connectors, core, core-extensions, decisions, diff, docs, extensions, harness, health, heartbeats, identity, marketplace, marketplace-mcp, mcp-apps, memory, mesh, notifications, observability, projects, relay, rooms, runtimes, search, session, shapes, spaces, tasks, terminal, todos, work-queue, workbench-serve, workspace (__tests__ sits beside them and is the one directory that is not a domain). Filesystem scanning: packages/mesh/src/discovery/unified-scanner.ts. API docs at /api/docs.

CommunityAdapter (packages/shared/src/community-adapter.ts) is the fourth swappable seam beside AgentRuntime, Transport and ConnectorProvider — one port for rooms in more than one place, gated by communityConformance. This machine's own SQLite rooms are the first backend behind it (services/communities/local/), registered as LOCAL_COMMUNITY at startup; it wraps RoomService rather than replacing it. GET /api/rooms keeps caller-filtered local rooms outside the single-identity port; any other registry entries contribute warnings, not clickable remote rooms. Native Community participation uses lazily cached, owner-qualified RemoteCommunityAdapter instances outside CommunityRegistry, reached through qualified /api/communities/:ref/rooms/:roomId routes and a dedicated SSE stream. Community credentials stay on the local server. Telegram/Slack bridged rooms are projections into local rooms, not community backends (ADR 260814-024525); the port is reserved for communities whose truth is remote.

Marketplace installs use a file-scoped, git-free transaction (services/marketplace/transaction.ts): stage in tmpdir → backup target → atomic rename → restore on failure. See contributing/marketplace-installs.md and ADR-0304.

Key conventions:

  • lib/dork-home.ts is the single source of truth for the data directory (~/.dork/ prod, apps/server/.temp/.dork/ dev). os.homedir() is banned outside the six carve-outs in Hard Rule 3.
  • lib/resolve-root.ts resolves the default working directory; each app has its own Zod-validated env.ts.
  • Persistent user config: ~/.dork/config.json via conf (services/core/config-manager.ts); Zod is the authoritative schema. Schema changes require a semver-keyed migration — contributing/configuration.md + the adding-config-fields skill.
  • External MCP server at /mcp (Streamable HTTP, stateless) exposes all DorkOS tools, including the 10 marketplace tools. Its auth is fail-closed (middleware/mcp-auth.ts): the per-instance local token gates every mutating call while login is off, per-user keys take over once login is on, and MCP_API_KEY is an optional static override for headless deployments — never the thing that turns auth on.

Sessions

Session storage is runtime-owned (ADR-0310): claude-code derives from SDK JSONL (~/.claude/projects/{slug}/*.jsonl), codex from SDK threads, opencode from its sidecar store — there is no unified DorkOS transcript store. GET /api/sessions aggregates across runtimes, tags each session with its runtime, degrades per runtime (warnings[]). Session locking via X-Client-Id. POST /api/sessions/:id/messages is trigger-only (202); all turn delivery, hydration, and cross-client sync ride the durable per-session SSE stream GET /api/sessions/:id/events (snapshot → gap-free replay via Last-Event-ID → live events with monotonic seq). The global GET /api/events stream fans out session lifecycle events.

Agent Storage (ADR-0043)

.dork/agent.json on disk (source of truth) + SQLite agents table (derived cache); file-first write-through, reconciler syncs every 5 min. DorkBot is the system agent, auto-created at ~/.dork/agents/dorkbot/ by ensureDorkBot(); system agents (isSystem: true) cannot be renamed, deleted, or unregistered — enforced at routes, MCP tools, and client UI.

Message search

One derived, rebuildable FTS5 index over everything that was said, read by GET /api/search and by ⌘⇧F in the app (apps/server/src/services/search/, features/command-palette/ui/MessageSearchDialog.tsx, spec specs/message-search/). It indexes rooms, Claude Code, Codex and OpenCode transcripts (bare-CLI sessions included) — never tool output; the copy is pinned by a test so a coverage claim cannot drift out of date silently. Agents can search and read agent work sessions; DorkOS's agent tools and API keep a person's own chats from agents that identify themselves (an X-DorkOS-Agent caller), not from a caller that omits it or reads the transcript files directly (spec audit-trail §3.4). Deleting the index is a supported recovery.

Client (apps/client/src/)

React 19 + Vite 6 + Tailwind 4 + shadcn/ui (new-york, neutral gray). Feature-Sliced Design with the inviolable layer rule shared ← entities ← features ← widgets (.claude/rules/fsd-layers.md); layers in src/layers/, app shell at src/ root may import any layer. Always import from barrel index.ts, never internal paths. Routing: TanStack Router, thin file-based modules in src/routes/ with routeTree.gen.ts generated by the shared web/desktop Vite plugin; router.tsx provides context and search parsing. Typed navigation uses shared factories (appRoutes, toSession); see contributing/routing.md. Public routes: /, /activity, /team (/agents redirects to it), /session, /threads, /tasks, /channels, /workspaces, /connections, /marketplace, /marketplace/sources, /feedback-requests, /signed-in (the landing page a sign-in's OAuth callback redirects to, outside the app shell), and /x/<extensionId>[/<path>] for pages extensions register (api.registerPage; the x/ prefix keeps them from ever colliding with a core route). /dev/* is the one path the router never sees — main.tsx mounts the Dev Playground on it directly, and only under import.meta.env.DEV. State: Zustand for UI, TanStack Query for server state (contributing/state-management.md). motion for animation, streamdown for markdown; design system in contributing/design-system.md. Portable Button/Input/Field/Notice implementations live in packages/ui behind the client facade; ownership and release rules are in contributing/shared-ui.md.

Site, Shared, CLI

apps/site: Next.js 16 + Fumadocs at dorkos.ai; public marketplace browse + install telemetry (Neon Postgres + Drizzle). packages/shared: import via @dorkos/shared/* subpaths — see the exports map in packages/shared/package.json (161 subpaths, no root entry). packages/cli: published as dorkos; config precedence CLI flags > env vars > ~/.dork/config.json > defaults.

The /flow Workflow

/flow is the PM-agnostic workflow engine (stage spine CAPTURE → … → DONE) — it lives entirely in the external marketplace plugin (dork-labs/marketplace, plugins/flow/; ADR-0297). Install it from the DorkOS Marketplace at project scope: Harness Sync projects it into .claude/commands/flow/ + .claude/skills/flow__* (ADR 260706-192819), so /flow:* works in DorkOS sessions and the bare claude CLI alike (claude --plugin-dir <marketplace-checkout>/plugins/flow also works for a one-off). There is no in-repo fallback; without the plugin installed, /flow:* does not exist. All tracker I/O goes through its linear-adapter skill; Linear team key DOR. Reach Linear via Linear MCP or composio execute LINEAR_* --account personal — never the artblocks work account. Reference: contributing/flow-engine.md.

If compaction fires mid-/flow, preserve verbatim: the work item id + title, current stage + sub-step, gate state, artifact pointers (spec dir, worktree path + branch, flow-state.json, open PR), and the assumption trail. Filesystem + tracker are ground truth; recover the rest from flow-state.json and the tracker.

Worktrees

One checkout, one writer. This repo is routinely multi-agent; main is the clean integration tree, not a shared scratchpad — two agents mutating one checkout corrupt each other. Default to an isolated worktree for any code change. Stay in main only when you are certainly the sole writer and the work is non-code (research/, specs/, tracker, docs prose) or a single commit landed immediately. Never create a worktree from inside one; never auto-remove one with uncommitted or unpushed work. Mechanics: working-in-worktrees skill + /worktree:create|list|remove. Intent stages (ideate/specify/decompose, specs/ markdown) stay in main; isolation begins at EXECUTE.

One ref namespace, many readers. That rule covers writers; the reader half is that a worktree isolates the working tree but not origin/* or the object store, which live in the shared common git dir. Another session's git fetch advances origin/main between two commands of your own reasoning, every command still exits 0, and the only symptom is a conclusion that does not match the code. Pin the base once — BASE=$(git rev-parse origin/main) — and compare against $BASE for the whole investigation; never name origin/main twice. Prefer git merge-base "$BASE" HEAD, and treat a surprising file list as evidence the ref moved rather than as data.

Pull Requests

Open PRs from a worktree branch based on origin/main. Direct pushes to main are rejected by repository rules — everything lands via PR + merge queue, spec/docs commits included (docs-only PRs: skip-changelog + review:light). The automated Claude review runs on-demand: full review on open/ready-for-review, re-review via the re-review label (auto-cleared). Control intensity with skip-review, review:light, review:deep. Flow and label semantics: creating-pull-requests skill; the reviewer rubric is REVIEW.md.

One topic, one PR. If an open PR touches the same files or must land first, stack on it with gh stack instead of branching from origin/main; merge a stack only with gh stack merge from its top PR, never gh pr merge --auto on a layer whose base is not main (that skips the queue). UI PRs run their affected Playwright specs locally before pushing and say so.

Hard Rules

Non-negotiable, enforced by ESLint/CI/convention. Rules 1-4 are ESLint errors, so they fail the lefthook pre-commit hook, pnpm verify, and the required lint check in the merge queue. Rule 5 also rides lint; 6-7 are hooks; 9 rides the required typecheck check:

  1. FSD layer violations are errors — no-restricted-imports enforces the hierarchy
  2. SDK imports confined — each runtime SDK is banned outside its adapter dir: @anthropic-ai/claude-agent-sdk → services/runtimes/claude-code/, @openai/codex-sdk → services/runtimes/codex/, @opencode-ai/sdk → services/runtimes/opencode/
  3. os.homedir() banned in apps/server/src — use lib/dork-home.ts. Seven carve-outs beyond tests, enumerated in .claude/rules/dork-home.md: lib/dork-home.ts, lib/boundary.ts (two inline-disabled call sites), claude-code/claude-config-dir.ts, codex/codex-home.ts, opencode/opencode-data-dir.ts, harness/agents-user-home.ts, browser/browser-user-home.ts — the last five mirror another program's resolution of its own directory 1:1, never DorkOS's (the sixth mirrors five vendors at once: $HOME/.agents/skills; the seventh reads Chrome, Edge and Brave metadata only on a person’s tap)
  4. TSDoc on exports — enforced by eslint-plugin-jsdoc. Every jsdoc rule is error (DOR-627); they were all warn until then, and since every package's lint script is a bare eslint ., which exits 0 on warnings, this rule used to fail nothing anywhere. A missing block description counts: @param/@returns/@internal tags alone are not a TSDoc comment
  5. Prettier + Tailwind class sorting are automatic — never hand-sort. Formatting runs at turn end (Stop hook) and again at pre-commit, deliberately not after each edit: rewriting a file the moment you edit it makes your in-context copy stale and breaks your next string-replace (.claude/hooks/format-changed.sh header). So a file you just wrote is not formatted yet, and that is fine — do not chase it. pre-push then checks the changed set rather than rewriting it (a hook that edits files mid-push leaves the tree disagreeing with what just went out), and refuses the push naming the files and the exact prettier --write command — DOR-1839, after seven PRs in one week went red on CI's formatting gate
  6. git stash and git checkout -- <path> are refused — the stash is shared by every worktree and holds your auto-checkpoints; the pathspec checkout silently reverts uncommitted work. Both have eaten work here. Park files in the session scratchpad and restore with cp. Enforced by .claude/hooks/git-guard.mjs via run-node-hook.sh, which fails closed when node is missing; git stash list/show and branch switching still work
  7. pkill, killall, and group/all-process kill are refused — several agents plus the operator's own pnpm dev (:6242) and dogfood app (:4242) run the same source on one machine, so a kill by name is a kill of everyone's process (a broad pkill -f took the operator's dev server down on 2026-08-18). Stop only the process you started, by the PID you already hold or lsof -ti :<your port>. Enforced by .claude/hooks/process-guard.mjs via run-node-hook.sh, which fails closed when node is missing. Both guards run as .claude/hooks/run-node-hook.sh <guard>.mjs: a bare node exits 127 wherever node is off the launching PATH, and 127 is a non-blocking error, so the rule was enforced by nothing there (DOR-2121). They read command TEXT, so a line that merely names a refused command is refused too — a deliberately coarse trade
  8. Path-specific rules in .claude/rules/ load when editing matching files (see .claude/README.md)
  9. No file over 500 lines may grow — max-lines (500, blank lines and comments skipped) is only a warn, so scripts/check-max-lines.ts enforces it as a ratchet in typecheck (DOR-2822). Every file over the limit is in scripts/max-lines/baseline.json: it may shrink, never grow, and a new file may not pass 500 (tests are exempt). After shrinking or moving one, run pnpm check:max-lines -- --update, which only lowers or removes entries; never raise a number by hand (CI compares against the base and refuses it). Split the file instead

Testing

Vitest with vi.mock(); tests in __tests__/ alongside source. Client tests: React Testing Library + jsdom with mock Transport via TransportProvider. Server session-route tests: FakeAgentRuntime + scenarios from @dorkos/test-utils; SSE integration via collectDurableEvents. Patterns and anti-patterns: .claude/rules/testing.md. Single file: pnpm vitest run <path>; full runs: pnpm test -- --run (see Commands for the bare-vitest gotcha). Local evidence, throwaway DORK_HOMEs and Playwright output worth keeping go under /Volumes/ext-ssd-1/dork-os/ when that drive is mounted (contributing/browser-verification.md#where-local-output-goes).

CI

The full guide, written for agents, is contributing/ci.md: the two gates (affected-only locally and on PRs; the full suites and the Playwright shards only in the merge queue), sharding, assert-tests-executed, site-build, capacity, and CI Steward. The must-knows:

  • Every pipeline change carries a ledger entry with a hypothesis. One file in ci/ledger/ (scaffold: node packages/ci-steward/src/cli.ts ledger-new --slug <slug>) naming one metric, its baseline, a target and a date; code, not a model, later computes whether it worked (/ci-status shows the verdicts, the SLOs and the one constraint to work on, from the ci-steward-data branch). Editing a workflow, lefthook.yml, turbo.json, .claude/settings.json, a gate script or ci/** loads .claude/rules/ci-pipeline.md with the protocol; the stewarding-ci-pipeline skill carries it for every harness. Ratchet releases block review by default.
  • main merges through a merge queue (ADR 260728-112203), and merges are fully autonomous: no human approval, ever (ADR 260919-174348). Never update a branch to satisfy a gate: the queue tests your PR on top of main plus everything ahead of it, and being behind blocks nothing.
  • Every required check comes from ruleset 19893973 (classic branch protection is gone); the list in contributing/ci.md is generated from ci/required-checks.json, so never restate the count. Every required check must report on pull_request and merge_group, with no paths: filter and no job-level if: that can skip it, except no-fragment-under-skip-label, which skips on merge_group by design and is allowlisted in ci/census-allowlist.yaml; the CI Steward census step in typecheck enforces it.
  • merge-tail.yml arms finished PRs as a backstop, roughly every 2-3 hours (GitHub throttles its */10 schedule; decision: scripts/should-arm-automerge.sh), so arm your own green PR yourself. Apply hold (or do-not-merge, wip, blocked) to keep a green PR from being armed. Arm your own with gh pr merge --auto <n>; an admin merge is never the answer (.claude/hooks/merge-guard.mjs refuses it).
  • A red is not a reason to add load (Team plan: 60 concurrent jobs, 19-25 per push). First failed-checks ejection from the queue: don't push or rerun; read the failing job, and once it is plainly not yours re-arm once with gh pr merge --auto <n> (85% re-pass unchanged). The same check failing again on the same head is a regression only the queue can see (browser tests run only there): stop re-arming, read the log, push a fix. A PR check that is not yours: gh run rerun <run-id> --failed, once. Never an empty commit.

Signing outward writes (agent provenance)

Anything you post to an external tracker or forge — a Linear comment or description, a GitHub PR body or comment — ends with one hidden, machine-readable line so a later session can route a follow-up back to yours:

<!-- agent:provenance {"v":1,"harness":"claude-code","sessionId":"…","account":"…","host":"…","surface":"…"} -->

Emit only fields you actually know (omitted is a fact; invented is a lie); the value must be valid JSON or the line is omitted entirely. Running under DorkOS, also emit instanceId and resumeUrl; in a public repo, omit resumeUrl and truncate sessionId to 8 chars. account is a short non-PII handle (for Claude Code: the CLAUDE_CONFIG_DIR basename, default claude) — never an email, and never the tracker account a getCurrentUser call returns; if the derived value contains @, omit it. Linear stores the line byte-for-byte in descriptions and comment bodies (API round-trip verified 2026-09-11); GitHub's preservation of HTML comments is long-established convention. Readers also accept the legacy flow:provenance name on old bodies. The canonical spec, full field table, and reply-routing rules: docs/provenance.md in dork-labs/marketplace (merged 3191607; readable without the flow plugin installed). Git commits keep their existing Claude-Session: trailer — the same idea in commit form.

Research

350+ reports in research/ (YYYYMMDD_topic-slug.md). Always check research/ before doing new research.

Artifacts

  • Identifiers: new ADRs and specs use timestamp ids YYMMDD-HHMMSS from .claude/scripts/id.ts (coordination-free; ADR-0312). Legacy 4-digit numbers are frozen and sort first. There is no nextNumber.
  • ADRs: decisions/<id>-<slug>.md + manifest.json. /adr:create, /adr:from-spec (applies the significance rubric at extraction — no draft state), /adr:review, /adr:list.
  • Specs: specs/<slug>/ with manifest.json (01-ideation.md, 02-specification.md, optional 03-tasks.json). Plans: plans/ at repo root.
  • Audits: one committed charter per domain in audits/<domain>.md (shared preamble in audits/README.md), with run logs and per-lens diff stamps under audits/runs/<domain>/; the UI domain is driven by /ui-audit:init|run|execute|pulse|measure and the auditing-ui skill, and judged against contributing/design-review.md.
  • Changelog: per-change fragments in changelog/unreleased/ (<id>-<slug>.md, timestamp-id + slug), compiled into CHANGELOG.md at release; never edit CHANGELOG.md directly (ADR 260707-231641, changelog/README.md).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.