Skip to content

Commit 0531a19

Browse files
committed
feat: redesign agentic-kit core and add agent runtime
1 parent b22c8a3 commit 0531a19

31 files changed

Lines changed: 6703 additions & 5676 deletions

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111
<a href="https://github.com/constructive-io/agentic-kit/blob/main/LICENSE"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
1212
</p>
1313

14-
A unified, streaming-capable interface for multiple LLM providers.
14+
A provider-portable LLM toolkit with structured streaming, model registries,
15+
cross-provider message normalization, and an optional stateful agent runtime.
1516

1617
## Packages
1718

18-
- **agentic-kit** — core library with provider abstraction and `AgentKit` manager
19+
- **agentic-kit** — low-level portability layer with model descriptors, registries, structured event streams, and compatibility helpers
20+
- **@agentic-kit/agent** — minimal stateful runtime with sequential tool execution and lifecycle events
1921
- **@agentic-kit/ollama** — adapter for local Ollama inference
2022
- **@agentic-kit/anthropic** — adapter for Anthropic Claude models
21-
- **@agentic-kit/openai** — adapter for OpenAI and OpenAI-compatible APIs
23+
- **@agentic-kit/openai**generalized adapter for OpenAI-compatible chat completion APIs
2224

2325
## Getting Started
2426

@@ -33,14 +35,14 @@ yarn test
3335
## Usage
3436

3537
```typescript
36-
import { createOllamaKit, createMultiProviderKit, OllamaAdapter } from 'agentic-kit';
38+
import { complete, getModel } from 'agentic-kit';
3739

38-
const kit = createOllamaKit('http://localhost:11434');
39-
const text = await kit.generate({ model: 'mistral', prompt: 'Hello' });
40+
const model = getModel('openai', 'gpt-4o-mini');
41+
const message = await complete(model!, {
42+
messages: [{ role: 'user', content: 'Hello', timestamp: Date.now() }],
43+
});
4044

41-
// Multi-provider
42-
const multi = createMultiProviderKit();
43-
multi.addProvider(new OllamaAdapter('http://localhost:11434'));
45+
console.log(message.content);
4446
```
4547

4648
## Contributing

REDESIGN_DECISIONS.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Agentic Kit Redesign Decisions
2+
3+
Date: 2026-04-18
4+
5+
This document records the redesign decisions made while evaluating `agentic-kit`
6+
against the comparable `pi-mono` architecture, especially `packages/ai` and
7+
`packages/agent`.
8+
9+
## Scope and Package Boundaries
10+
11+
1. `agentic-kit` remains the low-level provider portability layer.
12+
2. Stateful orchestration moves into a separate `@agentic-kit/agent` package.
13+
3. Tool execution stays out of `agentic-kit` core; the core only models tools,
14+
tool calls, and tool results.
15+
4. `@agentic-kit/agent` v1 should be intentionally minimal, shipping only the
16+
sequential tool loop, lifecycle events, abort/continue, and pluggable context
17+
transforms. Steering/follow-up queues and richer interruption policies are
18+
deferred to phase 2.
19+
20+
## Core Type System
21+
22+
5. Core tool definitions use plain JSON Schema.
23+
6. TypeBox/Zod support stays as helper adapters, not the core contract.
24+
7. Core models are represented by a provider-independent `ModelDescriptor`
25+
registry with capability metadata.
26+
8. The model registry must support both built-in descriptors and runtime
27+
registration of custom models/providers from day one.
28+
9. The core message model treats `image` input and `thinking` output as
29+
first-class content blocks in v1.
30+
10. `usage`, `cost`, `stopReason`, and abort-driven partial-result semantics are
31+
mandatory parts of the core contract in v1.
32+
33+
## Streaming and Conversation Semantics
34+
35+
11. Structured event streams become the primary streaming primitive; text-only
36+
chunk callbacks remain as convenience wrappers.
37+
12. Cross-provider replay and handoff is a hard requirement for v1, including
38+
normalization for reasoning blocks, tool-call IDs, aborted turns, and
39+
orphaned tool results.
40+
41+
## Provider Strategy
42+
43+
13. OpenAI-compatible backends should be handled by one generalized adapter path
44+
with compatibility flags, not many first-class provider packages.
45+
14. Embeddings stay out of the primary conversational core and live behind a
46+
separate optional capability interface or companion package.
47+
48+
## Migration Strategy
49+
50+
15. `agentic-kit` should ship a backward-compatibility layer for the current
51+
`generate({ model, prompt }, { onChunk })` API for one transition release.
52+
53+
## Architectural Implications
54+
55+
These decisions imply the following target architecture:
56+
57+
- `agentic-kit`
58+
Low-level portability layer. Owns message/content types, model descriptors,
59+
provider registry, streaming event protocol, compatibility transforms, usage,
60+
and provider adapters.
61+
- `@agentic-kit/agent`
62+
Optional stateful runtime. Owns tool execution, sequential loop semantics,
63+
lifecycle events, context transforms, and abort/continue behavior.
64+
- Separate optional capabilities or companion packages
65+
For non-conversational workloads such as embeddings, and optional schema
66+
helpers such as TypeBox/Zod integration.
67+
68+
## Design Principles Confirmed
69+
70+
- Keep the protocol portable and runtime-agnostic.
71+
- Normalize provider differences in the core instead of leaking them upward.
72+
- Treat OpenAI-compatible APIs as a compatibility class, not a brand-specific
73+
architecture.
74+
- Avoid coupling the low-level layer to any single schema library or vendor SDK.
75+
- Preserve a migration path from the existing text-only API while moving the
76+
real architecture to structured messages and events.

