Skip to content
Back to skills

Kernel Extension

ASecurity

Add new behavior to the composable kernel — new Phase, Guard, MetaTool, or custom kernel variant. Use when extending agent reasoning, adding tool call filtering, or building a custom kernel for a new strategy.

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

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tylerjrbuell/reactive-agents-ts --skill kernel-extension --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Kernel Extension?

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

Security grade badge for Kernel Extension
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tylerjrbuell-kernel-extension/badge)](https://www.skillsdirectory.com/skills/tylerjrbuell-kernel-extension)

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: kernel-extension
description: Add new behavior to the composable kernel — new Phase, Guard, MetaTool, or custom kernel variant. Use when extending agent reasoning, adding tool call filtering, or building a custom kernel for a new strategy.
user-invocable: false
---

# Kernel Extension — Composable Phase Architecture

## Decision Tree: What Are You Adding?

```
Does it need to READ the LLM response and TRANSFORM kernel state per-turn?
  YES → Phase

Does it need to BLOCK or MODIFY a specific tool call before execution?
  YES → Guard

Does it need to INTERCEPT a specific named tool call and return a synthetic result?
  YES → MetaTool entry in metaToolRegistry

Do you need a completely DIFFERENT phase pipeline for a new strategy?
  YES → Custom Kernel via makeKernel({ phases: [...] })
```

When in doubt: Guards are simpler than Phases. Phases are simpler than custom kernels.

## Adding a Phase

### File location

`packages/reasoning/src/kernel/capabilities/<cap>/<name>.ts` (pick the capability that
fits: `reason/`, `act/`, `reflect/`, `verify/`, `attend/`, etc. The old
`strategies/kernel/phases/` tree was removed in the Stage-5 capability re-layout.)

### Exact type signature (no deviations)

```typescript
import { Effect } from "effect";
import { LLMService } from "@reactive-agents/llm-provider";
import { KernelState, KernelContext } from "../../state/kernel-state.js";

export const myPhase = (
  state: KernelState,
  context: KernelContext,
): Effect.Effect<KernelState, never, LLMService> =>
  Effect.gen(function* () {
    // Read state — pure access, no mutation
    const lastStep = state.steps.at(-1);

    // Do work — yield* LLMService only if this is a think-equivalent phase
    // For non-LLM phases, use Effect.sync(() => ...) for pure transformations

    // Return FULL state — spread and override only what changed
    return {
      ...state,
      myNewField: "computed value",
    };
  });
```

### Wire into the kernel

```typescript
// In your strategy file or kernel/loop/react-kernel.ts:
import { makeKernel } from "../loop/react-kernel.js";
import { handleThinking } from "./reason/think.js";
import { handleActing } from "./act/act.js";
import { myPhase } from "./reason/my-phase.js";

// The DEFAULT pipeline is two phases: makeKernel() === [handleThinking, handleActing].
// Context assembly + guards run INSIDE handleThinking/handleActing — they are not
// separate top-level phases. Insert your phase relative to those two:
// - Before handleThinking: pre-processing, context enrichment
// - Between thinking and acting: post-LLM analysis / pre-execution enrichment
// - After handleActing: post-execution reflection
const kernel = makeKernel({
  phases: [handleThinking, myPhase, handleActing],
});
```

### Rules

- Phases are pure functions of `(state, context)` → `Effect<state>`
- NEVER mutate `state` directly — always return a new object via spread
- NEVER add per-turn logic to `kernel/loop/runner.ts` — that's what phases are for
- A phase that calls LLMService should be placed where `think.ts` is or alongside it

## Adding a Guard

### Location

`packages/reasoning/src/kernel/capabilities/act/guard.ts`

### Exact type signature

```typescript
import { Guard, GuardOutcome } from "../kernel-state.js";

export const myGuard: Guard = (
  toolCall: { name: string; input: unknown },
  state: KernelState,
  input: unknown,
): GuardOutcome =>
  // GuardOutcome MUST be exactly one of:
  //   { allow: true }
  //   { block: true; reason: string }
  toolCall.name === "forbidden-tool"
    ? { block: true, reason: "This tool is blocked by myGuard." }
    : { allow: true };
```

### Register for all strategies (default guards)

```typescript
// In guard.ts — add to defaultGuards array:
export const defaultGuards: Guard[] = [
  existingGuard1,
  deduplicationGuard,
  myGuard, // ← add here
];
```

### Register for a single strategy only

```typescript
// In your strategy file — pass a custom guards array:
const kernel = makeKernel({
  phases: [contextBuilder, think, guard, act],
  // custom guards passed via context — see KernelContext.guards
});
```

### Rules

- Guards are SYNCHRONOUS — no `Effect`, no `async`, no `yield*`
- Return exactly `{ allow: true }` or `{ block: true; reason: string }` — nothing else
- Guards run in array order; first `block` wins
- A blocked tool call is logged but does NOT end the run — the LLM gets the block reason and continues

## Adding a MetaTool

### Location

`packages/reasoning/src/kernel/capabilities/act/act.ts`

### Pattern

```typescript
// In act.ts, inside metaToolRegistry:
const metaToolRegistry: Record<string, MetaToolHandler> = {
  "pulse": pulseHandler,       // existing
  "brief": briefHandler,       // existing
  "my-meta-tool": async (args, state, context) => {
    // Receives the parsed tool call arguments
    // Returns a synthetic ToolResult — no real ToolService call
    const result = computeResult(args);
    return {
      content: JSON.stringify(result),
      success: true,
    };
  },
};
```

### When MetaTool vs real Tool

| Use | When |
|-----|------|
| MetaTool | Intercepts a known tool name, synthesizes result from in-memory state, no external I/O |
| Real Tool | Needs ToolService registration, may do HTTP/file/process I/O, follows `ToolDefinition` schema |

## Custom Kernel

Use when a strategy needs a fundamentally different phase sequence:

```typescript
import { makeKernel } from "../loop/react-kernel.js";
import { handleActing } from "./act/act.js";

// Compose only the phases you need:
export const myCustomKernel = makeKernel({
  phases: [myThink, handleActing],
  // Phases are executed in order, left to right, each turn
});

// Register as a ReasoningStrategy:
export const myStrategy: ReasoningStrategy = {
  name: "my-strategy",
  execute: (input) =>
    Effect.gen(function* () {
      const result = yield* myCustomKernel(input);
      return result;
    }),
};
```

## Testing a Phase

Every phase test needs a timeout. Use a mock LLMService layer.

```typescript
// tests/kernel/capabilities/reason/my-phase.test.ts
// Run: bun test packages/reasoning/tests/kernel/capabilities/reason/my-phase.test.ts --timeout 15000
import { Effect, Layer } from "effect";
import { describe, it, expect } from "bun:test";
import { myPhase } from "../../../../src/kernel/capabilities/reason/my-phase.js";
import { LLMService } from "@reactive-agents/llm-provider";
import { makeMockLLM } from "@reactive-agents/testing";

describe("myPhase", () => {
  const mockLLMLayer = Layer.succeed(LLMService, makeMockLLM({
    defaultResponse: "mock response",
  }));

  const makeState = (overrides = {}) => ({
    messages: [],
    steps: [],
    iteration: 0,
    status: "running" as const,
    ...overrides,
  });

  it("should transform state correctly", async () => {
    const state = makeState({ iteration: 1 });
    const context = { task: "test task", agentId: "agent-1" };

    const result = await myPhase(state, context).pipe(
      Effect.provide(mockLLMLayer),
      Effect.runPromise,
    );

    expect(result.myNewField).toBe("expected value");
  }, 15000);

  it("should not modify unrelated state fields", async () => {
    const state = makeState({ messages: [{ role: "user", content: "hi" }] });
    const context = { task: "test", agentId: "agent-1" };

    const result = await myPhase(state, context).pipe(
      Effect.provide(mockLLMLayer),
      Effect.runPromise,
    );

    // Phase should not touch fields it doesn't own
    expect(result.messages).toEqual(state.messages);
  }, 15000);
});
```

## Critical: Do NOT Touch

- `kernel/loop/runner.ts` main loop — extend via phases, not inline logic
- `context-engine.ts`: `buildStaticContext` is LIVE (the static system-prompt builder, called from `context/prompt-sections-default.ts`) — do NOT treat it as dead. Only `buildDynamicContext` was removed (Apr 2026). Earlier versions of this skill wrongly listed `buildStaticContext` as disabled.
- `state.messages[]` via direct mutation — return new state object from phases

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.