LLMSkillHub
mirroredCopied from Lifecycle-Innovations-Limited/claude-ops under MIT. Lifecycle-Innovations-Limited has not claimed this page, and is not affiliated with LLM SkillHub.

lifecycle-innovations-limited / ops-desk

OPS on-demand: This skill should be used when the user asks to "desk sweep", "open decisions", or…

0
SKILL.md
11.5 KB

OPS ► DESK SWEEP

Load ops-rules before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If AskUserQuestion / Workflow are missing, follow Rule 10 in ops-rules (Hermes: numbered options / two-turn Telegram card; delegate_task).

Goal: everything sitting on the owner's desk — pending decisions, staged drafts awaiting
approval, unpaid invoices to chase, documents to sign, blocked issues needing an owner
action — becomes a ranked queue of ready-to-approve action packages, then gets worked
down one item at a time. The success metric is the same spirit as inbox-zero: **an empty
desk**, with every remaining item either executed, decided, or parked with an explicit
reason and a reminder.

When to use ops-desk vs ops-inbox

  • /ops:ops-inbox — fresh inbound comms (who wrote me, what needs a reply).
  • /ops:ops-desk — the standing backlog (what am I blocking, what decision is waiting on
    me, what draft is staged, what must I sign/pay/approve). Run it after an inbox pass, or
    whenever the owner says "what's left / get this off my desk".

Step 1 — Build the item list (unless passed explicitly in )

  1. Task tracker (SSOT). Whichever the box uses, in order of preference:

    • Paperclip — paperclip issue list -C <company-id> for each company in
      ${CLAUDE_PLUGIN_DATA_DIR}/preferences.json → paperclip.companies[], falling back to
      paperclip company list. Keep status=blocked|todo|in_progress issues whose
      title/description implies an OWNER action — matcher: decision, owner decision,
      approve, sign, pay, chase, staged, awaiting owner, Rule-6.

    • Linear — mcp__linear__list_issues filtered to the owner as assignee, when
      Paperclip is absent.

    • GitHub — gh issue list --assignee @me as last resort.
  2. Inbox residue. Genuine NEEDS_REPLY plus todo/action-labeled emails surviving the
    most recent /ops:ops-inbox pass (run bin/ops-inbox-scan with OIS_NO_REFRESH=1 if
    one already ran this session).

  3. Dedupe against items already handled this session, and collapse duplicate issues
    tracking the same underlying ask (mirror boards, Linear↔Paperclip twins).

  4. Shape each item as {key, refs: [issue identifiers], hint} where hint states the
    concrete question to answer AND what must be verified (never trust the issue title
    alone).

Step 2 — Run the gather workflow (READ-ONLY, batched)

Invoke the Workflow tool with the script below and args = [items]. Hard rules the
script encodes:

  • Agents are READ-ONLY. No sends, archives, payments, signatures, mark-reads, or
    tracker mutations from inside the workflow — they read, verify, and prepare only. All
    execution happens later in the main session under the outbound-approval gate.

  • Batched fan-out (3 at a time). A wide parallel burst can rate-limit (429) the
    model-account pool — pooled CLIProxyAPI setups especially. Batches of 3 finish nearly as fast
    and never starve the rest of the fleet.

  • Verification over trust. Every load-bearing claim in a package must come from an
    actual read (issue body, thread, sqlite row) with dates/ids cited in status_summary.
    An item that turns out already handled comes back as action_type=already_done with
    evidence.

  • Partial failure → resume. If some agents die (rate limits, transient errors), fix
    nothing and relaunch with resumeFromRunId — completed agents replay from cache.

export const meta = {
  name: 'desk-sweep',
  description: 'Fan out read-only context agents over open decision/action items and return ready-to-approve action packages',
  phases: [
    { title: 'Gather', detail: 'one read-only agent per item, batches of 3' },
    { title: 'Synthesize', detail: 'order into an approval queue: quick wins first' },
  ],
}

const ACTION_SCHEMA = {
  type: 'object', additionalProperties: false,
  required: ['key', 'title', 'status_summary', 'recommendation', 'action_type'],
  properties: {
    key: { type: 'string' },
    title: { type: 'string' },
    status_summary: { type: 'string', description: '2-4 sentences: what this is, current state, evidence (dates/ids)' },
    recommendation: { type: 'string', description: 'single concrete next action with an approvable default' },
    action_type: { type: 'string', enum: ['send_email', 'send_whatsapp', 'decision', 'sign', 'pay', 'chase', 'info_only', 'archive', 'already_done'] },
    draft: { type: ['object', 'null'], additionalProperties: false, properties: {
      channel: { type: 'string', enum: ['email', 'whatsapp'] },
      to: { type: 'string' }, cc: { type: 'string' }, subject: { type: 'string' },
      body: { type: 'string', description: "FULL final text in the owner's voice + thread language" },
      reply_to_message_id: { type: 'string' },
    }},
    decision_options: { type: 'array', items: { type: 'string' } },
    blockers: { type: 'array', items: { type: 'string' } },
    effort_minutes: { type: 'number' },
    confidence: { type: 'string', enum: ['high', 'medium', 'low'] },
  },
}

phase('Gather')
const ITEMS = (typeof args === 'string' ? JSON.parse(args) : args) || []
if (!ITEMS.length) throw new Error('desk-sweep needs args: [{key, refs, hint}]')

