Skip to content
Back to skills

Implement Service

ASecurity

Create a new Effect-TS service following the mandatory Reactive Agents patterns. Use when adding a service to any package.

  • 28 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
ai-agentstypescriptsqlreact

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tylerjrbuell/reactive-agents-ts --skill implement-service --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Implement Service?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Implement Service
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tylerjrbuell-implement-service/badge)](https://www.skillsdirectory.com/skills/tylerjrbuell-implement-service)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: implement-service
description: Create a new Effect-TS service following the mandatory Reactive Agents patterns. Use when adding a service to any package.
argument-hint: <ServiceName> <package-name>
---

# Implement Service: $ARGUMENTS

## Step 1: Check the spec

Before writing any code, read the spec for the target package to confirm:

- The service name and interface are defined in the spec
- The service's dependencies are identified
- The service's methods and return types are specified

## Step 2: Create the service file

Create the service file at the path specified in the package's spec (usually `src/services/<name>.ts` or `src/<name>.ts`).

Follow this exact template:

```typescript
import { Effect, Context, Layer, Ref } from "effect";
// Import error types from this package
import { MyError } from "../errors.js";
// Import dependency services
import { DependencyService } from "@reactive-agents/some-package";

// ─── Service Tag ─────────────────────────────────────────────────────────────

/**
 * Brief description of what this service does.
 * Which layers/phases use it.
 */
export class ServiceName extends Context.Tag("ServiceName")<
  ServiceName,
  {
    /** Method description */
    readonly methodA: (input: InputType) => Effect.Effect<OutputType, ServiceError>;
    /** Method description */
    readonly methodB: () => Effect.Effect<readonly ResultType[], ServiceError>;
  }
>() {}

// ─── Live Implementation ─────────────────────────────────────────────────────

export const ServiceNameLive = Layer.effect(
  ServiceName,
  Effect.gen(function* () {
    // 1. Resolve dependencies
    const dep = yield* DependencyService;

    // 2. Initialize state (if needed)
    const state = yield* Ref.make(initialState);

    // 3. Return implementation
    return {
      methodA: (input) =>
        Effect.gen(function* () {
          // Implementation using Effect patterns
          const result = yield* dep.someMethod(input);
          yield* Ref.update(state, (s) => /* update state */);
          return result;
        }),

      methodB: () =>
        Ref.get(state).pipe(
          Effect.map((s) => Array.from(s.values())),
        ),
    };
  }),
);
```

## Step 3: Verify patterns

Check every line against these rules:

| Pattern                                     | Required                              | Anti-Pattern          |
| ------------------------------------------- | ------------------------------------- | --------------------- |
| `Context.Tag("Name")`                       | Service tag string matches class name | Different strings     |
| `Layer.effect(Tag, Effect.gen(...))`        | Layer creation                        | `new ServiceClass()`  |
| `yield* DependencyService`                  | Dependency resolution                 | Constructor injection |
| `Ref.make()` / `Ref.get()` / `Ref.update()` | State management                      | `let mutableVar`      |
| `Effect.Effect<T, E>`                       | Return types                          | `Promise<T>`          |
| `Effect.sync(() => ...)`                    | Synchronous operations (bun:sqlite)   | Raw synchronous calls |
| `Effect.tryPromise(...)`                    | Async operations (fetch, file I/O)    | Raw `await`           |
| `Data.TaggedError`                          | Error creation                        | `throw new Error()`   |

## Step 4: Wire into the package layer

Add the service to the package's `createXxxLayer()` factory in `src/runtime.ts`:

```typescript
export const createMyPackageLayer = () =>
  Layer.mergeAll(
    ExistingServiceLive,
    NewServiceLive.pipe(Layer.provide(DependencyServiceLive)),
  );
```

## Step 5: Export from index.ts

Add to `src/index.ts`:

```typescript
export { ServiceName, ServiceNameLive } from "./services/service-name.js";
```

## Step 6: Write tests

Create `tests/service-name.test.ts`:

```typescript
import { Effect, Layer } from "effect";
import { describe, it, expect } from "bun:test";
import { ServiceName, ServiceNameLive } from "../src/services/service-name.js";

describe("ServiceName", () => {
  // Compose test layer with mock dependencies
  const testLayer = ServiceNameLive.pipe(Layer.provide(MockDependencyLive));

  it("should handle methodA correctly", async () => {
    const result = await Effect.gen(function* () {
      const svc = yield* ServiceName;
      return yield* svc.methodA(testInput);
    }).pipe(Effect.provide(testLayer), Effect.runPromise);

    expect(result).toEqual(expectedOutput);
  });

  it("should return tagged error on failure", async () => {
    const result = await Effect.gen(function* () {
      const svc = yield* ServiceName;
      return yield* svc.methodA(badInput);
    }).pipe(
      Effect.provide(testLayer),
      Effect.flip, // Flip to get the error
      Effect.runPromise,
    );

    expect(result._tag).toBe("ServiceError");
  });
});
```

## Common Mistakes

1. **Forgetting `.js` extension in imports** — Bun ESM requires explicit `.js` extensions for relative imports
2. **Using `interface` instead of `Schema.Struct`** for data types passed between services
3. **Wrapping `LLMService.complete()` in `Effect.tryPromise`** — it already returns Effect
4. **Using `let` for state** — always use `Ref`
5. **Missing `readonly` on service method types** — all methods must be `readonly`
6. **Calling `LLMService.complete()` inside `Effect.tryPromise`** — LLMService methods already return `Effect`. Wrapping them in `Effect.tryPromise` creates a double-wrapped Effect that will never resolve correctly.
7. **Not adding kernel-specific services as phases** — if your new service needs to intercept per-turn reasoning, implement it as a `Phase` in the composable kernel rather than a standalone service. See `kernel-extension` skill.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.