kernl 0.11.1 → 0.11.2

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > kernl@0.11.1 build /home/runner/work/kernl/kernl/packages/kernl
2
+ > kernl@0.11.2 build /home/runner/work/kernl/kernl/packages/kernl
3
3
  > tsc && tsc-alias --resolve-full-paths
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @kernl/core
2
2
 
3
+ ## 0.11.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 47e44c0: Fix lifecycle event architecture: Thread now owns all lifecycle events and emits via agent for uniform observability
8
+ - Move thread.start/stop emissions from Kernl to Thread.stream()
9
+ - Both agent.on() and kernl.on() now receive all lifecycle events
10
+ - Remove redundant outcome field from ThreadStopEvent (use result/error instead)
11
+ - Fix: schedule() was missing thread events, agent.on("thread.\*") never fired
12
+
3
13
  ## 0.11.1
4
14
 
5
15
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"kernl.d.ts","sourceRoot":"","sources":["../../src/kernl/kernl.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EACL,MAAM,EAIP,MAAM,UAAU,CAAC;AAIlB,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAiC,MAAM,SAAS,CAAC;AAE3E;;;;;GAKG;AACH,qBAAa,KAAM,SAAQ,UAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;IAEjE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAa;IAEpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IAEvD,OAAO,CAAC,QAAQ,CAGd;IAGF,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,OAAO,GAAE,YAAiB;IAatC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAgChC;;OAEG;IACG,KAAK,CAAC,QAAQ,EAAE,OAAO,SAAS,eAAe,EACnD,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IA2C/D;;;;OAIG;IACG,QAAQ,CAAC,QAAQ,EAAE,OAAO,SAAS,eAAe,EACtD,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAS/D;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,OAAO,SAAS,eAAe,EAC1D,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAChC,aAAa,CAAC,iBAAiB,CAAC;IAwCnC;;;;OAIG;IACI,cAAc,CAAC,QAAQ,EAAE,OAAO,SAAS,eAAe,EAC7D,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAChC,aAAa,CAAC,iBAAiB,CAAC;IAWnC;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;CA6BzB"}
1
+ {"version":3,"file":"kernl.d.ts","sourceRoot":"","sources":["../../src/kernl/kernl.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EACL,MAAM,EAIP,MAAM,UAAU,CAAC;AAIlB,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAiC,MAAM,SAAS,CAAC;AAE3E;;;;;GAKG;AACH,qBAAa,KAAM,SAAQ,UAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;IAEjE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAa;IAEpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IAEvD,OAAO,CAAC,QAAQ,CAGd;IAGF,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,OAAO,GAAE,YAAiB;IAatC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAgChC;;OAEG;IACG,KAAK,CAAC,QAAQ,EAAE,OAAO,SAAS,eAAe,EACnD,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAS/D;;;;OAIG;IACG,QAAQ,CAAC,QAAQ,EAAE,OAAO,SAAS,eAAe,EACtD,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAS/D;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,OAAO,SAAS,eAAe,EAC1D,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAChC,aAAa,CAAC,iBAAiB,CAAC;IASnC;;;;OAIG;IACI,cAAc,CAAC,QAAQ,EAAE,OAAO,SAAS,eAAe,EAC7D,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAChC,aAAa,CAAC,iBAAiB,CAAC;IAWnC;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;CA6BzB"}
@@ -68,39 +68,8 @@ export class Kernl extends KernlHooks {
68
68
  */
69
69
  async spawn(thread) {
70
70
  this.athreads.set(thread.tid, thread);
71
- this.emit("thread.start", {
72
- kind: "thread.start",
73
- threadId: thread.tid,
74
- agentId: thread.agent.id,
75
- namespace: thread.namespace,
76
- context: thread.context,
77
- });
78
71
  try {
79
- const result = await thread.execute();
80
- this.emit("thread.stop", {
81
- kind: "thread.stop",
82
- threadId: thread.tid,
83
- agentId: thread.agent.id,
84
- namespace: thread.namespace,
85
- context: thread.context,
86
- state: thread.state,
87
- outcome: "success",
88
- result: result.response,
89
- });
90
- return result;
91
- }
92
- catch (err) {
93
- this.emit("thread.stop", {
94
- kind: "thread.stop",
95
- threadId: thread.tid,
96
- agentId: thread.agent.id,
97
- namespace: thread.namespace,
98
- context: thread.context,
99
- state: thread.state,
100
- outcome: "error",
101
- error: err instanceof Error ? err.message : String(err),
102
- });
103
- throw err;
72
+ return await thread.execute();
104
73
  }
105
74
  finally {
106
75
  this.athreads.delete(thread.tid);
@@ -127,37 +96,8 @@ export class Kernl extends KernlHooks {
127
96
  */
128
97
  async *spawnStream(thread) {
129
98
  this.athreads.set(thread.tid, thread);
130
- this.emit("thread.start", {
131
- kind: "thread.start",
132
- threadId: thread.tid,
133
- agentId: thread.agent.id,
134
- namespace: thread.namespace,
135
- context: thread.context,
136
- });
137
99
  try {
138
100
  yield* thread.stream();
139
- this.emit("thread.stop", {
140
- kind: "thread.stop",
141
- threadId: thread.tid,
142
- agentId: thread.agent.id,
143
- namespace: thread.namespace,
144
- context: thread.context,
145
- state: thread.state,
146
- outcome: "success",
147
- });
148
- }
149
- catch (err) {
150
- this.emit("thread.stop", {
151
- kind: "thread.stop",
152
- threadId: thread.tid,
153
- agentId: thread.agent.id,
154
- namespace: thread.namespace,
155
- context: thread.context,
156
- state: thread.state,
157
- outcome: "error",
158
- error: err instanceof Error ? err.message : String(err),
159
- });
160
- throw err;
161
101
  }
162
102
  finally {
163
103
  this.athreads.delete(thread.tid);
@@ -35,7 +35,7 @@ describe("Lifecycle Hooks", () => {
35
35
  expect(events[0].threadId).toBeDefined();
36
36
  expect(events[0].context).toBeDefined();
37
37
  });
38
- it("emits thread.stop with outcome=success on successful run", async () => {
38
+ it("emits thread.stop with result on successful run", async () => {
39
39
  const events = [];
40
40
  const model = createMockModel(async () => ({
41
41
  content: [message({ role: "assistant", text: "Done" })],
@@ -58,12 +58,11 @@ describe("Lifecycle Hooks", () => {
58
58
  kind: "thread.stop",
59
59
  agentId: "test-agent",
60
60
  namespace: "kernl",
61
- outcome: "success",
62
61
  state: "stopped",
63
62
  result: "Done",
64
63
  });
65
64
  });
66
- it("emits thread.stop with outcome=error on failure", async () => {
65
+ it("emits thread.stop with error on failure", async () => {
67
66
  const events = [];
68
67
  const model = createMockModel(async () => {
69
68
  throw new Error("Model exploded");
@@ -82,7 +81,6 @@ describe("Lifecycle Hooks", () => {
82
81
  expect(events[0]).toMatchObject({
83
82
  kind: "thread.stop",
84
83
  agentId: "test-agent",
85
- outcome: "error",
86
84
  error: "Model exploded",
87
85
  });
88
86
  });
@@ -112,6 +110,41 @@ describe("Lifecycle Hooks", () => {
112
110
  expect(kernlEvents).toHaveLength(1);
113
111
  expect(agentEvents[0].threadId).toBe(kernlEvents[0].threadId);
114
112
  });
113
+ it("agent.on receives thread events (emitted by Thread via agent.emit)", async () => {
114
+ const agentStartEvents = [];
115
+ const agentStopEvents = [];
116
+ const kernlStartEvents = [];
117
+ const kernlStopEvents = [];
118
+ const model = createMockModel(async () => ({
119
+ content: [message({ role: "assistant", text: "Done" })],
120
+ finishReason: "stop",
121
+ usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
122
+ warnings: [],
123
+ }));
124
+ const agent = new Agent({
125
+ id: "test-agent",
126
+ name: "Test",
127
+ instructions: "Test",
128
+ model,
129
+ });
130
+ const kernl = new Kernl();
131
+ kernl.register(agent);
132
+ // Thread events should be receivable via both agent.on and kernl.on
133
+ agent.on("thread.start", (e) => agentStartEvents.push(e));
134
+ agent.on("thread.stop", (e) => agentStopEvents.push(e));
135
+ kernl.on("thread.start", (e) => kernlStartEvents.push(e));
136
+ kernl.on("thread.stop", (e) => kernlStopEvents.push(e));
137
+ await agent.run("Hello");
138
+ // Both agent and kernl should receive thread events
139
+ expect(agentStartEvents).toHaveLength(1);
140
+ expect(agentStopEvents).toHaveLength(1);
141
+ expect(kernlStartEvents).toHaveLength(1);
142
+ expect(kernlStopEvents).toHaveLength(1);
143
+ // Same event data
144
+ expect(agentStartEvents[0].threadId).toBe(kernlStartEvents[0].threadId);
145
+ expect(agentStopEvents[0].threadId).toBe(kernlStopEvents[0].threadId);
146
+ expect(agentStopEvents[0].result).toBeDefined();
147
+ });
115
148
  });
116
149
  describe("Model events", () => {
117
150
  it("emits model.call.start before generation", async () => {
@@ -54,15 +54,11 @@ export interface ThreadStopEvent<TContext = unknown, TOutput = unknown> {
54
54
  */
55
55
  state: ThreadState;
56
56
  /**
57
- * The outcome of the execution.
58
- */
59
- outcome: "success" | "error" | "cancelled";
60
- /**
61
- * The result if outcome is "success".
57
+ * The result of execution (present on success).
62
58
  */
63
59
  result?: TOutput;
64
60
  /**
65
- * Error message if outcome is "error".
61
+ * Error message (present on error).
66
62
  */
67
63
  error?: string;
68
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -61,6 +61,7 @@ export declare class Thread<TContext = unknown, TOutput extends AgentOutputType
61
61
  private cpbuf;
62
62
  private persisted;
63
63
  private history;
64
+ private tickres?;
64
65
  private abort?;
65
66
  private storage?;
66
67
  constructor(options: ThreadOptions<TContext, TOutput>);
@@ -70,6 +71,11 @@ export declare class Thread<TContext = unknown, TOutput extends AgentOutputType
70
71
  execute(): Promise<ThreadExecuteResult<ResolvedAgentResponse<TOutput>>>;
71
72
  /**
72
73
  * Streaming execution - returns async iterator of events
74
+ *
75
+ * All runs (new or resumed) emit:
76
+ * - Exactly one thread.start
77
+ * - Zero or more model.call.* and tool.call.*
78
+ * - Exactly one thread.stop (with result on success, error on failure)
73
79
  */
74
80
  stream(): AsyncIterable<ThreadStreamEvent>;
75
81
  /**
@@ -104,7 +110,7 @@ export declare class Thread<TContext = unknown, TOutput extends AgentOutputType
104
110
  /**
105
111
  * Cancel the running thread
106
112
  *
107
- * TODO: Emit thread.stop with outcome: 'cancelled' when cancelled
113
+ * TODO: Emit thread.stop when cancelled (neither result nor error set)
108
114
  */
109
115
  cancel(): void;
110
116
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/thread/thread.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAKzD,OAAO,EAML,aAAa,EAKd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAEV,WAAW,EACX,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAEpB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAWrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,qBAAa,MAAM,CACjB,QAAQ,GAAG,OAAO,EAClB,OAAO,SAAS,eAAe,GAAG,MAAM;IAExC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAIlD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,OAAO,CAAwE;IAEvF,OAAO,CAAC,KAAK,CAAC,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAC,CAAc;gBAElB,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC;IAgCrD;;OAEG;IACG,OAAO,IAAI,OAAO,CACtB,mBAAmB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CACpD;IAoBD;;OAEG;IACI,MAAM,IAAI,aAAa,CAAC,iBAAiB,CAAC;IAuBjD;;;;;OAKG;YACY,QAAQ;IAgEvB;;;;;OAKG;YACY,IAAI;IAoEnB;;;;;OAKG;YACW,UAAU;IAuCxB;;;;;;OAMG;IACH,MAAM,CAAC,GAAG,KAAK,EAAE,gBAAgB,EAAE,GAAG,WAAW,EAAE;IAiBnD;;;;OAIG;IACH,MAAM;IAQN;;OAEG;YACW,cAAc;IAyC5B;;;;OAIG;YACW,YAAY;IAkF1B;;OAEG;YACW,mBAAmB;CAsDlC"}
1
+ {"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/thread/thread.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAKzD,OAAO,EAML,aAAa,EAKd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAEV,WAAW,EACX,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAEpB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAWrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,qBAAa,MAAM,CACjB,QAAQ,GAAG,OAAO,EAClB,OAAO,SAAS,eAAe,GAAG,MAAM;IAExC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAIlD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,OAAO,CAAwE;IACvF,OAAO,CAAC,OAAO,CAAC,CAAiC;IAEjD,OAAO,CAAC,KAAK,CAAC,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAC,CAAc;gBAElB,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC;IAgCrD;;OAEG;IACG,OAAO,IAAI,OAAO,CACtB,mBAAmB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CACpD;IAQD;;;;;;;OAOG;IACI,MAAM,IAAI,aAAa,CAAC,iBAAiB,CAAC;IAmDjD;;;;;OAKG;YACY,QAAQ;IAiEvB;;;;;OAKG;YACY,IAAI;IAoEnB;;;;;OAKG;YACW,UAAU;IAuCxB;;;;;;OAMG;IACH,MAAM,CAAC,GAAG,KAAK,EAAE,gBAAgB,EAAE,GAAG,WAAW,EAAE;IAiBnD;;;;OAIG;IACH,MAAM;IAQN;;OAEG;YACW,cAAc;IAyC5B;;;;OAIG;YACW,YAAY;IAkF1B;;OAEG;YACW,mBAAmB;CAsDlC"}
@@ -63,6 +63,7 @@ export class Thread {
63
63
  cpbuf; /* checkpoint buffer - events pending persistence */
64
64
  persisted; /* indicates thread was hydrated from storage */
65
65
  history;
66
+ tickres; /* final result from terminal tick */
66
67
  abort;
67
68
  storage;
68
69
  constructor(options) {
@@ -100,20 +101,16 @@ export class Thread {
100
101
  for await (const _event of this.stream()) {
101
102
  // just consume the stream (already in history in _execute())
102
103
  }
103
- // filter for language model items
104
- const items = this.history
105
- .filter((e) => e.kind !== "system")
106
- .map((e) => {
107
- const { tid, seq, timestamp, metadata, ...item } = e;
108
- return item;
109
- });
110
- const text = getFinalResponse(items);
111
- assert(text, "_execute continues until text !== null"); // (TODO): consider preventing infinite loops here
112
- const parsed = parseFinalResponse(text, this.agent.output);
113
- return { response: parsed, state: this.state };
104
+ assert(this.tickres, "_execute continues until tickres is set");
105
+ return { response: this.tickres, state: this.state };
114
106
  }
115
107
  /**
116
108
  * Streaming execution - returns async iterator of events
109
+ *
110
+ * All runs (new or resumed) emit:
111
+ * - Exactly one thread.start
112
+ * - Zero or more model.call.* and tool.call.*
113
+ * - Exactly one thread.stop (with result on success, error on failure)
117
114
  */
118
115
  async *stream() {
119
116
  if (this.state === RUNNING && this.abort) {
@@ -121,12 +118,38 @@ export class Thread {
121
118
  }
122
119
  this.state = RUNNING;
123
120
  this.abort = new AbortController();
121
+ this.tickres = undefined; // reset for this run
124
122
  await this.checkpoint(); /* c1: persist RUNNING state + initial input */
123
+ this.agent.emit("thread.start", {
124
+ kind: "thread.start",
125
+ threadId: this.tid,
126
+ agentId: this.agent.id,
127
+ namespace: this.namespace,
128
+ context: this.context,
129
+ });
125
130
  yield { kind: "stream-start" }; // always yield start immediately
126
131
  try {
127
132
  yield* this._execute();
133
+ this.agent.emit("thread.stop", {
134
+ kind: "thread.stop",
135
+ threadId: this.tid,
136
+ agentId: this.agent.id,
137
+ namespace: this.namespace,
138
+ context: this.context,
139
+ state: STOPPED,
140
+ result: this.tickres,
141
+ });
128
142
  }
129
143
  catch (err) {
144
+ this.agent.emit("thread.stop", {
145
+ kind: "thread.stop",
146
+ threadId: this.tid,
147
+ agentId: this.agent.id,
148
+ namespace: this.namespace,
149
+ context: this.context,
150
+ state: STOPPED,
151
+ error: err instanceof Error ? err.message : String(err),
152
+ });
130
153
  throw err;
131
154
  }
132
155
  finally {
@@ -170,6 +193,7 @@ export class Thread {
170
193
  const text = getFinalResponse(events);
171
194
  if (!text)
172
195
  continue; // run again, policy-dependent? (how to ensure no infinite loop here?)
196
+ this.tickres = parseFinalResponse(text, this.agent.output);
173
197
  await this.checkpoint(); /* c2: terminal tick - no tool calls */
174
198
  // await this.agent.runOutputGuardails(context, state);
175
199
  // this.kernl.emit("thread.terminated", context, output);
@@ -327,7 +351,7 @@ export class Thread {
327
351
  /**
328
352
  * Cancel the running thread
329
353
  *
330
- * TODO: Emit thread.stop with outcome: 'cancelled' when cancelled
354
+ * TODO: Emit thread.stop when cancelled (neither result nor error set)
331
355
  */
332
356
  cancel() {
333
357
  this.abort?.abort();
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/thread/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAIzD,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAIlE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EACV,WAAW,EAEX,iBAAiB,EACjB,SAAS,EACT,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,GAAG,WAAW,CAad;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,IAAI,QAAQ,CAE7E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,SAAS,GAAG,IAAI,CAG3E;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,IAAI,iBAAiB,CAY7E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,IAAI,iBAAiB,CAc5E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,MAAM,GAAG,IAAI,CAc1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,eAAe,EAChE,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,GACd,qBAAqB,CAAC,OAAO,CAAC,CAsBhC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/thread/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAIzD,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAIlE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EACV,WAAW,EAEX,iBAAiB,EACjB,SAAS,EACT,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,GAAG,WAAW,CAad;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,IAAI,QAAQ,CAE7E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,SAAS,GAAG,IAAI,CAG3E;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,IAAI,iBAAiB,CAY7E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,IAAI,iBAAiB,CAc5E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,MAAM,GAAG,IAAI,CAa1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,eAAe,EAChE,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,GACd,qBAAqB,CAAC,OAAO,CAAC,CAsBhC"}
@@ -80,11 +80,10 @@ export function isPublicEvent(event) {
80
80
  * Returns null if no assistant message with text content is found.
81
81
  */
82
82
  export function getFinalResponse(items) {
83
- // Scan backwards for the last assistant message
83
+ // scan backwards for the last assistant message
84
84
  for (let i = items.length - 1; i >= 0; i--) {
85
85
  const item = items[i];
86
86
  if (item.kind === "message" && item.role === "assistant") {
87
- // Extract text from content parts
88
87
  for (const part of item.content) {
89
88
  if (part.kind === "text") {
90
89
  return part.text;
@@ -120,6 +119,6 @@ export function parseFinalResponse(text, output) {
120
119
  throw new ModelBehaviorError(`Failed to parse structured output: ${error instanceof Error ? error.message : String(error)}`);
121
120
  }
122
121
  }
123
- // Fallback - should not reach here
122
+ // fallback - should not reach here
124
123
  return text;
125
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kernl",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "A modern AI agent framework",
5
5
  "keywords": [
6
6
  "kernl",
@@ -34,9 +34,9 @@
34
34
  "dependencies": {
35
35
  "@modelcontextprotocol/sdk": "^1.20.2",
36
36
  "yaml": "^2.8.2",
37
- "@kernl-sdk/protocol": "0.4.1",
38
37
  "@kernl-sdk/retrieval": "0.1.7",
39
- "@kernl-sdk/shared": "^0.3.1"
38
+ "@kernl-sdk/shared": "^0.3.1",
39
+ "@kernl-sdk/protocol": "0.4.1"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "zod": "^4.1.8"
@@ -102,42 +102,8 @@ export class Kernl extends KernlHooks {
102
102
  thread: Thread<TContext, TOutput>,
103
103
  ): Promise<ThreadExecuteResult<ResolvedAgentResponse<TOutput>>> {
104
104
  this.athreads.set(thread.tid, thread);
105
-
106
- this.emit("thread.start", {
107
- kind: "thread.start",
108
- threadId: thread.tid,
109
- agentId: thread.agent.id,
110
- namespace: thread.namespace,
111
- context: thread.context,
112
- });
113
-
114
105
  try {
115
- const result = await thread.execute();
116
-
117
- this.emit("thread.stop", {
118
- kind: "thread.stop",
119
- threadId: thread.tid,
120
- agentId: thread.agent.id,
121
- namespace: thread.namespace,
122
- context: thread.context,
123
- state: thread.state,
124
- outcome: "success",
125
- result: result.response,
126
- });
127
-
128
- return result;
129
- } catch (err) {
130
- this.emit("thread.stop", {
131
- kind: "thread.stop",
132
- threadId: thread.tid,
133
- agentId: thread.agent.id,
134
- namespace: thread.namespace,
135
- context: thread.context,
136
- state: thread.state,
137
- outcome: "error",
138
- error: err instanceof Error ? err.message : String(err),
139
- });
140
- throw err;
106
+ return await thread.execute();
141
107
  } finally {
142
108
  this.athreads.delete(thread.tid);
143
109
  }
@@ -168,39 +134,8 @@ export class Kernl extends KernlHooks {
168
134
  thread: Thread<TContext, TOutput>,
169
135
  ): AsyncIterable<ThreadStreamEvent> {
170
136
  this.athreads.set(thread.tid, thread);
171
-
172
- this.emit("thread.start", {
173
- kind: "thread.start",
174
- threadId: thread.tid,
175
- agentId: thread.agent.id,
176
- namespace: thread.namespace,
177
- context: thread.context,
178
- });
179
-
180
137
  try {
181
138
  yield* thread.stream();
182
-
183
- this.emit("thread.stop", {
184
- kind: "thread.stop",
185
- threadId: thread.tid,
186
- agentId: thread.agent.id,
187
- namespace: thread.namespace,
188
- context: thread.context,
189
- state: thread.state,
190
- outcome: "success",
191
- });
192
- } catch (err) {
193
- this.emit("thread.stop", {
194
- kind: "thread.stop",
195
- threadId: thread.tid,
196
- agentId: thread.agent.id,
197
- namespace: thread.namespace,
198
- context: thread.context,
199
- state: thread.state,
200
- outcome: "error",
201
- error: err instanceof Error ? err.message : String(err),
202
- });
203
- throw err;
204
139
  } finally {
205
140
  this.athreads.delete(thread.tid);
206
141
  }
@@ -54,7 +54,7 @@ describe("Lifecycle Hooks", () => {
54
54
  expect(events[0].context).toBeDefined();
55
55
  });
56
56
 
57
- it("emits thread.stop with outcome=success on successful run", async () => {
57
+ it("emits thread.stop with result on successful run", async () => {
58
58
  const events: ThreadStopEvent[] = [];
59
59
 
60
60
  const model = createMockModel(async () => ({
@@ -83,13 +83,12 @@ describe("Lifecycle Hooks", () => {
83
83
  kind: "thread.stop",
84
84
  agentId: "test-agent",
85
85
  namespace: "kernl",
86
- outcome: "success",
87
86
  state: "stopped",
88
87
  result: "Done",
89
88
  });
90
89
  });
91
90
 
92
- it("emits thread.stop with outcome=error on failure", async () => {
91
+ it("emits thread.stop with error on failure", async () => {
93
92
  const events: ThreadStopEvent[] = [];
94
93
 
95
94
  const model = createMockModel(async () => {
@@ -114,7 +113,6 @@ describe("Lifecycle Hooks", () => {
114
113
  expect(events[0]).toMatchObject({
115
114
  kind: "thread.stop",
116
115
  agentId: "test-agent",
117
- outcome: "error",
118
116
  error: "Model exploded",
119
117
  });
120
118
  });
@@ -151,6 +149,49 @@ describe("Lifecycle Hooks", () => {
151
149
  expect(kernlEvents).toHaveLength(1);
152
150
  expect(agentEvents[0].threadId).toBe(kernlEvents[0].threadId);
153
151
  });
152
+
153
+ it("agent.on receives thread events (emitted by Thread via agent.emit)", async () => {
154
+ const agentStartEvents: ThreadStartEvent[] = [];
155
+ const agentStopEvents: ThreadStopEvent[] = [];
156
+ const kernlStartEvents: ThreadStartEvent[] = [];
157
+ const kernlStopEvents: ThreadStopEvent[] = [];
158
+
159
+ const model = createMockModel(async () => ({
160
+ content: [message({ role: "assistant", text: "Done" })],
161
+ finishReason: "stop",
162
+ usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
163
+ warnings: [],
164
+ }));
165
+
166
+ const agent = new Agent({
167
+ id: "test-agent",
168
+ name: "Test",
169
+ instructions: "Test",
170
+ model,
171
+ });
172
+
173
+ const kernl = new Kernl();
174
+ kernl.register(agent);
175
+
176
+ // Thread events should be receivable via both agent.on and kernl.on
177
+ agent.on("thread.start", (e) => agentStartEvents.push(e));
178
+ agent.on("thread.stop", (e) => agentStopEvents.push(e));
179
+ kernl.on("thread.start", (e) => kernlStartEvents.push(e));
180
+ kernl.on("thread.stop", (e) => kernlStopEvents.push(e));
181
+
182
+ await agent.run("Hello");
183
+
184
+ // Both agent and kernl should receive thread events
185
+ expect(agentStartEvents).toHaveLength(1);
186
+ expect(agentStopEvents).toHaveLength(1);
187
+ expect(kernlStartEvents).toHaveLength(1);
188
+ expect(kernlStopEvents).toHaveLength(1);
189
+
190
+ // Same event data
191
+ expect(agentStartEvents[0].threadId).toBe(kernlStartEvents[0].threadId);
192
+ expect(agentStopEvents[0].threadId).toBe(kernlStopEvents[0].threadId);
193
+ expect(agentStopEvents[0].result).toBeDefined();
194
+ });
154
195
  });
155
196
 
156
197
  describe("Model events", () => {
package/src/lifecycle.ts CHANGED
@@ -74,17 +74,12 @@ export interface ThreadStopEvent<TContext = unknown, TOutput = unknown> {
74
74
  state: ThreadState;
75
75
 
76
76
  /**
77
- * The outcome of the execution.
78
- */
79
- outcome: "success" | "error" | "cancelled";
80
-
81
- /**
82
- * The result if outcome is "success".
77
+ * The result of execution (present on success).
83
78
  */
84
79
  result?: TOutput;
85
80
 
86
81
  /**
87
- * Error message if outcome is "error".
82
+ * Error message (present on error).
88
83
  */
89
84
  error?: string;
90
85
  }
@@ -107,6 +107,7 @@ export class Thread<
107
107
  private cpbuf: ThreadEvent[]; /* checkpoint buffer - events pending persistence */
108
108
  private persisted: boolean; /* indicates thread was hydrated from storage */
109
109
  private history: ThreadEvent[] /* history representing the event log for the thread */;
110
+ private tickres?: ResolvedAgentResponse<TOutput>; /* final result from terminal tick */
110
111
 
111
112
  private abort?: AbortController;
112
113
  private storage?: ThreadStore;
@@ -152,24 +153,17 @@ export class Thread<
152
153
  for await (const _event of this.stream()) {
153
154
  // just consume the stream (already in history in _execute())
154
155
  }
155
-
156
- // filter for language model items
157
- const items = this.history
158
- .filter((e) => e.kind !== "system")
159
- .map((e) => {
160
- const { tid, seq, timestamp, metadata, ...item } = e;
161
- return item as LanguageModelItem;
162
- });
163
-
164
- const text = getFinalResponse(items);
165
- assert(text, "_execute continues until text !== null"); // (TODO): consider preventing infinite loops here
166
- const parsed = parseFinalResponse(text, this.agent.output);
167
-
168
- return { response: parsed, state: this.state };
156
+ assert(this.tickres, "_execute continues until tickres is set");
157
+ return { response: this.tickres, state: this.state };
169
158
  }
170
159
 
171
160
  /**
172
161
  * Streaming execution - returns async iterator of events
162
+ *
163
+ * All runs (new or resumed) emit:
164
+ * - Exactly one thread.start
165
+ * - Zero or more model.call.* and tool.call.*
166
+ * - Exactly one thread.stop (with result on success, error on failure)
173
167
  */
174
168
  async *stream(): AsyncIterable<ThreadStreamEvent> {
175
169
  if (this.state === RUNNING && this.abort) {
@@ -178,14 +172,42 @@ export class Thread<
178
172
 
179
173
  this.state = RUNNING;
180
174
  this.abort = new AbortController();
175
+ this.tickres = undefined; // reset for this run
181
176
 
182
177
  await this.checkpoint(); /* c1: persist RUNNING state + initial input */
183
178
 
179
+ this.agent.emit("thread.start", {
180
+ kind: "thread.start",
181
+ threadId: this.tid,
182
+ agentId: this.agent.id,
183
+ namespace: this.namespace,
184
+ context: this.context,
185
+ });
186
+
184
187
  yield { kind: "stream-start" }; // always yield start immediately
185
188
 
186
189
  try {
187
190
  yield* this._execute();
191
+
192
+ this.agent.emit("thread.stop", {
193
+ kind: "thread.stop",
194
+ threadId: this.tid,
195
+ agentId: this.agent.id,
196
+ namespace: this.namespace,
197
+ context: this.context,
198
+ state: STOPPED,
199
+ result: this.tickres,
200
+ });
188
201
  } catch (err) {
202
+ this.agent.emit("thread.stop", {
203
+ kind: "thread.stop",
204
+ threadId: this.tid,
205
+ agentId: this.agent.id,
206
+ namespace: this.namespace,
207
+ context: this.context,
208
+ state: STOPPED,
209
+ error: err instanceof Error ? err.message : String(err),
210
+ });
189
211
  throw err;
190
212
  } finally {
191
213
  this.state = STOPPED;
@@ -233,6 +255,7 @@ export class Thread<
233
255
  const text = getFinalResponse(events);
234
256
  if (!text) continue; // run again, policy-dependent? (how to ensure no infinite loop here?)
235
257
 
258
+ this.tickres = parseFinalResponse(text, this.agent.output);
236
259
  await this.checkpoint(); /* c2: terminal tick - no tool calls */
237
260
 
238
261
  // await this.agent.runOutputGuardails(context, state);
@@ -410,7 +433,7 @@ export class Thread<
410
433
  /**
411
434
  * Cancel the running thread
412
435
  *
413
- * TODO: Emit thread.stop with outcome: 'cancelled' when cancelled
436
+ * TODO: Emit thread.stop when cancelled (neither result nor error set)
414
437
  */
415
438
  cancel() {
416
439
  this.abort?.abort();
@@ -113,11 +113,10 @@ export function isPublicEvent(event: ThreadEvent): event is PublicThreadEvent {
113
113
  * Returns null if no assistant message with text content is found.
114
114
  */
115
115
  export function getFinalResponse(items: LanguageModelItem[]): string | null {
116
- // Scan backwards for the last assistant message
116
+ // scan backwards for the last assistant message
117
117
  for (let i = items.length - 1; i >= 0; i--) {
118
118
  const item = items[i];
119
119
  if (item.kind === "message" && item.role === "assistant") {
120
- // Extract text from content parts
121
120
  for (const part of item.content) {
122
121
  if (part.kind === "text") {
123
122
  return part.text;
@@ -161,6 +160,6 @@ export function parseFinalResponse<TOutput extends AgentOutputType>(
161
160
  }
162
161
  }
163
162
 
164
- // Fallback - should not reach here
163
+ // fallback - should not reach here
165
164
  return text as ResolvedAgentResponse<TOutput>;
166
165
  }