Skip to content

Agent Skills

awaitly ships Agent Skills: procedural instructions that teach a coding agent the awaitly patterns, rules, and APIs. They work with Claude Code, Codex, Cursor, and any other agent the skills CLI supports.

Terminal window
npx skills add jagreehal/awaitly

Or pick one:

Terminal window
npx skills add jagreehal/awaitly --list
npx skills add jagreehal/awaitly --skill awaitly-patterns
Skill Use it for
awaitly-patterns Writing workflows, migrating from try/catch, debugging Result types
awaitly-analyze Static analysis: complexity, path enumeration, IR, diagram generation
awaitly-visualizer Event capture, renderers, collectors, time-travel, export URLs
  • step() forms - when to use direct vs thunk form
  • Error handling - how step() handles early exit, no manual checking
  • Step helpers - step.try(), step.retry(), step.withTimeout()
  • Concurrency - step.all / allAsync() instead of Promise.all()
  • Migration patterns - converting try/catch code to Result types
  • Testing - unwrapOk / unwrapErr assertions

Agents load installed skills automatically and use them contextually when you ask about writing workflows, migrating from try/catch, or debugging Result types. In Claude Code you can also invoke one explicitly:

/awaitly-patterns

R1: step() requires a string ID; use thunk form

Section titled “R1: step() requires a string ID; use thunk form”

step() requires a string ID as the first argument: step('id', fn, opts?).

// Canonical form - deferred, enables retry/caching
await step('getUser', () => deps.getUser(id));

Rule: Use thunk form by default for consistent execution semantics. Every step type takes an ID as the first argument: step(id, fn, opts), step.retry(id, operation, options), step.withTimeout(id, operation, options), step.try(id, fn, opts), step.fromResult(id, fn, opts), step.sleep(id, duration, opts?).

// Correct - step handles errors automatically
const user = await step('getUser', () => deps.getUser(id));
const order = await step('createOrder', () => deps.createOrder(user));
// Wrong - never check result.ok inside workflows
const userResult = await deps.getUser(id);
if (!userResult.ok) return userResult; // Don't do this!
// Correct
const user = await step('getUser', () => deps.getUser(id));
const data = await step.try('fetch', () => fetch(url), { error: 'FETCH_ERROR' });
// Wrong - bare await bypasses error handling
const user = await deps.getUser(id); // Don't do this!
// Correct - allAsync preserves Result types
const [user, posts] = await step('fetchUserData', () => allAsync([
deps.getUser(id),
deps.getPosts(id),
]));
// Wrong - Promise.all loses type information
const [user, posts] = await Promise.all([...]); // Don't do this!

You: Help me migrate this function to awaitly:

async function getOrder(id: string) {
try {
const user = await fetchUser(id);
const order = await createOrder(user);
return order;
} catch (e) {
throw new Error('Order failed');
}
}

Agent (with skill): I’ll migrate this to awaitly patterns…

import { ok, err, type AsyncResult, run } from 'awaitly';
// 1. Convert deps to return Results
async function fetchUser(id: string): AsyncResult<User, 'NOT_FOUND'> {
const user = await db.find(id);
return user ? ok(user) : err('NOT_FOUND');
}
async function createOrder(user: User): AsyncResult<Order, 'ORDER_FAILED'> {
// ...
}
// 2. Use run() with step()
async function getOrder(id: string) {
return run(async ({ step }) => {
const user = await step('fetchUser', () => fetchUser(id));
const order = await step('createOrder', () => createOrder(user));
return order;
});
}

Skills install into your project (.claude/skills/<name>/SKILL.md for Claude Code). Edit them to add project-specific conventions, examples from your codebase, or emphasis on the patterns your team uses most.

Agents tend to:

  • Suggest Promise.all() instead of allAsync()
  • Forget to use thunks for retry/caching
  • Add manual if (!result.ok) checks inside workflows
  • Mix throwing and Result patterns inconsistently

The skills prevent that from the start.