@agentloop/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # @agentloop/core
2
+
3
+ Core library for agentloop — the minimal, composable agent loop for TypeScript.
4
+
5
+ This package provides the agent loop, tools, policies, observers, messages, content types, and all foundational interfaces. It has zero production dependencies.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @agentloop/core
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { defineAgent, defineTool, definePolicy, defineObserver } from "@agentloop/core";
17
+ ```
18
+
19
+ You'll also need a provider package (e.g. `@agentloop/anthropic`) to create a model instance.
20
+
21
+ ## Agent
22
+
23
+ Create an agent with `defineAgent` and run it with `.run()` or `.stream()`:
24
+
25
+ ```ts
26
+ import { defineAgent } from "@agentloop/core";
27
+ import { createAnthropic } from "@agentloop/anthropic";
28
+
29
+ const provider = createAnthropic();
30
+ const model = provider.model("claude-sonnet-4-6", { maxTokens: 4096 });
31
+
32
+ const agent = defineAgent({
33
+ name: "assistant",
34
+ model,
35
+ instructions: "You are a helpful assistant.",
36
+ tools: [myTool],
37
+ policies: [myPolicy],
38
+ observers: [myObserver],
39
+ });
40
+
41
+ // Run to completion
42
+ const result = await agent.run("What is 2 + 2?");
43
+ console.log(result.text);
44
+ console.log(result.usage); // { inputTokens, outputTokens, totalTokens }
45
+ console.log(result.steps); // number of model calls
46
+ console.log(result.duration); // wall-clock ms
47
+ ```
48
+
49
+ ### Streaming
50
+
51
+ Stream observer events as they happen:
52
+
53
+ ```ts
54
+ const stream = agent.stream("Write a poem.");
55
+ for await (const event of stream) {
56
+ if (event.type === "textDelta") {
57
+ process.stdout.write(event.text);
58
+ }
59
+ }
60
+ const result = await stream.result;
61
+ ```
62
+
63
+ ### Structured output
64
+
65
+ Pass an `output` schema to get typed, validated results:
66
+
67
+ ```ts
68
+ import { z } from "zod";
69
+
70
+ const schema = z.object({
71
+ answer: z.string(),
72
+ confidence: z.number(),
73
+ });
74
+
75
+ const result = await agent.run("What year was TypeScript released?", {
76
+ output: schema,
77
+ });
78
+ console.log(result.object); // { answer: "2012", confidence: 0.95 }
79
+ ```
80
+
81
+ ### Run options
82
+
83
+ Override agent defaults per-run:
84
+
85
+ ```ts
86
+ const result = await agent.run("Hello", {
87
+ transcript: existingMessages, // continue a conversation
88
+ observers: [extraObserver], // additional observers
89
+ policies: [extraPolicy], // additional policies
90
+ output: schema, // structured output schema
91
+ state: { userId: "123" }, // run-scoped state
92
+ signal: abortController.signal, // cooperative cancellation
93
+ maxTokens: 2048, // model config overrides
94
+ temperature: 0.5,
95
+ });
96
+ ```
97
+
98
+ ### Agent nesting
99
+
100
+ Convert an agent into a tool for use by another agent:
101
+
102
+ ```ts
103
+ const researchTool = researchAgent.asTool({
104
+ name: "research",
105
+ description: "Research a topic.",
106
+ schema: z.object({ topic: z.string() }),
107
+ prompt: (args) => `Research: ${args.topic}`,
108
+ });
109
+ ```
110
+
111
+ ## Tools
112
+
113
+ Tools let the model call your functions. Define them with a schema and an execute function:
114
+
115
+ ```ts
116
+ import { defineTool } from "@agentloop/core";
117
+ import { z } from "zod";
118
+
119
+ const getWeather = defineTool({
120
+ name: "get_weather",
121
+ description: "Get weather for a city.",
122
+ schema: z.object({
123
+ city: z.string().describe("The city name"),
124
+ }),
125
+ async execute(args, ctx) {
126
+ ctx.update({ status: "fetching" }); // progress update
127
+ return { city: args.city, temp: 22 };
128
+ },
129
+ });
130
+ ```
131
+
132
+ The schema can be from Zod, Valibot, ArkType, or any library implementing [Standard Schema v1](https://github.com/standard-schema/standard-schema).
133
+
134
+ ### Return types
135
+
136
+ Tools can return a `string`, a `Record<string, unknown>` (auto-wrapped as JSON), or a `Content[]` array.
137
+
138
+ ### Lifecycle hooks
139
+
140
+ ```ts
141
+ const guarded = defineTool({
142
+ name: "delete_file",
143
+ description: "Delete a file.",
144
+ schema: z.object({ path: z.string() }),
145
+ before(args, ctx) {
146
+ if (args.path.startsWith("/etc")) {
147
+ return { action: "skip", reason: "Protected path" };
148
+ }
149
+ },
150
+ async execute(args) {
151
+ // ...
152
+ return `Deleted ${args.path}`;
153
+ },
154
+ after(args, output, ctx) {
155
+ // inspect or rewrite output
156
+ },
157
+ });
158
+ ```
159
+
160
+ ## Policies
161
+
162
+ Policies control the agent loop at five decision points:
163
+
164
+ | Hook | When | Available actions |
165
+ | --------------- | ---------------------------------- | -------------------------- |
166
+ | `beforeStep` | Before each model call | `stop` |
167
+ | `afterResponse` | After model responds, before tools | `stop`, `replace`, `retry` |
168
+ | `beforeToolRun` | Before each tool execution | `skip`, `stop` |
169
+ | `afterToolRun` | After each tool execution | `rewrite`, `retry`, `stop` |
170
+ | `afterStep` | After complete step | `stop`, `retry`, `inject` |
171
+
172
+ ```ts
173
+ import { definePolicy } from "@agentloop/core";
174
+
175
+ const tokenBudget = definePolicy({
176
+ name: "token-budget",
177
+ afterResponse(ctx, info) {
178
+ if (info.totalUsage.totalTokens > 100_000) {
179
+ return { action: "stop", reason: "Token budget exceeded" };
180
+ }
181
+ },
182
+ });
183
+ ```
184
+
185
+ Policies are evaluated in order. The first policy to return an action short-circuits — later policies are skipped.
186
+
187
+ ## Observers
188
+
189
+ Observers watch the loop and react to events. They don't affect control flow.
190
+
191
+ ```ts
192
+ import { defineObserver } from "@agentloop/core";
193
+
194
+ const logger = defineObserver({
195
+ name: "logger",
196
+ onRunStart(event) {
197
+ console.log(`Run started: model=${event.model}`);
198
+ },
199
+ onTextDelta(event) {
200
+ process.stdout.write(event.text);
201
+ },
202
+ onToolRunEnd(event) {
203
+ console.log(`${event.name}: ${event.duration}ms`);
204
+ },
205
+ onRunFinish(event) {
206
+ console.log(`${event.steps} steps, ${event.usage.totalTokens} tokens`);
207
+ },
208
+ });
209
+ ```
210
+
211
+ ### Event types
212
+
213
+ Text: `textStart`, `textDelta`, `textStop`
214
+ Thinking: `thinkingStart`, `thinkingDelta`, `thinkingStop`
215
+ Tool calls: `toolCallStart`, `toolCallDelta`, `toolCallStop`
216
+ Tool execution: `toolRunStart`, `toolRunUpdate`, `toolRunEnd`, `toolSkip`
217
+ Steps: `stepStart`, `stepRetry`, `stepFinish`
218
+ Response: `responseFinish`
219
+ Run: `runStart`, `runFinish`
220
+ Errors: `abort`, `error`
221
+
222
+ Use typed `on*` handlers for specific events or a catch-all `handler(event)` for everything.
223
+
224
+ ## Messages and content
225
+
226
+ ### Messages
227
+
228
+ Four message types: `SystemMessage`, `UserMessage`, `AssistantMessage`, `ToolMessage`.
229
+
230
+ Helper constructors:
231
+
232
+ ```ts
233
+ import { system, user, assistant } from "@agentloop/core";
234
+
235
+ const messages = [system("You are helpful."), user("Hello!")];
236
+ ```
237
+
238
+ ### Content parts
239
+
240
+ Messages contain typed content parts:
241
+
242
+ ```ts
243
+ import { text, json, blob, url } from "@agentloop/core";
244
+
245
+ // Text content
246
+ text("Hello world");
247
+
248
+ // Structured JSON
249
+ json({ key: "value" });
250
+
251
+ // Binary data (images, audio, etc.)
252
+ blob(uint8Array, "image/png");
253
+
254
+ // External URL
255
+ url("https://example.com/image.png", "image/png");
256
+ ```
257
+
258
+ ### Prompt type
259
+
260
+ `Prompt` is a flexible input type — it accepts a `string`, a single `Message`, or a `Message[]` array. Use `normalizePrompt()` to convert any prompt to a `Message[]`.
261
+
262
+ ## Model and Provider
263
+
264
+ The `Provider` interface creates `Model` instances. The `Model` interface streams responses:
265
+
266
+ ```ts
267
+ interface Provider {
268
+ model(name: string, config?: ModelConfig): Model;
269
+ }
270
+
271
+ interface Model {
272
+ name: string;
273
+ stream(options: {
274
+ messages: Message[];
275
+ tools?: ToolDefinition[];
276
+ config?: ModelConfig;
277
+ output?: Schema;
278
+ signal?: AbortSignal;
279
+ }): AsyncIterable<StreamPart>;
280
+ }
281
+ ```
282
+
283
+ Implement these interfaces to add support for any LLM provider.
284
+
285
+ ## License
286
+
287
+ MIT