@dxos/assistant 0.9.1-main.c7dcc2e112 → 0.10.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.
Files changed (70) hide show
  1. package/dist/lib/neutral/extraction/index.mjs +1 -1
  2. package/dist/lib/neutral/extraction/index.mjs.map +1 -1
  3. package/dist/lib/neutral/index.mjs +387 -230
  4. package/dist/lib/neutral/index.mjs.map +4 -4
  5. package/dist/lib/neutral/meta.json +1 -1
  6. package/dist/types/src/extraction/extraction-llm-function/proper-noun-extraction.d.ts +1 -202
  7. package/dist/types/src/extraction/extraction-llm-function/proper-noun-extraction.d.ts.map +1 -1
  8. package/dist/types/src/index.d.ts +1 -1
  9. package/dist/types/src/index.d.ts.map +1 -1
  10. package/dist/types/src/request/AiRequest.d.ts +13 -208
  11. package/dist/types/src/request/AiRequest.d.ts.map +1 -1
  12. package/dist/types/src/request/format.d.ts +1 -1
  13. package/dist/types/src/request/format.d.ts.map +1 -1
  14. package/dist/types/src/session/AiContext.d.ts +27 -105
  15. package/dist/types/src/session/AiContext.d.ts.map +1 -1
  16. package/dist/types/src/session/AiSession.d.ts +5 -29
  17. package/dist/types/src/session/AiSession.d.ts.map +1 -1
  18. package/dist/types/src/session/Harness.d.ts +122 -0
  19. package/dist/types/src/session/Harness.d.ts.map +1 -0
  20. package/dist/types/src/session/SessionLink.d.ts +8 -11
  21. package/dist/types/src/session/SessionLink.d.ts.map +1 -1
  22. package/dist/types/src/session/SessionLoader.d.ts +2 -2
  23. package/dist/types/src/session/SessionLoader.d.ts.map +1 -1
  24. package/dist/types/src/session/SkillHooks.d.ts +28 -0
  25. package/dist/types/src/session/SkillHooks.d.ts.map +1 -0
  26. package/dist/types/src/session/harness-control.d.ts +177 -0
  27. package/dist/types/src/session/harness-control.d.ts.map +1 -0
  28. package/dist/types/src/session/index.d.ts +3 -0
  29. package/dist/types/src/session/index.d.ts.map +1 -1
  30. package/dist/types/src/session/toolkit.d.ts +4 -4
  31. package/dist/types/src/session/toolkit.d.ts.map +1 -1
  32. package/dist/types/src/templates/system.d.ts +4 -6
  33. package/dist/types/src/templates/system.d.ts.map +1 -1
  34. package/dist/types/src/tool-runtime/index.d.ts.map +1 -0
  35. package/dist/types/src/tool-runtime/services.d.ts.map +1 -0
  36. package/dist/types/src/util/artifact.d.ts.map +1 -1
  37. package/dist/types/src/util/diff.d.ts +9 -3
  38. package/dist/types/src/util/diff.d.ts.map +1 -1
  39. package/dist/types/src/util/tracing.d.ts +40 -0
  40. package/dist/types/src/util/tracing.d.ts.map +1 -1
  41. package/dist/types/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +28 -25
  43. package/src/extraction/extraction-llm-function/proper-noun-extraction.ts +1 -1
  44. package/src/index.ts +1 -1
  45. package/src/request/AiRequest.ts +35 -14
  46. package/src/request/format.ts +9 -11
  47. package/src/session/AiContext.test.ts +52 -32
  48. package/src/session/AiContext.ts +73 -95
  49. package/src/session/AiSession.test.ts +4 -4
  50. package/src/session/AiSession.ts +43 -83
  51. package/src/session/Harness.ts +241 -0
  52. package/src/session/SessionLink.ts +8 -8
  53. package/src/session/SessionLoader.ts +4 -4
  54. package/src/session/SkillHooks.ts +71 -0
  55. package/src/session/harness-control.ts +30 -0
  56. package/src/session/index.ts +3 -0
  57. package/src/session/toolkit.ts +8 -8
  58. package/src/templates/instructions/format.tpl +3 -3
  59. package/src/templates/instructions/system.tpl +10 -12
  60. package/src/templates/system.ts +6 -2
  61. package/src/util/artifact.ts +9 -2
  62. package/src/util/diff.test.ts +4 -4
  63. package/src/util/diff.ts +15 -8
  64. package/src/util/tracing.ts +1 -1
  65. package/dist/types/src/functions/index.d.ts.map +0 -1
  66. package/dist/types/src/functions/services.d.ts.map +0 -1
  67. /package/dist/types/src/{functions → tool-runtime}/index.d.ts +0 -0
  68. /package/dist/types/src/{functions → tool-runtime}/services.d.ts +0 -0
  69. /package/src/{functions → tool-runtime}/index.ts +0 -0
  70. /package/src/{functions → tool-runtime}/services.ts +0 -0