packages/agent/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# @agentic-kit/agent
2+
3+
Minimal stateful agent runtime for `agentic-kit`.
4+
5+
This package provides:
6+
7+
- sequential tool execution
8+
- lifecycle events for UI and orchestration
9+
- abort and continue semantics
10+
- pluggable context transforms
11+
12+
It is intentionally minimal in v1 and sits on top of the lower-level
13+
`agentic-kit` provider portability layer.
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { createAssistantMessageEventStream, type Context, type ModelDescriptor } from 'agentic-kit';
2+
3+
import { Agent } from '../src';
4+
5+
function createModel(): ModelDescriptor {
6+
return {
7+
id: 'demo',
8+
name: 'Demo',
9+
api: 'fake',
10+
provider: 'fake',
11+
baseUrl: 'http://fake.local',
12+
input: ['text'],
13+
reasoning: false,
14+
tools: true,
15+
};
16+
}
17+
18+
describe('@agentic-kit/agent', () => {
19+
it('runs a minimal sequential tool loop', async () => {
20+
const responses = [
21+
{
22+
role: 'assistant' as const,
23+
api: 'fake',
24+
provider: 'fake',
25+
model: 'demo',
26+
usage: {
27+
input: 1,
28+
output: 1,
29+
cacheRead: 0,
30+
cacheWrite: 0,
31+
totalTokens: 2,
32+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
33+
},
34+
stopReason: 'toolUse' as const,
35+
timestamp: Date.now(),
36+
content: [
37+
{ type: 'toolCall' as const, id: 'tool_1', name: 'echo', arguments: { text: 'hello' } },
38+
],
39+
},
40+
{
41+
role: 'assistant' as const,
42+
api: 'fake',
43+
provider: 'fake',
44+
model: 'demo',
45+
usage: {
46+
input: 1,
47+
output: 1,
48+
cacheRead: 0,
49+
cacheWrite: 0,
50+
totalTokens: 2,
51+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
52+
},
53+
stopReason: 'stop' as const,
54+
timestamp: Date.now(),
55+
content: [{ type: 'text' as const, text: 'done' }],
56+
},
57+
];
58+
59+
let callIndex = 0;
60+
const streamFn = (_model: ModelDescriptor, _context: Context) => {
61+
const stream = createAssistantMessageEventStream();
62+
const response = responses[callIndex++];
63+
64+
queueMicrotask(() => {
65+
stream.push({ type: 'start', partial: response });
66+
if (response.content[0].type === 'toolCall') {
67+
stream.push({
68+
type: 'toolcall_start',
69+
contentIndex: 0,
70+
partial: response,
71+
});
72+
stream.push({
73+
type: 'toolcall_end',
74+
contentIndex: 0,
75+
toolCall: response.content[0],
76+
partial: response,
77+
});
78+
} else {
79+
stream.push({
80+
type: 'text_start',
81+
contentIndex: 0,
82+
partial: response,
83+
});
84+
stream.push({
85+
type: 'text_delta',
86+
contentIndex: 0,
87+
delta: 'done',
88+
partial: response,
89+
});
90+
stream.push({
91+
type: 'text_end',
92+
contentIndex: 0,
93+
content: 'done',
94+
partial: response,
95+
});
96+
}
97+
stream.push({
98+
type: 'done',
99+
reason: response.stopReason === 'toolUse' ? 'toolUse' : 'stop',
100+
message: response,
101+
});
102+
stream.end(response);
103+
});
104+
105+
return stream;
106+
};
107+
108+
const agent = new Agent({
109+
initialState: {
110+
model: createModel(),
111+
},
112+
streamFn,
113+
});
114+
115+
agent.setTools([
116+
{
117+
name: 'echo',
118+
label: 'Echo',
119+
description: 'Echo text',
120+
parameters: {
121+
type: 'object',
122+
properties: {
123+
text: { type: 'string' },
124+
},
125+
required: ['text'],
126+
},
127+
execute: async (_toolCallId, params) => ({
128+
content: [{ type: 'text', text: String(params.text) }],
129+
}),
130+
},
131+
]);
132+
133+
await agent.prompt('hello');
134+
135+
expect(agent.state.messages).toHaveLength(4);
136+
expect(agent.state.messages[1]).toMatchObject({
137+
role: 'assistant',
138+
stopReason: 'toolUse',
139+
});
140+
expect(agent.state.messages[2]).toMatchObject({
141+
role: 'toolResult',
142+
toolName: 'echo',
143+
content: [{ type: 'text', text: 'hello' }],
144+
});
145+
expect(agent.state.messages[3]).toMatchObject({
146+
role: 'assistant',
147+
content: [{ type: 'text', text: 'done' }],
148+
});
149+
});
150+
});

