Agent Skills

Write idiomatic Effect v4 TypeScript following official best practices from effect-solutions and the Effect source. Use when writing, reviewing, or refactoring Effect code: services (ServiceMap.Service), layers and dependency injection, error handling (Schema.TaggedErrorClass), data modeling (Schema.Class, branded types, variants), testing (@effect/vitest), HTTP clients (effect/unstable/http), CLI tools (effect/unstable/cli), config, observability, and project setup. Triggers on: 'Effect', 'effe

Install

npx skills add https://github.com/joelhooks/effectts-skills --skill effect-ts
SKILL.md

Effect-TS (v4)

Patterns from effect-solutions and the Effect source. This covers the latest v4 APIs.

Source-First Rule

When working in any repo that uses Effect (effect or @effect/* in package/dependency files), reference the official Effect source before writing, reviewing, or refactoring Effect code. Do not rely on stale memory, blog posts, or high-level docs alone.

  • If the effect_source tool is available, use it for status, hydrate, and search instead of hand-rolled shell commands.
  • First check for a repo-local shallow source mirror at .agent-sources/effect/.
  • If it is missing, create it before doing Effect work: mkdir -p .agent-sources && git clone --depth 1 --filter=blob:none https://github.com/effect-ts/effect.git .agent-sources/effect
  • Keep the mirror out of product commits. If needed, add .agent-sources/ to .git/info/exclude, not the project .gitignore, unless Joel explicitly wants it committed.
  • Search the mirror for current patterns and APIs, especially under packages/effect/src/ and package tests/examples, before calling something an Effect best practice.

Local Source References

  • repo-local Effect source mirror (canonical for current work): .agent-sources/effect/
  • effect-solutions (best practices, docs, examples): ~/Code/kitlangton/effect-solutions/
  • fallback global Effect monorepo: ~/Code/effect-ts/effect/
  • Search source for implementations: grep -r "pattern" .agent-sources/effect/packages/effect/src/

Effect.gen and Effect.fn

Effect.gen provides sequential, readable composition (like async/await for Effect):

import { Effect } from "effect"

const program = Effect.gen(function* () {
  const data = yield* fetchData
  yield* Effect.logInfo(`Processing: ${data}`)
  return yield* processData(data)
})

Effect.fn adds call-site tracing and named spans. Use for all service methods:

const processUser = Effect.fn("processUser")(function* (userId: string) {
  yield* Effect.logInfo(`Processing user ${userId}`)
  const user = yield* getUser(userId)
  return yield* processData(user)
})

// Second argument for cross-cutting concerns (retry, timeout)
const fetchWithRetry = Effect.fn("fetchWithRetry")(
  function* (url: string) {
    const data = yield* fetchData(url)
    return yield* processData(data)
  },
  flow(
    Effect.retry(Schedule.recurs(3)),
    Effect.timeout("5 seconds")
  )
)

ServiceMap.Service

Define services as classes with a unique tag and typed interface:

import { Effect, ServiceMap } from "effect"

class Database extends ServiceMap.Service<
  Database,
  {
    readonly query: (sql: string) => Effect.Effect<unknown[]>
    readonly execute: (sql: string) => Effect.Effect<void>
  }
>()("@app/Database") {}

Implement with Layer.effect or Layer.sync, using Effect.fn for all methods:

import { Effect, Layer } from "effect"

class Users extends ServiceMap.Service<
  Users,
  {
    readonly findById: (id: UserId) => Effect.Effect<User, UserNotFoundError>
    readonly all: () => Effect.Effect<readonly User[]>
  }
>()("@app/Users") {
  static readonly layer = Layer.effect(
    Users,
    Effect.gen(function* () {
      const http = yield* HttpClient.HttpClient
      const findById = Effect.fn("Users.findById")(function* (id: UserId) {
        const response = yield* http.get(`/users/${id}`)
        return yield* HttpClientResponse.schemaBodyJson(User)(response)
      })
      const all = Effect.fn("Users.all")(function* () {
        const response = yield* http.get("/users")
        return yield* HttpClientResponse.schemaBodyJson(Schema.Array(User))(response)
      })
      return { findById, all }
    })
  )
}

Rules:

  • Tag identifiers must be unique. Use @app/ServiceName pattern
  • Service methods should have R = never (dependencies via Layer, not method signatures)
  • Use readonly properties

See references/services-and-layers.md for service-driven development, test layers, layer memoization, and full composition patterns.

Schema.Class and Branded Types

Use Schema.Class for domain records. Brand all entity IDs and domain primitives:

import { Schema } from "effect"

const UserId = Schema.String.pipe(Schema.brand("UserId"))
type UserId = typeof UserId.Type

const Email = Schema.String.pipe(Schema.brand("Email"))
type Email = typeof Email.Type

class User extends Schema.Class("User")({
  id: UserId,
  name: Schema.String,
  email: Email,
  createdAt: Schema.Date,
}) {
  get displayName() { return `${this.name} (${this.email})` }
}

// Construct with makeUnsafe for brands
const userId = UserId.makeUnsafe("user-123")

Use Schema.TaggedClass + Schema.Union for variants (OR types):

import { Match, Schema } from "effect"

class Success extends Schema.TaggedClass("Success")("Success", {
  value: Schema.Number,
}) {}
class Failure extends Schema.TaggedClass("Failure")("Failure", {
  error: Schema.String,
}) {}
const Result = Schema.Union([Success, Failure])
type Result = typeof Result.Type

// Exhaustive pattern matching
const render = (r: Result) => Match.valueTags(r, {
  Success: ({ value }) => `Got: ${value}`,
  Failure: ({ error }) => `Error: ${error}`,
})

See references/data-modeling.md for JSON encoding, Schema.Literals, validation, and full patterns.

Schema.TaggedErrorClass

Define domain errors with Schema.TaggedErrorClass. They are yieldable (no Effect.fail needed):

import { Schema } from "effect"

class UserNotFoundError extends Schema.TaggedErrorClass("UserNotFoundError")(
  "UserNotFoundError",
  { userId: UserId, message: Schema.String }
) {}

// Yieldable: yield directly in generators
const getUser = Effect.fn("getUser")(function* (id: UserId) {
  const user = yield* findUser(id)
  if (!user) yield* new UserNotFoundError({ userId: id, message: "Not found" })
  return user
})

Recover with catchTag / catchTags:

// Single tag
const recovered = program.pipe(
  Effect.catchTag("UserNotFoundError", (e) =>
    Effect.succeed(`User ${e.userId} missing`)
  )
)

// Multiple tags
const recovered2 = program.pipe(
  Effect.catchTags({
    UserNotFoundError: (e) => Effect.succeed("not found"),
    ValidationError: (e) => Effect.succeed("invalid"),
  })
)

See references/error-handling.md for defects, Schema.Defect, and recovery patterns.

Layer Composition

Compose layers with Layer.provideMerge (incremental, flat types) and Layer.merge (parallel):

import { Effect, Layer } from "effect"

// Compose layers for the app
const appLayer = UserService.layer.pipe(
  Layer.provideMerge(DatabaseLayer),
  Layer.provideMerge(LoggerLayer),
  Layer.provideMerge(ConfigLayer),
)

// Provide once at the entry point
const main = program.pipe(Effect.provide(appLayer))
Effect.runPromise(main)

Key rules:

  • Store parameterized layers in constants (layer memoization by reference identity)
  • Provide once at app entry, not scattered throughout code
  • Use Layer.sync for synchronous implementations, Layer.effect for effectful ones

Testing Quick Start

import { describe, expect, it } from "@effect/vitest"
import { Effect, Layer } from "effect"

it.effect("queries database", () =>
  Effect.gen(function* () {
    const db = yield* Database
    const results = yield* db.query("SELECT *")
    expect(results.length).toBe(2)
  }).pipe(Effect.provide(Database.testLayer))
)
  • Use it.effect for Effect-based tests (provides TestContext with TestClock)
  • Use it.live for real time / real clock
  • Provide fresh layers per test to prevent state leakage
  • Use it.layer only when sharing expensive resources across a suite

See references/testing.md for the full worked example and advanced patterns.

Pipe for Instrumentation

const program = fetchData.pipe(
  Effect.timeout("5 seconds"),
  Effect.retry(Schedule.exponential("100 millis").pipe(
    Schedule.compose(Schedule.recurs(3))
  )),
  Effect.tap((data) => Effect.logInfo(`Fetched: ${data}`)),
  Effect.withSpan("fetchData"),
)

Anti-Patterns

Do Not Do Instead
console.log(...) Effect.log(...) with structured data
process.env.KEY Config.string("KEY") or Config.redacted("KEY")
throw new Error() inside Effect.gen yield* new TaggedError({...}) or Effect.fail(...)
Effect.runSync(...) inside services Keep everything effectful
Effect.catchAll(() => ...) losing type info Effect.catchTag / Effect.catchTags
null / undefined in domain types Option<T> with Option.match
Option.getOrThrow(...) Option.match({ onNone, onSome }) or Option.getOrElse
Effect.Service (v3) ServiceMap.Service (v4)
Schema.TaggedError<T>() (v3) Schema.TaggedErrorClass("Tag")("Tag", {...}) (v4)
Scatter Effect.provide calls Provide once at app entry
Call parameterized layer constructors inline Store layers in constants (memoization)

Reference Files

Load these as needed for deeper patterns:

  • Services & Layers: ServiceMap.Service, service-driven development, test layers, layer memoization, provide vs provideMerge
  • Data Modeling: Schema.Class, branded types, variants, Match.valueTags, JSON encoding
  • Schema Decisions: Schema.Class vs Struct vs TaggedClass decision flowchart, migration patterns
  • Error Handling: Schema.TaggedErrorClass, catch/catchTag/catchTags, defects, Schema.Defect, TypeId/refail patterns
  • Testing: @effect/vitest setup, it.effect/it.live/it.layer, TestClock, Effect.flip, FiberRef isolation, worked example
  • HTTP Clients: HttpClient, request building, response decoding, middleware, retries, typed API service
  • CLI: Command.make, Arguments, Flags, subcommands, worked task manager example
  • Config: Config module, schema validation, ConfigProvider, Redacted, config layers
  • Processes & Scopes: Fork types, Scope.extend, Command for child processes, killable background tasks
  • Setup: tsconfig, Effect Language Service, project structure, module settings

Related skills

entra-app-registrationmicrosoft606KGuides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.azure-messagingmicrosoft595KTroubleshoot and resolve issues with Azure Messaging SDKs for Event Hubs and Service Bus. Covers connection failures, authentication errors, message processing issues, and SDK configuration problems. WHEN: event hub SDK error, service bus SDK issue, messaging connection failure, AMQP error, event processor host issue, message lock lost, message lock expired, lock renewal, lock renewal batch, send timeout, receiver disconnected, SDK troubleshooting, azure messaging SDK, event hub consumer, servicentra-agent-idmicrosoft328KProvision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmi_path, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmi_path token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registsupabasesupabase298KUse when doing ANY task involving Supabase. Triggers: Supabase products (Database, Auth, Edge Functions, Realtime, Storage, Vectors, Cron, Queues); client libraries and SSR integrations (supabase-js, @supabase/ssr) in Next.js, React, SvelteKit, Astro, Remix; auth issues (login, logout, sessions, JWT, cookies, getSession, getUser, getClaims, RLS); Supabase CLI or MCP server; schema changes, migrations, declarative schemas, security audits, Postgres extensions (pg_graphql, pg_cron, pg_vector); deb

Search skills and MCP servers

Fuzzy search across 23,137 skills and servers