Submatter/.agents/skills/backlog-sync/SKILL.md
Randall Stillwell 3fc7d01585 feat(worker): OpenHands session bootstrap + 5 Agent Skills
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
2026-09-07 15:10:02 -05:00

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_items or cursor_sync_mappings tables
  • Running or debugging the markdown import (pnpm --filter @tasks/database watch:markdown-backlog)
  • Understanding how agent_prompt inheritance 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, not Feature_X or featureX)
  • 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 kind from filename if frontmatter omits it (Task-*.md → task)
  • Infers slug from filename if frontmatter omits it (strips Plan-/Epic-/Task- prefix)
  • Infers plan_slug from path if frontmatter omits it (plans/Plan-foo/... → foo)
  • Infers epic_slug from path for tasks if frontmatter omits it (Epic-bar/Task-x.md → bar)
  • Computes contentHash (SHA-256 of full file) for idempotency
  • Stores workflowPrompt from agent_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):

  1. Run pnpm --filter @tasks/database watch:markdown-backlog
  2. Scanner walks plans/ for *.md files matching the pattern
  3. Parser extracts frontmatter + body + contentHash
  4. Syncer upserts rows to markdown_backlog_items by (workspace_id, repo_path) unique key
  5. Hierarchical parent_id links 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_case and camelCase in frontmatter keys (parser expects plan_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: global for repo-wide work that's not tenant-specific
  • Run pnpm --filter @tasks/database watch:markdown-backlog after adding/editing files to sync

Common Patterns

Creating a New Task

  1. Navigate to the Epic folder: plans/Plan-<plan>/Epic-<epic>/
  2. Create Task-<slug>.md (lowercase-hyphenated slug)
  3. Copy frontmatter template from docs/templates/task-template.md
  4. Fill required fields: kind: task, slug, title, status: draft, priority, plan_slug, epic_slug, tenant_id, updated_at
  5. Write task body (## Description, ## Acceptance criteria, etc.)
  6. Run pnpm --filter @tasks/database watch:markdown-backlog to import
  7. 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:

  1. Check file structure: ls plans/Plan-<plan>/Epic-<epic>/Task-<slug>.md
  2. Validate frontmatter YAML syntax (common issue: unbalanced quotes, missing colons)
  3. Check watcher logs: pnpm --filter @tasks/database watch:markdown-backlog (look for parse errors)
  4. Query DB: SELECT * FROM markdown_backlog_items WHERE slug = '<slug>' AND workspace_id = '<uuid>'
  5. 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
  • convoy-lifecycle — when backlog tasks map to convoy briefs
  • model-routing — how agent_prompt + model_policy interact
  • code-review-two-axis — reviewing changes to backlog sync code