Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3c61cab
Finalized Wakeup Core Schema
ConnorIllingworth Dec 8, 2025
6fad733
Added Routes to working files.
ConnorIllingworth Dec 8, 2025
56d0486
[Builds Locally] Add scheduled wakeup store and PG implementation
ConnorIllingworth Dec 8, 2025
778f554
Wakeup Sys Tool
ConnorIllingworth Dec 8, 2025
4949a3b
Routes to files
ConnorIllingworth Dec 8, 2025
136a2ee
[Builds Locally] Add in-memory wakeup store to Kernl storage
ConnorIllingworth Dec 8, 2025
62deb7f
[Builds Locally] Add wakeup system tool and integrate with agent
ConnorIllingworth Dec 8, 2025
fde2269
[Builds Locally] Refactor wakeup tool to require explicit thread_id
ConnorIllingworth Dec 8, 2025
a9ba302
Update wakeup record creation to match domain schema
ConnorIllingworth Dec 8, 2025
1c1c798
[Builds Locally] Rename wakeup system tool to sleep
ConnorIllingworth Dec 8, 2025
4b35008
Refactor sleep tool to use context threadId
ConnorIllingworth Dec 8, 2025
c9ec86f
Add sleeper agent demonstrating sleep/wakeup tool
ConnorIllingworth Dec 10, 2025
5f51888
Register sleeper agent in Kernl app
ConnorIllingworth Dec 10, 2025
e258581
ChatGPT WiP
ConnorIllingworth Dec 10, 2025
55faa31
Refactor thread sleep handling and approval flow
ConnorIllingworth Dec 10, 2025
fcf4999
Add WakeupScheduler for automated wakeup polling
ConnorIllingworth Dec 10, 2025
4771d8d
Switch kernl dependency to workspace and enable scheduler
ConnorIllingworth Dec 10, 2025
ae588c4
Update scheduler configuration in app setup
ConnorIllingworth Dec 10, 2025
0bcf440
Remove placeholder comment from agent index
ConnorIllingworth Dec 10, 2025
f23b3fd
Rename AGENTS.md to .codex/AGENTS.md
ConnorIllingworth Dec 12, 2025
fd50570
Refactor scheduled wakeups schema and migration
ConnorIllingworth Dec 12, 2025
8bb6aab
Refactor wakeup scheduling to use sleepFor duration
ConnorIllingworth Dec 12, 2025
c04bfb6
Simplify wait tool to require delay_s parameter
ConnorIllingworth Dec 12, 2025
80a8ffb
Add migration to enable vector extension
ConnorIllingworth Dec 12, 2025
5f0fd8c
Add user message on thread wakeup in scheduler
ConnorIllingworth Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .codex/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Project Instructions for Claude Code

## Development Guidelines

- We use **pnpm** as our package manager
- At the beginning of any conversation, familiarize yourself with the repo structure
- Prefer method names with linux style, lowercase short words
- Never promote yourself in commits (no "Generated with Claude" messages)
- If we refactor something and are no longer using an old file, clean up after yourself

`pnpm build` is the build command - not `pnpm build:_` or anything else.

---

# Rule: Read the Docs First

**You must do this before proposing designs, writing code, or editing files:**

## 1) Read the canonical specs in `/docs/`
Review the relevant files and design constraints before you suggest anything.

## 2) Follow established codebase patterns
- **Examine existing code** to understand established patterns, conventions, and architectural decisions
- **Maintain consistency** with existing naming conventions, file structures, and coding patterns
- **Reuse existing patterns** rather than inventing new ones unless the spec explicitly requires deviation
- If you must deviate from existing patterns, explicitly justify why based on the `/docs/` specifications

## 3) If the spec is missing or ambiguous
- **Stop and propose a spec addition** first (succinct ADR-style note). Do not implement until the spec gap is resolved.
- Offer a default, but mark it as **"Proposed addition to /docs"**.

## Checklists

### Before coding
- [ ] I looked up the relevant `/docs/*` file(s).
- [ ] I examined existing codebase patterns for similar functionality.
- [ ] I verified my approach is consistent with established conventions.
2 changes: 1 addition & 1 deletion microprojects/playground/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@kernl-sdk/pg": "workspace:*",
"@kernl-sdk/server": "workspace:*",
"@kernl-sdk/turbopuffer": "workspace:*",
"kernl": "*"
"kernl": "workspace:*"
},
"devDependencies": {
"@types/node": "^24.10.0",
Expand Down
16 changes: 16 additions & 0 deletions microprojects/playground/server/src/agents/sleeper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// import "@kernl-sdk/ai/openai";
import { openai } from "@kernl-sdk/ai/openai";
import { Agent } from "kernl";
// import { anthropic } from "@kernl-sdk/ai/anthropic";


export const sleeper = new Agent({
id: "sleeper",
name: "Sleeper",
description: "An agent that demonstrates the sleep/wakeup system tool",
instructions: `You are a helpful assistant that can pause and wait.
When asked to wait or sleep, use the wait_until tool with an appropriate delay.
Always explain what you're doing before sleeping.`,
model: openai("gpt-5.1"),
memory: { enabled: true },
});
13 changes: 12 additions & 1 deletion microprojects/playground/server/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Kernl } from "kernl";
import { pgvector, postgres } from "@kernl-sdk/pg";
import { turbopuffer } from "@kernl-sdk/turbopuffer";
import "@kernl-sdk/ai/openai";