const CHUNK = 3
const packages = []
for (let i = 0; i < ITEMS.length; i += CHUNK) {
  const batch = ITEMS.slice(i, i + CHUNK)
  const res = (await parallel(batch.map(it => () =>
    agent(
      `You are a READ-ONLY desk-sweep context agent. You MUST NOT send, archive, pay, sign, ` +
      `mark-read, or mutate ANYTHING — no mail sends, no POSTs to send/archive endpoints, no ` +
      `tracker mutations. Read, search, and prepare only.\n\n` +
      `ITEM key="${it.key}": ${it.hint}\nReferences: ${JSON.stringify(it.refs || [])}\n\n` +
      `Gather context read-only, every shell command with an explicit timeout ` +
      `("timeout --signal=TERM --kill-after=5s 30s <cmd>"): the task tracker record(s) for each ` +
      `ref incl. comments; Gmail via gog (search then thread get -j; messages nest under ` +
      `.thread.messages); the WhatsApp bridge store read-only via sqlite3 (merge a person's @lid ` +
      `and @s.whatsapp.net chats; resolve names via contacts); local files if referenced.\n\n` +
      `RULES: verify every load-bearing claim with an actual read; if already handled, return ` +
      `action_type=already_done with evidence. Drafts must be the FULL final text in the owner's ` +
      `voice and the thread's language, no AI tone. Decisions get 2-4 concrete options, ` +
      `recommended first. Your final output MUST be the structured action package.`,
      { label: `gather:${it.key}`, phase: 'Gather', schema: ACTION_SCHEMA }
    )
  ))).filter(Boolean)
  packages.push(...res)
  log(`gathered ${packages.length}/${ITEMS.length}`)
}

phase('Synthesize')
return await agent(
  `You are READ-ONLY. Order these action packages into an approval queue: quick wins first ` +
  `(low effort, high confidence, drafts ready), then decisions, then heavy/blocked. Drop ` +
  `nothing; flag conflicts between packages.\n\n` + JSON.stringify(packages, null, 2) +
  `\n\nReturn {queue: [same packages verbatim + "rank"], conflicts: [], stats: {total, ` +
  `ready_drafts, decisions, already_done}}.`,
  { label: 'synthesize', phase: 'Synthesize',
    schema: { type: 'object', additionalProperties: true, required: ['queue'] } }
)

Step 3 — Work the approval queue (main session, outbound gate)

Per package, in rank order:

  • already_done / info_only → report in one line and close the item in the task
    tracker (status → done). This is tracker-only closure: no mailbox, chat or file is
    archived, moved or marked read. These are the free wins.

  • archive → a package from a read-only agent is a recommendation, not owner
    authorization. Show the item and the evidence, then archive it only after explicit
    owner authorization for that item (e.g. via AskUserQuestion). Without it, leave the
    thread where it is and report the item as still open.

  • send_email / send_whatsapp / chase → exactly as in /ops:ops-inbox and
    ops-rules Rule 6: one draft, one approval, one send, only through the host's
    approved outbound gate. The gate shows the exact recipient and full latest text;
    approval is the owner's native yes bound to those bytes, never a token, counter or
    earlier approval. Re-read the live thread before sending, verify delivery on a
    fresh read, then update the tracker. Archive only under explicit archive
    authorization. If the gate refuses or does not support the channel, keep the item
    owned with the exact diagnostic; never use a direct CLI, bridge or API sender.

  • decision → present the options (recommended first) via AskUserQuestion, record
    the outcome in the tracker, execute any follow-through it implies.

  • sign / pay → prepare only: surface the exact link/document/amount and what to
    check before signing/paying. NEVER sign or move money autonomously (Rule 5).

Step 4 — Wrap up

Report: sent / decided / closed / still-open-with-reason. Schedule reminders
(CronCreate, non-outbound) for anything parked with a follow-up horizon. The task
tracker is the SSOT — every touched item gets its status updated there before the
summary prints.

Agent Teams support

If CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is set, use Agent Teams when fanning out
the gather workflow across independent item sources (Paperclip per company, Linear, GitHub,
Gmail/WhatsApp residue, etc.) in parallel. This enables:

  • Gather agents share context mid-flight (one surfaces a high-priority payment →
    the ranker can prioritize before the desk-sweep plan is emitted)

  • You can steer scope in real time ("skip Linear, focus on Paperclip only")
  • Agents report progress as each gather round completes, so the main session can
    start the approval-queue step earlier on the items already returned

Team setup (only when the flag is enabled, and only for genuinely parallel
sources — a single-source desk sweep stays inline):

TeamCreate("ops-desk")
Agent(team_name="ops-desk", name="paperclip-gather", prompt="Gather owner-action items across all configured Paperclip companies")
Agent(team_name="ops-desk", name="linear-gather", prompt="Gather Linear items assigned to the owner")
Agent(team_name="ops-desk", name="inbox-residue", prompt="Gmail/WhatsApp NEEDS_REPLY + todo-labeled items from the last inbox pass")
Agent(team_name="ops-desk", name="github-chases", prompt="Open PRs awaiting owner review, blocked issues")

After the team reports, the main session runs Step 3 (work the approval queue) on
the merged, ranked list. Each SendMessage from a gather agent is a structured action
package — never an outbound send (Rule 6: per-draft outbound approval gate stays in
the main session, not in the team).

If the flag is NOT set, fall back to standard fire-and-forget Agent subagents (one
per source) — same shape, slower wall-clock because no shared context.