Implement Service: $ARGUMENTS
SkillAI & modelsCreate a new Effect-TS service following the mandatory Reactive Agents patterns. Use when adding a service to any package.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Implement Service: $ARGUMENTS skill
What this skill tells your AI
The instructions your AI receives, as published by tylerjrbuell/reactive-agents-ts in .agents/skills/implement-service/SKILL.md and read by ahel’s review.
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:
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:
export const createMyPackageLayer = () =>
Layer.mergeAll(
ExistingServiceLive,
NewServiceLive.pipe(Layer.provide(DependencyServiceLive)),
);
Step 5: Export from index.ts
Add to src/index.ts:
export { ServiceName, ServiceNameLive } from "./services/service-name.js";
Step 6: Write tests
Create tests/service-name.test.ts:
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
- Forgetting
.jsextension in imports — Bun ESM requires explicit.jsextensions for relative imports - Using
interfaceinstead ofSchema.Structfor data types passed between services - Wrapping
LLMService.complete()inEffect.tryPromise— it already returns Effect - Using
letfor state — always useRef - Missing
readonlyon service method types — all methods must bereadonly - Calling
LLMService.complete()insideEffect.tryPromise— LLMService methods already returnEffect. Wrapping them inEffect.tryPromisecreates a double-wrapped Effect that will never resolve correctly. - Not adding kernel-specific services as phases — if your new service needs to intercept per-turn reasoning, implement it as a
Phasein the composable kernel rather than a standalone service. Seekernel-extensionskill.
Signals
- GitHub stars
- 27
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
implement-service- Source
- github.com/tylerjrbuell/reactive-agents-ts