// import { turbopuffer } from "@kernl-sdk/turbopuffer";

import { echo } from "./agents/echo";
import { sleeper } from "./agents/sleeper";
import { titler } from "./agents/titler";
import { watson } from "./agents/watson";

Expand All @@ -12,13 +15,21 @@ export function build(): Kernl {
db: postgres({ connstr: process.env.DATABASE_URL! }),
vector: pgvector({ connstr: process.env.DATABASE_URL! }),
},
// scheduler: true,
scheduler: {
autoStart: true
}
});

// --- agents ---
kernl.register(echo);
kernl.register(sleeper);
kernl.register(titler);
kernl.register(watson);

// start wakeup scheduler
// kernl.schedule?.start();

return kernl;
}

Expand Down
30 changes: 29 additions & 1 deletion packages/kernl/src/agent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* /packages/kernl/src/agent.ts
*/

import {
message,
LanguageModel,
Expand All @@ -15,7 +19,7 @@ import type {
RThreadUpdateParams,
} from "@/api/resources/threads/types";
import type { Context, UnknownContext } from "./context";
import { Tool, memory } from "./tool";
import { Tool, memory, sleep } from "./tool";
import { BaseToolkit } from "./tool/toolkit";
import {
InputGuardrail,
Expand Down Expand Up @@ -112,14 +116,23 @@ export class Agent<
this.kernl = kernl;

// initialize system toolkits

// Memory System Tool
if (this.memory.enabled) {
// safety: system tools only rely on ctx.agent, not ctx.context
const toolkit = memory as unknown as BaseToolkit<TContext>;
this.systools.push(toolkit);
toolkit.bind(this);
}
// Sleep System Tool
{
const sleepToolkit = sleep as unknown as BaseToolkit<TContext>;
this.systools.push(sleepToolkit);
sleepToolkit.bind(this);
}
}


/**
* Blocking execution - spawns or resumes thread and waits for completion
*
Expand Down Expand Up @@ -263,6 +276,21 @@ export class Agent<
return undefined;
}

/**
* @internal
*
* Check if a tool ID belongs to a system toolkit.
*
* @param id The tool ID to check
* @returns true if the tool is a system tool
*/
isSysTool(id: string): boolean {
for (const toolkit of this.systools) {
if (toolkit.get(id)) return true;
}
return false;
}

/**
* @internal
*
Expand Down
86 changes: 79 additions & 7 deletions packages/kernl/src/agent/__tests__/systools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ describe("Agent systools", () => {
const kernl = new Kernl();
kernl.register(agent);

expect(agent.systools.length).toBe(1);
// memory + sleep toolkits
expect(agent.systools.length).toBe(2);
expect(agent.systools[0].id).toBe("sys.memory");
expect(agent.systools[1].id).toBe("sys.sleep");
});

it("has no systools when memory not configured", () => {
it("has only sleep toolkit when memory not configured", () => {
const agent = new Agent({
id: "test-agent",
name: "Test",
Expand All @@ -41,10 +43,12 @@ describe("Agent systools", () => {
const kernl = new Kernl();
kernl.register(agent);

expect(agent.systools.length).toBe(0);
// sleep is always registered
expect(agent.systools.length).toBe(1);
expect(agent.systools[0].id).toBe("sys.sleep");
});

it("has no systools when memory.enabled is false", () => {
it("has only sleep toolkit when memory.enabled is false", () => {
const agent = new Agent({
id: "test-agent",
name: "Test",
Expand All @@ -56,7 +60,9 @@ describe("Agent systools", () => {
const kernl = new Kernl();
kernl.register(agent);

expect(agent.systools.length).toBe(0);
// sleep is always registered
expect(agent.systools.length).toBe(1);
expect(agent.systools[0].id).toBe("sys.sleep");
});

it("can retrieve memory tools via agent.tool()", () => {
Expand Down Expand Up @@ -114,12 +120,13 @@ describe("Agent systools", () => {
const ctx = new Context("test");
const tools = await agent.tools(ctx);

// Memory tools should be first (from systools)
// Order: list, create, update, search
// Memory tools should be first (from systools), then sleep tools
// Order: memory tools (list, create, update, search), then sleep tools (wait_until)
expect(tools[0].id).toBe("list_memories");
expect(tools[1].id).toBe("create_memory");
expect(tools[2].id).toBe("update_memory");
expect(tools[3].id).toBe("search_memories");
expect(tools[4].id).toBe("wait_until");
});
});

Expand Down Expand Up @@ -147,4 +154,69 @@ describe("Agent systools", () => {
expect(agent.memory).toEqual({ enabled: true });
});
});

describe("sleep toolkit", () => {
it("sleep toolkit is always registered", () => {
const agent = new Agent({
id: "test-agent",
name: "Test",
instructions: "Test",
model,
});

const kernl = new Kernl();
kernl.register(agent);

const sleepToolkit = agent.systools.find((t) => t.id === "sys.sleep");
expect(sleepToolkit).toBeDefined();
});

it("can retrieve wait_until tool via agent.tool()", () => {
const agent = new Agent({
id: "test-agent",
name: "Test",
instructions: "Test",
model,
});

const kernl = new Kernl();
kernl.register(agent);

expect(agent.tool("wait_until")).toBeDefined();
});

it("includes wait_until in agent.tools() output", async () => {
const agent = new Agent({
id: "test-agent",
name: "Test",
instructions: "Test",
model,
});

const kernl = new Kernl();
kernl.register(agent);

const ctx = new Context("test");
const tools = await agent.tools(ctx);
const ids = tools.map((t) => t.id);

expect(ids).toContain("wait_until");
});

it("wait_until tool has correct description", () => {
const agent = new Agent({
id: "test-agent",
name: "Test",
instructions: "Test",
model,
});

const kernl = new Kernl();
kernl.register(agent);

const tool = agent.tool("wait_until");
expect(tool).toBeDefined();
expect(tool!.id).toBe("wait_until");
});
});
});
4 changes: 4 additions & 0 deletions packages/kernl/src/agent/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* /packages/kernl/src/agent/types.ts
*/

import { type ZodType } from "zod";

import {
Expand Down
10 changes: 10 additions & 0 deletions packages/kernl/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* /packages/kernl/src/context.ts
*/

import type { Agent } from "@/agent";

/**
Expand Down Expand Up @@ -26,6 +30,12 @@ export class Context<TContext = UnknownContext> {
*/
agent?: Agent<TContext, any>;

/**
* The thread ID for the current execution.
* Set by the thread during tool execution.
*/
threadId?: string;

// ----------------------
// TEMPORARY: Tool approval tracking until actions system is refined
// ----------------------
Expand Down
15 changes: 15 additions & 0 deletions packages/kernl/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* /package/kernl/src/index.ts
*/
export { Kernl } from "./kernl";
export type {
KernlOptions,
Expand Down Expand Up @@ -79,3 +82,15 @@ export type {
MemoryByte,
MemoryByteCodec,
} from "./memory";

// --- wakeups ---
export type {
WakeupStore,
NewScheduledWakeup,
ScheduledWakeup,
ScheduledWakeupUpdate,
} from "./wakeup";

// --- scheduler ---
export { WakeupScheduler } from "./scheduler";
export type { WakeupSchedulerOptions, WakeupSchedulerState } from "./scheduler";
3 changes: 3 additions & 0 deletions packages/kernl/src/kernl/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* /packages/kernl/src/kernl/index.ts
*/
export { Kernl } from "./kernl";
export type {
KernlOptions,
Expand Down
14 changes: 14 additions & 0 deletions packages/kernl/src/kernl/kernl.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* /packages/kernl/src/kernl/kernl.ts
*/
import type { LanguageModel } from "@kernl-sdk/protocol";
import { resolveEmbeddingModel } from "@kernl-sdk/retrieval";

Expand All @@ -15,6 +18,7 @@ import {
MemoryIndexHandle,
buildMemoryIndexSchema,
} from "@/memory";
import { WakeupScheduler } from "@/scheduler";

import type { ThreadExecuteResult, ThreadStreamEvent } from "@/thread/types";
import type { AgentOutputType } from "@/agent/types";
Expand All @@ -38,6 +42,7 @@ export class Kernl extends KernlHooks<UnknownContext, AgentOutputType> {
readonly threads: RThreads;
readonly agents: RAgents;
readonly memories: Memory;
readonly scheduler: WakeupScheduler | null;

constructor(options: KernlOptions = {}) {
super();
Expand Down Expand Up @@ -73,6 +78,15 @@ export class Kernl extends KernlHooks<UnknownContext, AgentOutputType> {
: undefined,
encoder,
});

// initialize scheduler
if (options.scheduler) {
const schedulerOpts =
typeof options.scheduler === "boolean" ? {} : options.scheduler;
this.scheduler = new WakeupScheduler(this, schedulerOpts);
} else {
this.scheduler = null;
}
}

/**
Expand Down
Loading