@@ -6,15 +6,13 @@
6
6
 
7
7
  import { Atom, Registry as AtomRegistry } from '@effect-atom/atom-react';
8
8
  import * as EArray from 'effect/Array';
9
- import * as Context from 'effect/Context';
10
- import * as Effect from 'effect/Effect';
11
9
  import * as Function from 'effect/Function';
12
10
  import * as Runtime from 'effect/Runtime';
13
11
  import * as Schema from 'effect/Schema';
14
12
 
15
- import { Blueprint } from '@dxos/compute';
13
+ import { Skill } from '@dxos/compute';
16
14
  import { Resource } from '@dxos/context';
17
- import { Annotation, DXN, Feed, Obj, type QueryResult, Query, Ref, Type } from '@dxos/echo';
15
+ import { Annotation, Database, DXN, Feed, Obj, Query, type QueryResult, Ref, Type } from '@dxos/echo';
18
16
  import { assertArgument } from '@dxos/invariant';
19
17
  import { EID, type URI } from '@dxos/keys';
20
18
  import { log } from '@dxos/log';
@@ -23,26 +21,26 @@ import { ComplexSet, isNonNullable } from '@dxos/util';
23
21
  /**
24
22
  * Thread message that binds or unbinds contextual objects to a conversation.
25
23
  */
26
- export const Binding = Schema.Struct({
27
- blueprints: Schema.Struct({
28
- added: Schema.Array(Ref.Ref(Blueprint.Blueprint)),
29
- removed: Schema.Array(Ref.Ref(Blueprint.Blueprint)),
30
- }),
31
-
32
- objects: Schema.Struct({
33
- added: Schema.Array(Ref.Ref(Obj.Unknown)),
34
- removed: Schema.Array(Ref.Ref(Obj.Unknown)),
35
- }),
36
- }).pipe(Annotation.HiddenAnnotation.set(true), Type.makeObject(DXN.make('org.dxos.type.contextBinding', '0.1.0')));
37
-
38
- export type Binding = Type.InstanceType<typeof Binding>;
24
+ export class Binding extends Type.makeObject<Binding>(DXN.make('org.dxos.type.contextBinding', '0.1.0'))(
25
+ Schema.Struct({
26
+ skills: Schema.Struct({
27
+ added: Schema.Array(Ref.Ref(Skill.Skill)),
28
+ removed: Schema.Array(Ref.Ref(Skill.Skill)),
29
+ }),
30
+
31
+ objects: Schema.Struct({
32
+ added: Schema.Array(Ref.Ref(Obj.Unknown)),
33
+ removed: Schema.Array(Ref.Ref(Obj.Unknown)),
34
+ }),
35
+ }).pipe(Annotation.HiddenAnnotation.set(true)),
36
+ ) {}
39
37
  export type BindingProps = Partial<{
40
- blueprints: Ref.Ref<Blueprint.Blueprint>[];
38
+ skills: Ref.Ref<Skill.Skill>[];
41
39
  objects: Ref.Ref<Obj.Unknown>[];
42
40
  }>;
43
41
 