packages/agent/jest.config.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
preset: 'ts-jest',
4+
testEnvironment: 'node',
5+
transform: {
6+
'^.+\\.tsx?$': [
7+
'ts-jest',
8+
{
9+
babelConfig: false,
10+
tsconfig: 'tsconfig.json',
11+
},
12+
],
13+
},
14+
transformIgnorePatterns: ['/node_modules/*'],
15+
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
16+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
17+
modulePathIgnorePatterns: ['dist/*'],
18+
moduleNameMapper: {
19+
'^(\\.{1,2}/.*)\\.js$': '$1',
20+
'^agentic-kit$': '<rootDir>/../agentic-kit/src',
21+
'^@agentic-kit/(.*)$': '<rootDir>/../$1/src',
22+
},
23+
};

packages/agent/package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@agentic-kit/agent",
3+
"version": "0.1.0",
4+
"author": "Dan Lynch <pyramation@gmail.com>",
5+
"description": "Minimal stateful agent runtime for agentic-kit",
6+
"main": "index.js",
7+
"module": "esm/index.js",
8+
"types": "index.d.ts",
9+
"homepage": "https://github.com/constructive-io/agentic-kit",
10+
"license": "SEE LICENSE IN LICENSE",
11+
"publishConfig": {
12+
"access": "public",
13+
"directory": "dist"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "https://github.com/constructive-io/agentic-kit"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/constructive-io/agentic-kit/issues"
21+
},
22+
"scripts": {
23+
"clean": "makage clean",
24+
"prepack": "npm run build",
25+
"build": "makage build",
26+
"build:dev": "makage build --dev",
27+
"lint": "eslint . --fix",
28+
"test": "jest",
29+
"test:watch": "jest --watch"
30+
},
31+
"dependencies": {
32+
"agentic-kit": "workspace:*"
33+
},
34+
"keywords": []
35+
}

0 commit comments

Comments
 (0)