Skip to content
← Public packages

@kentcdodds/kody-issue-triage

Loop-safe triage for Kody run errors and fleet package-runtime error-rate elevations. Wakes Cole (Grok Bot) to decide; Cursor agents only when Cole escalates.

AGENTS.md

177 lines · 7.1 KB · Markdown

@kentcdodds/kody-issue-triage — agents

Human overview: README.md. Secrets / wake URLs by name only — never paste wake keys, tokens, or full webhook URLs with credentials. Do not disable live subscriptions or the sweep job.

Secrets / wake storage

Name / storeNotes
grokBotWakeWebhookUrlUser-secret pattern for the minted @kentcdodds/grok-bot wake URL (document the name only)
packageStorage wake URLSet via ./configure-wake-webhook after mint — never log the URL
cursorApiKeyVia @kentcdodds/cursor when Patch escalates (Cursor / platform path)
discordBotTokenKentPersonalAutomationVia @kentcdodds/discord

Import paths

import describe from 'kody:@kentcdodds/kody-issue-triage'
import onRunError from 'kody:@kentcdodds/kody-issue-triage/on-run-error'
import onFleetErrorRate from 'kody:@kentcdodds/kody-issue-triage/on-fleet-error-rate'
import sweep from 'kody:@kentcdodds/kody-issue-triage/sweep'
import getIssueState from 'kody:@kentcdodds/kody-issue-triage/get-issue-state'
import peerContext from 'kody:@kentcdodds/kody-issue-triage/peer-context'
import escalateToKody from 'kody:@kentcdodds/kody-issue-triage/escalate-to-kody'
import recordOutcome from 'kody:@kentcdodds/kody-issue-triage/record-outcome'
import updateCard from 'kody:@kentcdodds/kody-issue-triage/update-card'
import pause from 'kody:@kentcdodds/kody-issue-triage/pause'
import resume from 'kody:@kentcdodds/kody-issue-triage/resume'
import status from 'kody:@kentcdodds/kody-issue-triage/status'
import releaseLease from 'kody:@kentcdodds/kody-issue-triage/release-lease'
import configureWakeWebhook from 'kody:@kentcdodds/kody-issue-triage/configure-wake-webhook'
import probeWakeWebhook from 'kody:@kentcdodds/kody-issue-triage/probe-wake-webhook'

Prefer static kody:@kentcdodds/kody-issue-triage/... imports from execute. Do not lead examples with packages.invoke.

Smoke / dryRun

Operator snapshot (read-only):

import status from 'kody:@kentcdodds/kody-issue-triage/status'

export default async function main() {
	return await status()
	// => { enabled, hourBucket, spawnedThisHour, lease, recent, ... }
}

Sweep without waking Patch / mutating Discord:

import sweep from 'kody:@kentcdodds/kody-issue-triage/sweep'

export default async function main() {
	return await sweep({ dryRun: true, limit: 3 })
}

Patch wake path probe (POSTs dryRun: true through stored wake URL; never returns the URL):

import probeWakeWebhook from 'kody:@kentcdodds/kody-issue-triage/probe-wake-webhook'

export default async function main() {
	return await probeWakeWebhook()
	// => { ok, host, httpStatus, bodyPreview, live: false, ... }
}

Lease release preview:

import releaseLease from 'kody:@kentcdodds/kody-issue-triage/release-lease'

export default async function main() {
	return await releaseLease({ dryRun: true })
}

Configure wake URL after mint (pass URL once; do not echo it back in chat):

import configureWakeWebhook from 'kody:@kentcdodds/kody-issue-triage/configure-wake-webhook'

export default async function main(input: { url: string }) {
	return await configureWakeWebhook({ url: input.url })
}

Progressive Discord card links

Keep the single Discord card updated as work progresses. Call ./update-card at each stage (does not release the lease or close the fingerprint). Keep Activity links; accumulate Agent → PR → Merge → Deploy.

import updateCard from 'kody:@kentcdodds/kody-issue-triage/update-card'

export default async function main() {
	return await updateCard({
		fingerprint: 'REPLACE_FINGERPRINT',
		stage: 'started', // 'started' | 'pr' | 'merged' | 'deployed'
		agentUrl: 'https://cursor.com/agents/REPLACE',
		// prUrl: 'https://github.com/OWNER/REPO/pull/N',
		// mergeCommitUrl: 'https://github.com/OWNER/REPO/commit/SHA',
		// deployUrl: 'https://github.com/OWNER/REPO/actions/runs/ID',
	})
}

Finish with ./record-outcome (still accepts prUrl, plus optional mergeCommitUrl / deployUrl).

record-outcome (agents / Patch finishing work):

import recordOutcome from 'kody:@kentcdodds/kody-issue-triage/record-outcome'

export default async function main() {
	return await recordOutcome({
		fingerprint: 'workflow:skills:timeout-90s',
		outcome: 'recommendation',
		classification: 'kody',
		title: 'Raise the workflow timeout for skills?',
		context: 'Standing 90s timeouts on skills repo.pushed.',
		recommendation: 'Raise the step budget for this family.',
		rightFix: 'Raise the step budget for this family.',
		options: ['Raise the timeout', 'Treat as noise', 'Escalate to a platform ticket'],
	})
}

Edge cases (Patch wake / fingerprints / loops)

  • Handlers only write packageStorage(); they never spawn or post Discord. Sweep (job or event workflow) is the wake path.
  • One Patch wake per fingerprint (surface + owner + normalized family), not per run id. Coarse families collapse TypeError/timeout variants; JSON wrapper instance keys drop; catalog/missing-selector throws collapse to package-caller-validation.
  • Skip: this package, subscription surface, agent MCP execute origins (and package/inline-code runs from execute), smoke-test fingerprints, sibling triage record-outcome validation throws.
  • Platform isolate-reset weather → standing resolved_noise (skills retriever interrupts, 2500ms observer timeouts, dynamic-worker cap, system-email /api/status interrupts). ignored / resolved_noise do not re-queue after 24h.
  • After fixed / recommendation with a kody_agent_id, leftovers increment count but do not re-queue; pre-completed_at leftover runs stay closed.
  • Caps: max 3 agents/hour; anomaly at 15 new fingerprints or 80 raw events/hour. Global lease: one in-flight wake/agent.
  • Dead agents without record-outcomefailed after ~2h stale-spawn window.
  • LIVE peer Cloud agents for the same family stay queued (no second wake).
  • Discord spawn card: persist discord_message_id (edit-existing-first for the same fingerprint) before waking Patch. On wake/spawn failure after the Discord post/edit, edit the card off pure "Waking Patch…" to a retryable queued status when practical — never leave an orphan Queued card with no stored id. ./record-outcome still owns terminal Discord edits.
  • Primary wake target is Patch (bot: 'patch'). Patch owns package/run triage and may escalate to Cole for platform issues. escalate-to-kody / Cursor remains for platform work on kentcdodds/kody when Patch decides — triage agents must not edit Kody themselves.
  • Sentry issue triage stays on @kentcdodds/sentry-triage → Cole; this package covers Kody package/run errors only.
  • Verify helpers with ./smoke-test or { smoke: true } when documented — never dummy ids that create new fingerprints.
  • Kill switch: ./pause / ./resume. ./release-lease only for operator recovery when a spawn died without record-outcome.

Dependency note

Uses @kentcdodds/github, which authenticates via createAuthenticatedFetch on the github-bot / github-kent OAuth integrations (not mirrored github-*AccessToken user secrets).