44
42
  export class Bindings {
45
- readonly blueprints = new ComplexSet<Ref.Ref<Blueprint.Blueprint>>((ref) => ref.uri);
43
+ readonly skills = new ComplexSet<Ref.Ref<Skill.Skill>>((ref) => ref.uri);
46
44
 
47
45
  // TODO(burdon): Some DXNs have the Space prefix so only compare the object ID.
48
46
  readonly objects = new ComplexSet<Ref.Ref<Obj.Unknown>>((ref) => {
@@ -50,9 +48,9 @@ export class Bindings {
50
48
  return echoUri ? EID.getEntityId(echoUri) : undefined;
51
49
  });
52
50
 
53
- toJSON(): { blueprints: URI.URI[]; objects: URI.URI[] } {
51
+ toJSON(): { skills: URI.URI[]; objects: URI.URI[] } {
54
52
  return {
55
- blueprints: EArray.fromIterable(this.blueprints).map((ref) => ref.uri),
53
+ skills: EArray.fromIterable(this.skills).map((ref) => ref.uri),
56
54
  objects: EArray.fromIterable(this.objects).map((ref) => ref.uri),
57
55
  };
58
56
  }
@@ -60,21 +58,21 @@ export class Bindings {
60
58
 
61
59
  export type BinderOptions = {
62
60
  feed: Feed.Feed;
63
- runtime: Runtime.Runtime<Feed.FeedService>;
61
+ runtime: Runtime.Runtime<Database.Service>;
64
62
  /** @effect-atom/atom-react Registry for reactive state management. */
65
63
  registry?: AtomRegistry.Registry;
66
64
  };
67
65
 
68
66
  /**
69
- * Manages bindings of blueprints and objects to a conversation.
67
+ * Manages bindings of skills and objects to a conversation.
70
68
  */
71
69
  // TODO(burdon): Context should manage ephemeral state of bindings until prompt is issued?
72
70
  export class Binder extends Resource {
73
- private readonly _blueprints = Atom.make<Blueprint.Blueprint[]>([]).pipe(Atom.keepAlive);
71
+ private readonly _skills = Atom.make<Skill.Skill[]>([]).pipe(Atom.keepAlive);
74
72
  private readonly _objects = Atom.make<Obj.Unknown[]>([]).pipe(Atom.keepAlive);
75
73
  private readonly _registry: AtomRegistry.Registry;
76
74
  private readonly _feed: Feed.Feed;
77
- private readonly _runtime: Runtime.Runtime<Feed.FeedService>;
75
+ private readonly _runtime: Runtime.Runtime<Database.Service>;
78
76
 
79
77
  #bindingsQuery: QueryResult.QueryResult<Binding> | undefined;
80
78
 
@@ -88,10 +86,10 @@ export class Binder extends Resource {
88
86
  }
89
87
 
90
88
  /**
91
- * Returns the blueprints atom for subscription.
89
+ * Returns the skills atom for subscription.
92
90
  */
93
- get blueprints(): Atom.Atom<Blueprint.Blueprint[]> {
94
- return this._blueprints;
91
+ get skills(): Atom.Atom<Skill.Skill[]> {
92
+ return this._skills;
95
93
  }
96
94
 
97
95
  /**
@@ -102,10 +100,10 @@ export class Binder extends Resource {
102
100
  }
103
101
 
104
102
  /**
105
- * Gets the current blueprints value.
103
+ * Gets the current skills value.
106
104
  */
107
- getBlueprints(): Blueprint.Blueprint[] {
108
- return this._registry.get(this._blueprints);
105
+ getSkills(): Skill.Skill[] {
106
+ return this._registry.get(this._skills);
109
107
  }
110
108
 
111
109
  /**
@@ -116,10 +114,10 @@ export class Binder extends Resource {
116
114
  }
117
115
 
118
116
  /**
119
- * Subscribe to changes in blueprints.
117
+ * Subscribe to changes in skills.
120
118
  */
121
- subscribeBlueprints(cb: (blueprints: Blueprint.Blueprint[]) => void): () => void {
122
- return this._registry.subscribe(this._blueprints, () => cb(this._registry.get(this._blueprints)));
119
+ subscribeSkills(cb: (skills: Skill.Skill[]) => void): () => void {
120
+ return this._registry.subscribe(this._skills, () => cb(this._registry.get(this._skills)));
123
121
  }
124
122
 
125
123
  /**
@@ -153,8 +151,8 @@ export class Binder extends Resource {
153
151
  log('sync', { bindingItems: results.length });
154
152
  await this._updateBindings(results);
155
153
  log('sync complete', {
156
- blueprints: this._registry.get(this._blueprints).length,
157
- blueprintKeys: this._registry.get(this._blueprints).map((bp) => Blueprint.getKey(bp)),
154
+ skills: this._registry.get(this._skills).length,
155
+ skillKeys: this._registry.get(this._skills).map((bp) => Skill.getKey(bp)),
158
156
  });
159
157
  }
160
158
  }
@@ -169,25 +167,25 @@ export class Binder extends Resource {
169
167
 
170
168
  log('_updateBindings', {
171
169
  items: items.length,
172
- blueprintRefs: [...bindings.blueprints].map((ref) => ({ uri: ref.uri, available: ref.isAvailable })),
170
+ skillRefs: [...bindings.skills].map((ref) => ({ uri: ref.uri, available: ref.isAvailable })),
173
171
  });
174
172
 
175
173
  // Resolve references (loading them first if needed).
176
- const currentBlueprints = this._registry.get(this._blueprints);
174
+ const currentSkills = this._registry.get(this._skills);
177
175
  const currentObjects = this._registry.get(this._objects);
178
- const resolvedBlueprints = await this._resolve(bindings.blueprints, currentBlueprints);
176
+ const resolvedSkills = await this._resolve(bindings.skills, currentSkills);
179
177
  const resolvedObjects = await this._resolve(bindings.objects, currentObjects);
180
178
 
181
179
  log('_updateBindings resolved', {
182
- resolvedBlueprints: resolvedBlueprints.length,
183
- resolvedBlueprintKeys: resolvedBlueprints.map((bp) => Obj.getMeta(bp).key ?? '<missing>'),
180
+ resolvedSkills: resolvedSkills.length,
181
+ resolvedSkillKeys: resolvedSkills.map((bp) => Obj.getMeta(bp).key ?? '<missing>'),
184
182
  });
185
183
 
186
- // Drop blueprints that have no registry key — they cannot be used downstream
187
- // (e.g. tool/operation registration calls Blueprint.getKey which throws).
188
- const keyedBlueprints = resolvedBlueprints.filter((bp) => {
184
+ // Drop skills that have no registry key — they cannot be used downstream
185
+ // (e.g. tool/operation registration calls Skill.getKey which throws).
186
+ const keyedSkills = resolvedSkills.filter((bp) => {
189
187
  if (Obj.getMeta(bp).key === undefined) {
190
- log.warn('dropping blueprint with no meta key', { uri: Obj.getURI(bp) });
188
+ log.warn('dropping skill with no meta key', { uri: Obj.getURI(bp) });
191
189
  return false;
192
190
  }
193
191
  return true;
@@ -195,54 +193,54 @@ export class Binder extends Resource {
195
193
 
196
194
  // Filter current state to only items still in the reduced binding set,
197
195
  // then merge in newly resolved items. This ensures unbind events are respected.
198
- const reducedBlueprintDxns = new Set<URI.URI>([...bindings.blueprints].map((ref) => ref.uri));
196
+ const reducedSkillDxns = new Set<URI.URI>([...bindings.skills].map((ref) => ref.uri));
199
197
  const reducedObjectDxns = new Set<URI.URI>([...bindings.objects].map((ref) => ref.uri));
200
- const filteredBlueprints = currentBlueprints.filter((obj) => {
198
+ const filteredSkills = currentSkills.filter((obj) => {
201
199
  const uri = Obj.getURI(obj);
202
- return uri != null && reducedBlueprintDxns.has(uri) && Obj.getMeta(obj).key !== undefined;
200
+ return uri != null && reducedSkillDxns.has(uri) && Obj.getMeta(obj).key !== undefined;
203
201
  });
204
202
  const filteredObjects = currentObjects.filter((obj) => {
205
203
  const uri = Obj.getURI(obj);
206
204
  return uri != null && reducedObjectDxns.has(uri);
207
205
  });
208
- const mergedBlueprints = this._mergeInto(filteredBlueprints, keyedBlueprints);
206
+ const mergedSkills = this._mergeInto(filteredSkills, keyedSkills);
209
207
  const mergedObjects = this._mergeInto(filteredObjects, resolvedObjects);
210
208
 
211
- this._registry.set(this._blueprints, mergedBlueprints);
209
+ this._registry.set(this._skills, mergedSkills);
212
210
  this._registry.set(this._objects, mergedObjects);
213
211
 
214
212
  log('updated bindings', {
215
- blueprints: mergedBlueprints.length,
213
+ skills: mergedSkills.length,
216
214
  objects: mergedObjects.length,
217
215
  });
218
216
  }
219
217
 
220
218
  protected override async _close(): Promise<void> {
221
219
  // Reset atoms to empty state.
222
- this._registry.set(this._blueprints, []);
220
+ this._registry.set(this._skills, []);
223
221
  this._registry.set(this._objects, []);
224
222
  }
225
223
 
226
- async bind({ blueprints, objects }: BindingProps): Promise<void> {
227
- const currentBlueprints = this._registry.get(this._blueprints);
224
+ async bind({ skills, objects }: BindingProps): Promise<void> {
225
+ const currentSkills = this._registry.get(this._skills);
228
226
  const currentObjects = this._registry.get(this._objects);
229
227
 
230
- const { added: addedBlueprints, next: nextBlueprints } = this._processBindings(blueprints, currentBlueprints);
228
+ const { added: addedSkills, next: nextSkills } = this._processBindings(skills, currentSkills);
231
229
  const { added: addedObjects, next: nextObjects } = this._processBindings(objects, currentObjects);
232
- if (!addedBlueprints.length && !addedObjects.length) {
230
+ if (!addedSkills.length && !addedObjects.length) {
233
231
  return;
234
232
  }
235
233
 
236
234
  // Atomic updates - subscribers notified automatically.
237
- this._registry.set(this._blueprints, nextBlueprints);
235
+ this._registry.set(this._skills, nextSkills);
238
236
  this._registry.set(this._objects, nextObjects);
239
237
 
240
- log('bind', { blueprints: addedBlueprints.length, objects: addedObjects.length });
238
+ log('bind', { skills: addedSkills.length, objects: addedObjects.length });
241
239
  await Runtime.runPromise(this._runtime)(
242
240
  Feed.append(this._feed, [
243
241
  Obj.make(Binding, {
244
- blueprints: {
245
- added: addedBlueprints,
242
+ skills: {
243
+ added: addedSkills,
246
244
  removed: [],
247
245
  },
248
246
  objects: {
@@ -254,19 +252,19 @@ export class Binder extends Resource {
254
252
  );
255
253
  }
256
254
 
257
- async unbind({ blueprints, objects }: BindingProps): Promise<void> {
258
- if (!blueprints?.length && !objects?.length) {
255
+ async unbind({ skills, objects }: BindingProps): Promise<void> {
256
+ if (!skills?.length && !objects?.length) {
259
257
  return;
260
258
  }
261
259
 
262
260
  // Immediately update atom state so removals are reflected before the queue round-trips.
263
- const removedBlueprintDxns = (blueprints ?? []).map((ref) => ref.uri);
261
+ const removedSkillDxns = (skills ?? []).map((ref) => ref.uri);
264
262
  const removedObjectDxns = (objects ?? []).map((ref) => ref.uri);
265
- if (removedBlueprintDxns.length > 0) {
266
- const current = this._registry.get(this._blueprints);
263
+ if (removedSkillDxns.length > 0) {
264
+ const current = this._registry.get(this._skills);
267
265
  this._registry.set(
268
- this._blueprints,
269
- current.filter((obj) => !removedBlueprintDxns.some((uri) => Obj.getURI(obj) === uri)),
266
+ this._skills,
267
+ current.filter((obj) => !removedSkillDxns.some((uri) => Obj.getURI(obj) === uri)),
270
268
  );
271
269
  }
272
270
  if (removedObjectDxns.length > 0) {
@@ -277,13 +275,13 @@ export class Binder extends Resource {
277
275
  );
278
276
  }
279
277
 
280
- log('unbind', { blueprints: blueprints?.length, objects: objects?.length });
278
+ log('unbind', { skills: skills?.length, objects: objects?.length });
281
279
  await Runtime.runPromise(this._runtime)(
282
280
  Feed.append(this._feed, [
283
281
  Obj.make(Binding, {
284
- blueprints: {
282
+ skills: {
285
283
  added: [],
286
- removed: blueprints ?? [],
284
+ removed: skills ?? [],
287
285
  },
288
286
  objects: {
289
287
  added: [],
@@ -329,14 +327,14 @@ export class Binder extends Resource {
329
327
  }
330
328
 
331
329
  /**
332
- * Reduce results into sets of blueprints and objects.
330
+ * Reduce results into sets of skills and objects.
333
331
  */
334
332
  private _reduce(items: Binding[]): Bindings {
335
333
  return Function.pipe(
336
334
  items,
337
- EArray.reduce(new Bindings(), (context, { blueprints, objects }) => {
338
- blueprints.added.forEach((ref) => context.blueprints.add(ref));
339
- blueprints.removed.forEach((ref) => context.blueprints.delete(ref));
335
+ EArray.reduce(new Bindings(), (context, { skills, objects }) => {
336
+ skills.added.forEach((ref) => context.skills.add(ref));
337
+ skills.removed.forEach((ref) => context.skills.delete(ref));
340
338
 
341
339
  objects.added.forEach((ref) => context.objects.add(ref));
342
340
  objects.removed.forEach((ref) => {
@@ -375,7 +373,7 @@ export class Binder extends Resource {
375
373
 
376
374
  /**
377
375
  * Resolve references to objects, loading them first if needed and falling back to existing objects.
378
- * DXN refs (e.g. `dxn:org.dxos.blueprint.database`) resolve via the wired-up ECHO ref resolver
376
+ * DXN refs (e.g. `dxn:org.dxos.skill.database`) resolve via the wired-up ECHO ref resolver
379
377
  * which already spans both the space DB and the hypergraph registry.
380
378
  */
381
379
  private async _resolve<T extends Obj.Unknown>(refs: Iterable<Ref.Ref<T>>, current: T[]): Promise<T[]> {
@@ -398,23 +396,3 @@ export class Binder extends Resource {
398
396
  .filter(isNonNullable);
399
397
  }
400
398
  }
401
-
402
- export class Service extends Context.Tag('@dxos/assistant/AiContextService')<
403
- Service,
404
- {
405
- binder: Binder;
406
- }
407
- >() {
408
- static bindContext = ({ blueprints, objects }: BindingProps): Effect.Effect<void, never, Service> =>
409
- Effect.gen(function* () {
410
- const { binder } = yield* Service;
411
- yield* Effect.promise(() => binder.bind({ blueprints, objects }));
412
- });
413
-
414
- static findObjects = <T extends Type.AnyObj>(type: T): Effect.Effect<Type.InstanceType<T>[], never, Service> => {
415
- return Effect.gen(function* () {
416
- const { binder } = yield* Service;
417
- return binder.getObjects().filter(Obj.instanceOf(type));
418
- });
419
- };
420
- }
@@ -27,7 +27,7 @@ describe('AiSession.Session.getHistory', () => {
27
27
  const message = makeMessage('hello');
28
28
  yield* Feed.append(feed, [message]);
29
29
 
30
- const runtime = yield* Effect.runtime<Feed.FeedService>();
30
+ const runtime = yield* Effect.runtime<Database.Service>();
31
31
  const session = new AiSession.Session({ feed, runtime });
32
32
 
33
33
  const result = yield* Effect.promise(() => session.getHistory());
@@ -51,7 +51,7 @@ describe('AiSession.Session.getHistory', () => {
51
51
  forkMsg,
52
52
  ]);
53
53
 
54
- const runtime = yield* Effect.runtime<Feed.FeedService>();
54
+ const runtime = yield* Effect.runtime<Database.Service>();
55
55
  const session = new AiSession.Session({ feed: forkFeed, runtime });
56
56
 
57
57
  const result = yield* Effect.promise(() => session.getHistory());
@@ -76,7 +76,7 @@ describe('AiSession.Session.getHistory', () => {
76
76
  Obj.make(SessionLink.SessionLink, { feedRef: Ref.make(sourceFeed), messageId: msg2.id }),
77
77
  ]);
78
78
 
79
- const runtime = yield* Effect.runtime<Feed.FeedService>();
79
+ const runtime = yield* Effect.runtime<Database.Service>();
80
80
  const session = new AiSession.Session({ feed: forkFeed, runtime });
81
81
 
82
82
  const result = yield* Effect.promise(() => session.getHistory());
@@ -100,7 +100,7 @@ describe('AiSession.Session.getHistory', () => {
100
100
  forkMsg,
101
101
  ]);
102
102
 
103
- const runtime = yield* Effect.runtime<Feed.FeedService>();
103
+ const runtime = yield* Effect.runtime<Database.Service>();
104
104
  const session = new AiSession.Session({ feed: forkFeed, runtime });
105
105
 
106
106
  const result = yield* Effect.promise(() => session.getHistory());
@@ -6,7 +6,6 @@
6
6
 
7
7
  import { type Registry as AtomRegistry } from '@effect-atom/atom-react';
8
8
  import * as Array from 'effect/Array';
9
- import * as Context from 'effect/Context';
10
9
  import * as Effect from 'effect/Effect';
11
10
  import * as Either from 'effect/Either';
12
11
  import { pipe } from 'effect/Function';
@@ -15,20 +14,21 @@ import * as Record from 'effect/Record';
15
14
  import * as Runtime from 'effect/Runtime';
16
15
 
17
16
  import { type OpaqueToolkit, type ToolExecutionService, type ToolResolverService } from '@dxos/ai';
18
- import { type Blueprint, McpServer, Operation, Trace } from '@dxos/compute';
17
+ import { McpServer, Operation, type Skill, Trace } from '@dxos/compute';
19
18
  import { Resource } from '@dxos/context';
20
19
  import { Database, Feed, Filter, Obj, Registry } from '@dxos/echo';
21
- import { EffectEx } from '@dxos/effect';
22
20
  import { invariant } from '@dxos/invariant';
23
21
  import { log } from '@dxos/log';
24
22
  import { McpToolkit } from '@dxos/mcp-client';
25
- import { Message, type ContentBlock } from '@dxos/types';
23
+ import { type ContentBlock, Message } from '@dxos/types';
26
24
 
27
- import { ToolExecutionServices } from '../functions';
28
25
  import { AiRequest, type GenerationObserver, formatSystemPrompt } from '../request';
26
+ import { ToolExecutionServices } from '../tool-runtime';
29
27
  import { McpServerError } from '../util';
30
28
  import * as AiContext from './AiContext';
29
+ import * as Harness from './Harness';
31
30
  import { SessionLoader } from './SessionLoader';
31
+ import * as SkillHooks from './SkillHooks';
32
32
  import { createToolkit } from './toolkit';
33
33
 
34
34
  export type RunProps<R = never> = {
@@ -38,7 +38,7 @@ export type RunProps<R = never> = {
38
38
  toolkit?: OpaqueToolkit.OpaqueToolkit<R>;
39
39
 
40
40
  /**
41
- * Space-level MCP servers to connect alongside blueprint-defined ones.
41
+ * Space-level MCP servers to connect alongside skill-defined ones.
42
42
  */
43
43
  mcpServers?: readonly McpServer.McpServer[];
44
44
 
@@ -52,7 +52,7 @@ export type RunProps<R = never> = {
52
52
 
53
53
  export type Options = {
54
54
  feed: Feed.Feed;
55
- runtime: Runtime.Runtime<Feed.FeedService>;
55
+ runtime: Runtime.Runtime<Database.Service>;
56
56
  /** @effect-atom/atom-react Registry for reactive state. */
57
57
  registry?: AtomRegistry.Registry;
58
58
  };
@@ -69,11 +69,11 @@ const SUMMARY_THRESHOLD = 80_000;
69
69
  */
70
70
  export class Session extends Resource {
71
71
  /**
72
- * Blueprints and objects bound to the session.
72
+ * Skills and objects bound to the session.
73
73
  */
74
74
  private readonly _binder: AiContext.Binder;
75
75
  private readonly _feed: Feed.Feed;
76
- private readonly _runtime: Runtime.Runtime<Feed.FeedService>;
76
+ private readonly _runtime: Runtime.Runtime<Database.Service>;
77
77
  private readonly _sessionLoader = new SessionLoader();
78
78
 
79
79
  public constructor(options: Options) {
@@ -114,7 +114,7 @@ export class Session extends Resource {
114
114
  ToolExecutionService | ToolResolverService
115
115
  > {
116
116
  return Effect.gen(this, function* () {
117
- const toolkit = yield* createToolkit({ blueprints: this.context.getBlueprints() });
117
+ const toolkit = yield* createToolkit({ skills: this.context.getSkills() });
118
118
  return toolkit.toolkit.tools;
119
119
  }).pipe(Effect.orDie);
120
120
  }
@@ -140,12 +140,12 @@ export class Session extends Resource {
140
140
  ): Effect.Effect<Message.Message[], AiRequest.RunError, AiRequest.RunRequirements | R> {
141
141
  return Effect.gen(this, function* () {
142
142
  const history = yield* Effect.promise(() => this.getHistory());
143
- const blueprints = this.context.getBlueprints();
143
+ const skills = this.context.getSkills();
144
144
  const objects = this.context.getObjects();
145
145
 
146
146
  log('run', {
147
147
  history: history.length,
148
- blueprints: blueprints.length,
148
+ skills: skills.length,
149
149
  objects: objects.length,
150
150
  });
151
151
 
@@ -159,34 +159,47 @@ export class Session extends Resource {
159
159
 
160
160
  yield* request.begin({
161
161
  history,
162
- blueprints,
162
+ skills,
163
163
  objects,
164
164
  prompt: params.prompt,
165
165
  system: params.system,
166
166
  });
167
167
 
168
- // Turn loop: recompute toolkit and system prompt between turns to pick up dynamically enabled blueprints.
168
+ // Fire begin-request hooks declared by the bound skills. These run in the agent's turn
169
+ // fiber (Tier A only), so they cannot reach the live host (Tier B) — that is the end hook's job.
170
+ yield* SkillHooks.runHooks({
171
+ skills,
172
+ phase: 'begin-request',
173
+ invoke: (operation, input) => Operation.invoke(operation, input).pipe(Effect.asVoid, Effect.orDie),
174
+ });
175
+
176
+ // Turn loop: recompute toolkit and system prompt between turns to pick up dynamically enabled skills.
169
177
  do {
170
178
  yield* Effect.promise(() => this.context.sync());
171
- const currentBlueprints = this.context.getBlueprints();
172
- const mcps = yield* connectMcpServers(currentBlueprints, params.mcpServers);
179
+ const currentSkills = this.context.getSkills();
180
+ const mcps = yield* connectMcpServers(currentSkills, params.mcpServers);
173
181
  const toolkit = yield* createToolkit({
174
182
  toolkit: params.toolkit,
175
- blueprints: currentBlueprints,
183
+ skills: currentSkills,
176
184
  opaqueToolkits: mcps,
177
185
  });
178
186
 
179
187
  log('toolkit', { tools: Record.keys(toolkit.toolkit.tools) });
180
188
  const system = yield* formatSystemPrompt({
181
189
  system: params.system,
182
- blueprints: currentBlueprints,
190
+ skills: currentSkills,
183
191
  objects: this.context.getObjects(),
184
192
  }).pipe(Effect.orDie);
185
193
 
186
- const { done } = yield* request.runAgentTurn({ system, toolkit });
194
+ const { done, finishReason } = yield* request.runAgentTurn({ system, toolkit });
187
195
  if (done) {
188
196
  break;
189
197
  }
198
+ // A paused server-tool turn (e.g. Anthropic `pause_turn`) resumes with another request and
199
+ // no local tool execution; the trailing server tool call must be left intact for the provider.
200
+ if (finishReason === 'pause') {
201
+ continue;
202
+ }
190
203
 
191
204
  yield* request.runTools({ toolkit });
192
205
  } while (true);
@@ -201,10 +214,13 @@ export class Session extends Resource {
201
214
  }).pipe(
202
215
  Effect.provide(
203
216
  Layer.mergeAll(
204
- Layer.succeed(AiContext.Service, {
205
- binder: this.context,
206
- }),
207
- Layer.succeed(Service, this),
217
+ // Tier A only: the agent's own turn fiber has no `ProcessManager.Service` in scope, so
218
+ // Tier B (setAlarm/enqueueMessage) is reachable only by child operations through the
219
+ // process-affinity HarnessService LayerSpec — not from here.
220
+ Layer.succeed(
221
+ Harness.HarnessService,
222
+ Harness.fromBinder({ feed: this._feed, runtime: this._runtime, binder: this.context }),
223
+ ),
208
224
  Operation.withInvocationOptions({ conversation: Obj.getURI(this._feed) }),
209
225
  ),
210
226
  ),
@@ -213,68 +229,12 @@ export class Session extends Resource {
213
229
  }
214
230
  }
215
231
 
216
- /**
217
- * Gives access to the ai session.
218
- */
219
- export class Service extends Context.Tag('@dxos/assistant/AiSessionService')<Service, Session>() {
220
- /**
221
- * Create a new session layer from options.
222
- */
223
- static layer = (options: Options): Layer.Layer<Service | AiContext.Service> =>
224
- aiContextFromSession.pipe(
225
- Layer.provideMerge(
226
- Layer.scoped(
227
- Service,
228
- Effect.gen(function* () {
229
- const session = yield* EffectEx.acquireReleaseResource(() => new Session(options));
230
- return session;
231
- }),
232
- ),
233
- ),
234
- );
235
-
236
- /**
237
- * Create a new session with a new feed.
238
- */
239
- static layerNewFeed = (
240
- options?: Omit<Options, 'feed' | 'runtime'>,
241
- ): Layer.Layer<Service | AiContext.Service, never, Database.Service | Feed.FeedService> =>
242
- Layer.unwrapScoped(
243
- Effect.gen(function* () {
244
- const feed = yield* Database.add(Feed.make());
245
- const runtime = yield* Effect.runtime<Feed.FeedService>();
246
- return Service.layer({ ...options, feed, runtime });
247
- }),
248
- );
249
-
250
- /**
251
- * Run a prompt in the current session.
252
- */
253
- static run = <R = never>(
254
- params: RunProps<R>,
255
- ): Effect.Effect<Message.Message[], AiRequest.RunError, AiRequest.RunRequirements | Service | R> =>
256
- Effect.gen(function* () {
257
- const session = yield* Service;
258
- return yield* session.createRequest(params);
259
- });
260
- }
261
-
262
- const aiContextFromSession = Layer.effect(
263
- AiContext.Service,
264
- Effect.gen(function* () {
265
- const session = yield* Service;
266
- return {
267
- binder: session.context,
268
- };
269
- }),
270
- );
271
-
272
232
  const connectMcpServers = (
273
- blueprints: readonly Blueprint.Blueprint[],
233
+ skills: readonly Skill.Skill[],
274
234
  spaceMcpServers: readonly McpServer.McpServer[] = [],
275
235
  ): Effect.Effect<OpaqueToolkit.OpaqueToolkit[], never, Trace.TraceService> => {
276
- const blueprintServers: McpToolkit.McpToolkitOptions[] = pipe(
277
- blueprints,
236
+ const skillServers: McpToolkit.McpToolkitOptions[] = pipe(
237
+ skills,
278
238
  Array.flatMap((_) => _.mcpServers ?? []),
279
239
  Array.map(({ url, protocol, apiKey }) => ({ url, protocol, apiKey })),
280
240
  );
@@ -283,7 +243,7 @@ const connectMcpServers = (
283
243
  protocol,
284
244
  apiKey,
285
245
  }));
286
- const allServers = [...blueprintServers, ...spaceServers];
246
+ const allServers = [...skillServers, ...spaceServers];
287
247
 
288
248
  return pipe(
289
249
  allServers,