Phase 1: Add 5 SKILL.md files to .agents/skills/ - backlog-sync: plans/ markdown ↔ DB contract - convoy-lifecycle: status machine, gates, MCP tools, checkpoints - model-routing: role→model via OmniRoute profiles + registry - code-review-two-axis: Standards vs Spec parallel sub-agent review - tdd-red-green: TDD loop with seams Phase 2: OmniRoute operational docs - docs/omniroute-role-mapping.md: profile→role matrix, convoy examples - docs/omniroute-setup.md: step-by-step operator guide Phase 3: Worker OpenHands integration - apps/worker/src/session/load-skills.ts: loads .agents/skills/ - apps/worker/src/session/openhands-session.ts: agent-server REST adapter - apps/worker/src/executor/index.ts: createOpenHandsExecutor() factory - apps/worker/src/env.ts: WORKER_SESSION_TYPE, AGENT_SERVER_URL, SESSION_API_KEY - apps/worker/src/handle-job.ts: routes to OpenHands when configured - apps/worker/src/session/minimal-session.ts: injects skills into system prompt - packages/shared/src/worker/index.ts: re-export WorkerExecutorOptions
10 KiB
| name | description |
|---|---|
| backlog-sync | Work with the plans/ markdown backlog - understand the strict layout (Plan/Epic/Task hierarchy), parse frontmatter, sync to markdown_backlog_items, and maintain the DB ↔ .md contract. Use when creating or modifying backlog items, understanding the import process, or working with the markdown-backlog package. |
Backlog Sync
The plans/ directory is the source of truth for product work. It's a strict markdown hierarchy parsed into the markdown_backlog_items database table. This skill teaches you the layout rules, frontmatter contract, and how the markdown ↔ DB sync works.
When to Activate
Use this skill when:
- Creating or editing files under
plans/ - Working with
markdown_backlog_itemsorcursor_sync_mappingstables - Running or debugging the markdown import (
pnpm --filter @tasks/database watch:markdown-backlog) - Understanding how
agent_promptinheritance works (plan → epic → task) - Troubleshooting backlog sync issues
- Implementing features that read/write backlog data
Core Rules
1. Directory Structure is Strict
The parser depends on this exact layout. Do not improvise.
plans/
Plan-<slug>/
Plan-<slug>.md ← Plan overview
Epic-<slug>/
Epic-<slug>.md ← Epic definition
Task-<slug>.md ← Executable work item
Task-<another>.md
Key constraints:
- Slugs are lowercase-hyphenated (
feature-x, notFeature_XorfeatureX) - Slugs are stable — renaming breaks Cursor sync mappings
- One task per file
- File names must match the pattern:
Plan-<slug>.md,Epic-<slug>.md,Task-<slug>.md
2. Frontmatter Contract
Every Plan/Epic/Task markdown file must start with YAML frontmatter. The parser extracts these fields:
Required across all kinds:
---
kind: plan | epic | task
slug: matches-the-filename-slug
title: Human-readable title
status: draft | ready | in_progress | blocked | done | cancelled
priority: P0 | P1 | P2 | P3
tenant_id: UUID or "global"
updated_at: "YYYY-MM-DD"
---
Plan-specific additions:
cursor_plan_id: null # populated by Cursor sync
Epic-specific additions:
plan_slug: parent-plan-slug
cursor_epic_id: null
Task-specific additions:
plan_slug: parent-plan-slug
epic_slug: parent-epic-slug
owner: unassigned | github-username
cursor_todo_id: null
agent_prompt: |
Optional multi-line prompt for agents working on this task.
Inherits from epic → plan if not set here.
What the parser does:
- Extracts frontmatter via regex:
/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/ - Parses YAML (tolerates syntax errors → empty object)
- Infers
kindfrom filename if frontmatter omits it (Task-*.md→task) - Infers
slugfrom filename if frontmatter omits it (stripsPlan-/Epic-/Task-prefix) - Infers
plan_slugfrom path if frontmatter omits it (plans/Plan-foo/...→foo) - Infers
epic_slugfrom path for tasks if frontmatter omits it (Epic-bar/Task-x.md→bar) - Computes
contentHash(SHA-256 of full file) for idempotency - Stores
workflowPromptfromagent_prompt:frontmatter field
3. Agent Prompt Inheritance
Tasks can define agent_prompt: in frontmatter. If missing, the system inherits:
Task (no agent_prompt) → Epic (check agent_prompt) → Plan (check agent_prompt) → Default
The resolveWorkflowPrompt function in packages/database/src/markdown-backlog/resolve-prompt.ts implements this cascade. When an agent claims a task via MCP claim_task, the resolved prompt is returned as context.
Default prompt (when nothing is set):
You are an AI assistant helping implement a software task.
Follow the acceptance criteria and constraints in the task description.
Run pnpm lint && pnpm type-check after code changes.
4. Database Sync Flow
Import (Markdown → DB):
- Run
pnpm --filter @tasks/database watch:markdown-backlog - Scanner walks
plans/for*.mdfiles matching the pattern - Parser extracts frontmatter + body + contentHash
- Syncer upserts rows to
markdown_backlog_itemsby(workspace_id, repo_path)unique key - Hierarchical
parent_idlinks are computed (task → epic → plan)
Key columns in markdown_backlog_items:
| Column | Source |
|---|---|
workspace_id |
Env var MARKDOWN_BACKLOG_WORKSPACE_ID |
repo_path |
Relative posix path: plans/Plan-foo/Epic-bar/Task-baz.md |
kind |
Frontmatter kind: or inferred from filename |
slug |
Frontmatter slug: or inferred from filename |
title |
Frontmatter title: or first # Heading or filename |
body |
Markdown body after frontmatter |
content_hash |
SHA-256 of full file |
parent_id |
Computed by matching plan_slug/epic_slug to parent rows |
workflow_prompt |
Frontmatter agent_prompt: (null if not set) |
Export (DB → Markdown):
Not yet fully automated. The exportBacklogItemToMarkdown function can generate markdown from a DB row, but writes are manual or triggered via tRPC backlog.createPlan/createEpic/createTask.
5. Don't Break the Contract
What NOT to do:
- Don't nest tasks under other tasks (only Plan → Epic → Task)
- Don't put secrets or API keys in frontmatter (files are committed)
- Don't hand-edit
cursor_plan_id,cursor_epic_id,cursor_todo_id(Cursor owns these) - Don't rename slugs without understanding the Cursor sync impact (breaks mappings)
- Don't mix
snake_caseandcamelCasein frontmatter keys (parser expectsplan_slug,epic_slug,agent_prompt) - Don't create files outside the
plans/Plan-*/structure (parser ignores them)
What TO do:
- Match existing style (see
docs/templates/plan-template.md,epic-template.md,task-template.md) - Keep tasks bead-scale (one agent session can complete or materially advance)
- Cross-link with relative paths: tasks link to epic via
./Epic-<slug>.md, to plan via../Plan-<slug>.md - Use
tenant_id: globalfor repo-wide work that's not tenant-specific - Run
pnpm --filter @tasks/database watch:markdown-backlogafter adding/editing files to sync
Common Patterns
Creating a New Task
- Navigate to the Epic folder:
plans/Plan-<plan>/Epic-<epic>/ - Create
Task-<slug>.md(lowercase-hyphenated slug) - Copy frontmatter template from
docs/templates/task-template.md - Fill required fields:
kind: task,slug,title,status: draft,priority,plan_slug,epic_slug,tenant_id,updated_at - Write task body (## Description, ## Acceptance criteria, etc.)
- Run
pnpm --filter @tasks/database watch:markdown-backlogto import - Verify row exists:
SELECT * FROM markdown_backlog_items WHERE slug = '<slug>'
Adding an Agent Prompt to a Task
If the task needs custom context for agents:
---
kind: task
slug: implement-feature-x
# ... other required fields ...
agent_prompt: |
This task extends the existing authentication system.
Key constraints:
- Use NextAuth v5 (not v4)
- Follow patterns in apps/web/lib/auth.ts
- Add tests for new session logic
---
When an agent calls MCP claim_task with this task's backlogItemId, the prompt is returned and injected into the session context.
Checking Sync Status
# Watch mode (auto-imports on file change)
pnpm --filter @tasks/database watch:markdown-backlog
# One-shot import
MARKDOWN_BACKLOG_WORKSPACE_ID=<uuid> node packages/database/dist/markdown-backlog-cli.js
What it logs:
- Created: new rows inserted
- Updated: contentHash changed
- Skipped: contentHash matches (idempotent)
- Errors: parse failures, missing parent refs
Understanding the Hierarchy via SQL
-- Get full tree for a plan
WITH RECURSIVE tree AS (
SELECT id, kind, slug, title, parent_id, 0 AS depth
FROM markdown_backlog_items
WHERE kind = 'plan' AND slug = 'automation-foundation'
UNION ALL
SELECT c.id, c.kind, c.slug, c.title, c.parent_id, p.depth + 1
FROM markdown_backlog_items c
JOIN tree p ON c.parent_id = p.id
)
SELECT kind, slug, title, depth FROM tree ORDER BY depth, slug;
Examples
Example 1: Valid Task Frontmatter
---
kind: task
slug: db-convoy-events-migration
title: Drizzle convoy_events table and migration
plan_slug: automation-foundation
epic_slug: convoy-lifecycle-mcp
status: done
priority: P1
tenant_id: global
owner: unassigned
cursor_todo_id: null
updated_at: "2026-08-12"
---
# Task summary
Add Drizzle schema + migration for convoy_events...
Example 2: Task with Agent Prompt
---
kind: task
slug: trpc-backlog-tree
title: tRPC backlog.list / tree / get for markdown_backlog_items
plan_slug: automation-foundation
epic_slug: plans-roadmap-ui
status: ready
priority: P1
tenant_id: global
owner: unassigned
cursor_todo_id: null
updated_at: "2026-08-12"
agent_prompt: |
Extend apps/web backlog router: list/tree/get for markdown_backlog_items.
Always filter by workspace_id. Zod inputs. No Plans UI yet — API only.
Reuse existing schema. claim_task / complete_task; lint + type-check + test.
---
Example 3: Debugging Import Issues
Problem: Task not appearing in DB after markdown edit.
Diagnosis:
- Check file structure:
ls plans/Plan-<plan>/Epic-<epic>/Task-<slug>.md - Validate frontmatter YAML syntax (common issue: unbalanced quotes, missing colons)
- Check watcher logs:
pnpm --filter @tasks/database watch:markdown-backlog(look for parse errors) - Query DB:
SELECT * FROM markdown_backlog_items WHERE slug = '<slug>' AND workspace_id = '<uuid>' - Check
content_hash: if it matches, sync is idempotent (no change detected)
Fix: Correct frontmatter YAML, re-run import, verify row updated.
References
- Parser source:
packages/database/src/markdown-backlog/parse.ts - Sync logic:
packages/database/src/markdown-backlog/sync.ts - Prompt resolution:
packages/database/src/markdown-backlog/resolve-prompt.ts - Templates:
docs/templates/plan-template.md,epic-template.md,task-template.md - Glossary:
docs/Glossary.md(backlog hierarchy, sync vocabulary) - Cursor sync notes:
config/CursorSync.md(push/pull behavior, mappings) - Schema:
packages/database/src/schema/markdown_backlog_items.ts
Related Skills
convoy-lifecycle— when backlog tasks map to convoy briefsmodel-routing— how agent_prompt + model_policy interactcode-review-two-axis— reviewing changes to backlog sync code