@owlmeans/agent 0.1.18-rc.11

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 (111) hide show
  1. package/README.md +93 -0
  2. package/agent-meta/manifest.json +16 -0
  3. package/agent-meta/skills/agent/SKILL.md +122 -0
  4. package/build/consts.d.ts +16 -0
  5. package/build/consts.d.ts.map +1 -0
  6. package/build/consts.js +16 -0
  7. package/build/consts.js.map +1 -0
  8. package/build/errors.d.ts +16 -0
  9. package/build/errors.d.ts.map +1 -0
  10. package/build/errors.js +27 -0
  11. package/build/errors.js.map +1 -0
  12. package/build/helpers/compaction.d.ts +46 -0
  13. package/build/helpers/compaction.d.ts.map +1 -0
  14. package/build/helpers/compaction.js +119 -0
  15. package/build/helpers/compaction.js.map +1 -0
  16. package/build/helpers/index.d.ts +4 -0
  17. package/build/helpers/index.d.ts.map +1 -0
  18. package/build/helpers/index.js +4 -0
  19. package/build/helpers/index.js.map +1 -0
  20. package/build/helpers/rolling.d.ts +25 -0
  21. package/build/helpers/rolling.d.ts.map +1 -0
  22. package/build/helpers/rolling.js +45 -0
  23. package/build/helpers/rolling.js.map +1 -0
  24. package/build/helpers/tools.d.ts +29 -0
  25. package/build/helpers/tools.d.ts.map +1 -0
  26. package/build/helpers/tools.js +36 -0
  27. package/build/helpers/tools.js.map +1 -0
  28. package/build/index.d.ts +12 -0
  29. package/build/index.d.ts.map +1 -0
  30. package/build/index.js +11 -0
  31. package/build/index.js.map +1 -0
  32. package/build/model.d.ts +15 -0
  33. package/build/model.d.ts.map +1 -0
  34. package/build/model.js +208 -0
  35. package/build/model.js.map +1 -0
  36. package/build/plugins/export.d.ts +8 -0
  37. package/build/plugins/export.d.ts.map +1 -0
  38. package/build/plugins/export.js +4 -0
  39. package/build/plugins/export.js.map +1 -0
  40. package/build/plugins/memory-events.d.ts +36 -0
  41. package/build/plugins/memory-events.d.ts.map +1 -0
  42. package/build/plugins/memory-events.js +83 -0
  43. package/build/plugins/memory-events.js.map +1 -0
  44. package/build/plugins/memory-graph.d.ts +52 -0
  45. package/build/plugins/memory-graph.d.ts.map +1 -0
  46. package/build/plugins/memory-graph.js +155 -0
  47. package/build/plugins/memory-graph.js.map +1 -0
  48. package/build/plugins/summarize.d.ts +44 -0
  49. package/build/plugins/summarize.d.ts.map +1 -0
  50. package/build/plugins/summarize.js +77 -0
  51. package/build/plugins/summarize.js.map +1 -0
  52. package/build/runtime/checkpoint.d.ts +37 -0
  53. package/build/runtime/checkpoint.d.ts.map +1 -0
  54. package/build/runtime/checkpoint.js +52 -0
  55. package/build/runtime/checkpoint.js.map +1 -0
  56. package/build/runtime/provider.d.ts +18 -0
  57. package/build/runtime/provider.d.ts.map +1 -0
  58. package/build/runtime/provider.js +30 -0
  59. package/build/runtime/provider.js.map +1 -0
  60. package/build/runtime/transport.d.ts +27 -0
  61. package/build/runtime/transport.d.ts.map +1 -0
  62. package/build/runtime/transport.js +29 -0
  63. package/build/runtime/transport.js.map +1 -0
  64. package/build/service.d.ts +14 -0
  65. package/build/service.d.ts.map +1 -0
  66. package/build/service.js +61 -0
  67. package/build/service.js.map +1 -0
  68. package/build/stores/index.d.ts +3 -0
  69. package/build/stores/index.d.ts.map +1 -0
  70. package/build/stores/index.js +2 -0
  71. package/build/stores/index.js.map +1 -0
  72. package/build/stores/memory.d.ts +6 -0
  73. package/build/stores/memory.d.ts.map +1 -0
  74. package/build/stores/memory.js +0 -0
  75. package/build/stores/memory.js.map +1 -0
  76. package/build/stores/types.d.ts +38 -0
  77. package/build/stores/types.d.ts.map +1 -0
  78. package/build/stores/types.js +2 -0
  79. package/build/stores/types.js.map +1 -0
  80. package/build/types.d.ts +130 -0
  81. package/build/types.d.ts.map +1 -0
  82. package/build/types.js +2 -0
  83. package/build/types.js.map +1 -0
  84. package/package.json +72 -0
  85. package/src/consts.ts +19 -0
  86. package/src/errors.ts +33 -0
  87. package/src/helpers/compaction.ts +172 -0
  88. package/src/helpers/index.ts +3 -0
  89. package/src/helpers/rolling.ts +68 -0
  90. package/src/helpers/tools.ts +46 -0
  91. package/src/index.ts +11 -0
  92. package/src/model.ts +269 -0
  93. package/src/plugins/export.ts +12 -0
  94. package/src/plugins/memory-events.ts +129 -0
  95. package/src/plugins/memory-graph.ts +217 -0
  96. package/src/plugins/summarize.ts +129 -0
  97. package/src/runtime/checkpoint.ts +89 -0
  98. package/src/runtime/provider.ts +35 -0
  99. package/src/runtime/transport.ts +50 -0
  100. package/src/service.ts +97 -0
  101. package/src/stores/index.ts +2 -0
  102. package/src/stores/memory.ts +0 -0
  103. package/src/stores/types.ts +45 -0
  104. package/src/types.ts +144 -0
  105. package/tests/_tools/model.ts +50 -0
  106. package/tests/agent.spec.ts +218 -0
  107. package/tests/plugins.spec.ts +257 -0
  108. package/tests/runtime.spec.ts +140 -0
  109. package/tests/summary.spec.ts +143 -0
  110. package/tests/tools.spec.ts +68 -0
  111. package/tsconfig.json +19 -0
@@ -0,0 +1,6 @@
1
+ import type { AgentRunStateStore, ConversationStore, MemoryEventStore, MemoryGraphStore } from './types.js';
2
+ export declare const createMemoryConversationStore: () => ConversationStore;
3
+ export declare const createMemoryGraphStore: () => MemoryGraphStore;
4
+ export declare const createMemoryEventStore: () => MemoryEventStore;
5
+ export declare const createMemoryRunStateStore: () => AgentRunStateStore;
6
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAC1E,MAAM,YAAY,CAAA;AAanB,eAAO,MAAM,6BAA6B,QAAO,iBAsBhD,CAAA;AAED,eAAO,MAAM,sBAAsB,QAAO,gBAqBzC,CAAA;AAED,eAAO,MAAM,sBAAsB,QAAO,gBA8BzC,CAAA;AAED,eAAO,MAAM,yBAAyB,QAAO,kBAO5C,CAAA"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { AgentRunState, ConversationEvent, ConversationEventInput, ConversationRef, MemoryEvent, MemoryEventInput, MemoryNode } from '@owlmeans/agent-common';
2
+ /**
3
+ * Storage, as this package needs it.
4
+ *
5
+ * These are PORTS, not resources. The package could have taken `Resource<T>` and let a consumer
6
+ * register a backend under an alias — but `Resource.list()` is not uniformly queryable
7
+ * (`@owlmeans/static-resource` throws on any criteria), so a plugin written against its query
8
+ * semantics could not be exercised with the monorepo's own in-memory backend. A port names what
9
+ * the plugin actually needs, which is a much smaller surface than CRUD, and any backend can
10
+ * satisfy it — including a file on disk, which is what the project-history equivalent is.
11
+ *
12
+ * Every port is optional to bind. A plugin whose port is missing degrades to a no-op rather than
13
+ * throwing, exactly as `ExecutionService.checkpoint` does with no plugin registered: memory is an
14
+ * enhancement, and an application that has not wired storage yet must still be able to run agents.
15
+ */
16
+ export interface ConversationStore {
17
+ /** The most recent `limit` events, NEWEST FIRST. */
18
+ last: (ref: ConversationRef, limit: number) => Promise<ConversationEvent[]>;
19
+ /** Append one event, allocating its `seq`. */
20
+ append: (event: ConversationEventInput) => Promise<ConversationEvent>;
21
+ }
22
+ export interface MemoryGraphStore {
23
+ /** Every node of a scope, without its content — names and links only. */
24
+ index: (scope: string) => Promise<Array<Pick<MemoryNode, 'subsystem' | 'links' | 'updatedAt'>>>;
25
+ read: (scope: string, subsystem: string) => Promise<MemoryNode | null>;
26
+ write: (node: Omit<MemoryNode, 'id' | 'updatedAt'>) => Promise<MemoryNode>;
27
+ }
28
+ export interface MemoryEventStore {
29
+ /** The most recent `limit` events of a scope, NEWEST FIRST. */
30
+ read: (scope: string, limit: number) => Promise<MemoryEvent[]>;
31
+ /** Append one event, allocating its `seq`, and prune the scope to `limit` if given. */
32
+ append: (event: MemoryEventInput, limit?: number) => Promise<MemoryEvent>;
33
+ }
34
+ export interface AgentRunStateStore {
35
+ load: (runId: string) => Promise<AgentRunState | null>;
36
+ save: (state: AgentRunState) => Promise<void>;
37
+ }
38
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/stores/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,eAAe,EACzE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAC1C,MAAM,wBAAwB,CAAA;AAE/B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,IAAI,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAA;IAC3E,8CAA8C;IAC9C,MAAM,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAA;CACtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAC/F,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IACtE,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,WAAW,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;CAC3E;AAED,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IAC9D,uFAAuF;IACvF,MAAM,EAAE,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;CAC1E;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAA;IACtD,IAAI,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/stores/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,130 @@
1
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
2
+ import type { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
3
+ import type { StructuredToolInterface } from '@langchain/core/tools';
4
+ import type { BasicConfig, BasicContext, InitializedService } from '@owlmeans/context';
5
+ import type { FlowModel, FlowProvider, ShallowFlow } from '@owlmeans/flow';
6
+ import type { Execution, LlmPlugin, ModelInputItem, PromptService } from '@owlmeans/llm';
7
+ import type { AgentRunStatus, ConversationEvent, ConversationRef } from '@owlmeans/agent-common';
8
+ import type { AgentTransport } from './runtime/transport.js';
9
+ import type { ConversationStore } from './stores/types.js';
10
+ /** Tools an agent may call, keyed however the caller likes — resolution is by `tool.name`. */
11
+ export interface AgentToolSet {
12
+ [key: string]: StructuredToolInterface;
13
+ }
14
+ /**
15
+ * What the caller gets told about each model call.
16
+ *
17
+ * Shaped to match `spectate(spectator, callType)` from `@owlmeans/llm` exactly, so an application
18
+ * that already has a spectator passes the curried function straight in.
19
+ */
20
+ export interface AgentSpectateHook {
21
+ (input: ModelInputItem[], message: AIMessage, action: string, retries: number, startedAt?: number): Promise<unknown>;
22
+ }
23
+ export interface AgentOptions {
24
+ /** The execution the run belongs to. Its `prompt` policy is the agent's persona. */
25
+ exec: Execution;
26
+ /** Overrides the model resolved from the execution. */
27
+ agentModel?: BaseChatModel;
28
+ tools: AgentToolSet;
29
+ /** Static volatile context. Lands in `PromptBlock.Context`, never in the cached prefix. */
30
+ context?: string[];
31
+ conversation?: ConversationRef;
32
+ /** LangGraph entrypoint name; shows up in traces. */
33
+ entrypoint?: string;
34
+ spectate?: AgentSpectateHook;
35
+ prompts?: () => PromptService;
36
+ /** Provider plugin used for cache placement. Resolved from the model when omitted. */
37
+ provider?: LlmPlugin;
38
+ maxTurns?: number;
39
+ /**
40
+ * Whether `invoke()` finalizes the run itself.
41
+ *
42
+ * Leave it on for a caller whose work ends when the model stops talking. Turn it OFF when
43
+ * something runs AFTER the agent that changes the outcome — a validation pass, a build — because
44
+ * a compaction written before that step describes a state that did not survive it, and the
45
+ * "what to do next" it produces is then advice about a world that no longer exists.
46
+ */
47
+ autoFinish?: boolean;
48
+ plugins?: AgentPlugin[];
49
+ }
50
+ export interface AgentInvokeArgs {
51
+ /** LangChain `runName` for the model calls of this run. */
52
+ action?: string;
53
+ /** Extra volatile context for this call only. */
54
+ context?: string[];
55
+ }
56
+ export interface AgentRunOutcome {
57
+ status: AgentRunStatus;
58
+ /** What happened after the loop — a fixer verdict, a build result. Reaches the compaction. */
59
+ note?: string;
60
+ error?: Error;
61
+ }
62
+ /** What a plugin sees. Deliberately carries no service: a model built standalone has none. */
63
+ export interface AgentRun {
64
+ id: string;
65
+ conversation: ConversationRef;
66
+ exec: Execution;
67
+ flow: FlowModel;
68
+ /** The ask that opened the run. */
69
+ prompt: string;
70
+ action: string;
71
+ }
72
+ export interface AgentRunHandle {
73
+ id: string;
74
+ conversation: ConversationRef;
75
+ /** Fires `onFinish` on every plugin. Idempotent — a second call is a no-op, never a second event. */
76
+ finish: (outcome: AgentRunOutcome) => Promise<void>;
77
+ }
78
+ export interface AgentResult {
79
+ message: AIMessage;
80
+ /** The whole transcript of the run, the opening human message included. */
81
+ messages: BaseMessage[];
82
+ run: AgentRunHandle;
83
+ }
84
+ /**
85
+ * The package's own optional-capability seam.
86
+ *
87
+ * A plugin may contribute what an agent knows (`context`), what it can do (`tools`), watch it work
88
+ * (`onTurn`), and act when it stops (`onFinish`). Everything memory- and summary-related in this
89
+ * family is one of these; nothing in the loop itself knows those features exist.
90
+ */
91
+ export interface AgentPlugin {
92
+ alias: string;
93
+ /** Lower runs first. Defaults to 50. */
94
+ order?: number;
95
+ context?: (run: AgentRun) => Promise<string[]>;
96
+ tools?: (run: AgentRun) => AgentToolSet;
97
+ onTurn?: (run: AgentRun, messages: readonly BaseMessage[]) => Promise<void>;
98
+ onFinish?: (run: AgentRun, result: AgentResult, outcome: AgentRunOutcome) => Promise<void>;
99
+ }
100
+ export interface AgentModel {
101
+ use: (plugin: AgentPlugin) => void;
102
+ invoke: (input: string | HumanMessage, args?: AgentInvokeArgs) => Promise<AgentResult>;
103
+ conversation: () => ConversationRef;
104
+ }
105
+ export interface ConversationApi {
106
+ last: (limit?: number) => Promise<ConversationEvent[]>;
107
+ append: (event: Omit<ConversationEvent, 'id' | 'seq' | 'createdAt'>) => Promise<ConversationEvent>;
108
+ }
109
+ export interface AgentServiceOptions {
110
+ /** Extra flows the provider should serve. The run lifecycle flow is always included. */
111
+ flows?: ShallowFlow[];
112
+ transport?: AgentTransport;
113
+ plugins?: AgentPlugin[];
114
+ conversations?: ConversationStore;
115
+ }
116
+ export interface AgentService extends InitializedService {
117
+ /** Build an agent with the service's plugins already attached. */
118
+ agent: (options: AgentOptions) => AgentModel;
119
+ use: (plugin: AgentPlugin) => void;
120
+ plugins: () => AgentPlugin[];
121
+ flow: FlowProvider;
122
+ transport: () => AgentTransport;
123
+ /** Conversation access for callers that want it outside a run. No store bound → empty results. */
124
+ conversation: (ref: ConversationRef) => ConversationApi;
125
+ }
126
+ export interface WithAgentsService {
127
+ agents: () => AgentService;
128
+ }
129
+ export type AgentContext<C extends BasicConfig = BasicConfig> = BasicContext<C> & WithAgentsService;
130
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@owlmeans/agent",
3
+ "version": "0.1.18-rc.11",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc -b",
8
+ "dev": "sleep 178 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
9
+ "watch": "tsc -b -w --preserveWatchOutput --pretty",
10
+ "test": "bun test ./tests"
11
+ },
12
+ "main": "build/index.js",
13
+ "module": "build/index.js",
14
+ "types": "build/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": "./build/index.js",
18
+ "require": "./build/index.js",
19
+ "default": "./build/index.js",
20
+ "module": "./build/index.js",
21
+ "types": "./build/index.d.ts"
22
+ },
23
+ "./plugins": {
24
+ "import": "./build/plugins/export.js",
25
+ "require": "./build/plugins/export.js",
26
+ "default": "./build/plugins/export.js",
27
+ "module": "./build/plugins/export.js",
28
+ "types": "./build/plugins/export.d.ts"
29
+ },
30
+ "./helpers": {
31
+ "import": "./build/helpers/index.js",
32
+ "require": "./build/helpers/index.js",
33
+ "default": "./build/helpers/index.js",
34
+ "module": "./build/helpers/index.js",
35
+ "types": "./build/helpers/index.d.ts"
36
+ },
37
+ "./stores": {
38
+ "import": "./build/stores/index.js",
39
+ "require": "./build/stores/index.js",
40
+ "default": "./build/stores/index.js",
41
+ "module": "./build/stores/index.js",
42
+ "types": "./build/stores/index.d.ts"
43
+ }
44
+ },
45
+ "devDependencies": {
46
+ "@langchain/core": "^1.2.9",
47
+ "@langchain/langgraph": "^1.4.13",
48
+ "@owlmeans/dep-config": "workspace:*",
49
+ "@types/bun": "^1.3.14",
50
+ "@types/node": "^26.1.0",
51
+ "nodemon": "^3.1.14",
52
+ "typescript": "^7.0.2",
53
+ "zod": "^4.1.0"
54
+ },
55
+ "dependencies": {
56
+ "@owlmeans/agent-common": "^0.1.18-rc.11",
57
+ "@owlmeans/basic-ids": "^0.1.18-rc.6",
58
+ "@owlmeans/context": "^0.1.18-rc.6",
59
+ "@owlmeans/error": "^0.1.18-rc.6",
60
+ "@owlmeans/flow": "^0.1.18-rc.9",
61
+ "@owlmeans/llm": "^0.1.18-rc.9",
62
+ "@owlmeans/llm-common": "^0.1.18-rc.8",
63
+ "ajv": "^8.17.1"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public"
67
+ },
68
+ "peerDependencies": {
69
+ "@langchain/core": "^1.2.9",
70
+ "@langchain/langgraph": "^1.4.13"
71
+ }
72
+ }
package/src/consts.ts ADDED
@@ -0,0 +1,19 @@
1
+ export { AGENTS_SERVICE } from '@owlmeans/agent-common'
2
+
3
+ /** Default LangGraph entrypoint name. Shows up in traces, so it is worth overriding per agent. */
4
+ export const DEFAULT_ENTRYPOINT = 'owlmeans-agent'
5
+
6
+ /** Default action label for a model call, used as the LangChain `runName`. */
7
+ export const DEFAULT_ACTION = 'agent-ask'
8
+
9
+ /**
10
+ * How many tool rounds one run may take before it is stopped.
11
+ *
12
+ * A model that keeps calling tools without ever answering is not rare — it is the ordinary failure
13
+ * mode of a loop whose tool results do not satisfy it. Without a ceiling the run consumes the
14
+ * caller's budget until something else kills it, which reads as a hang rather than a refusal.
15
+ */
16
+ export const DEFAULT_MAX_TURNS = 64
17
+
18
+ /** Ordering weight of a plugin that declares none. */
19
+ export const DEFAULT_PLUGIN_ORDER = 50
package/src/errors.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { ResilientError } from '@owlmeans/error'
2
+
3
+ export class AgentError extends ResilientError {
4
+ public static override typeName: string = `AgentRuntime${ResilientError.typeName}`
5
+
6
+ constructor(message: string = 'error') {
7
+ super(AgentError.typeName, `agent-runtime:${message}`)
8
+ }
9
+ }
10
+
11
+ /** The agent was built without something it cannot work around — a model, or a tool set. */
12
+ export class AgentMissconfiguredError extends AgentError {
13
+ public static override typeName: string = `Missconfigured${AgentError.typeName}`
14
+
15
+ constructor(message: string = 'error') {
16
+ super(`missconfigured:${message}`)
17
+ this.type = AgentMissconfiguredError.typeName
18
+ }
19
+ }
20
+
21
+ /** The tool loop hit its turn ceiling without the model ever answering. */
22
+ export class AgentLoopExhaustedError extends AgentError {
23
+ public static override typeName: string = `LoopExhausted${AgentError.typeName}`
24
+
25
+ constructor(message: string = 'error') {
26
+ super(`loop-exhausted:${message}`)
27
+ this.type = AgentLoopExhaustedError.typeName
28
+ }
29
+ }
30
+
31
+ ResilientError.registerErrorClass(AgentError)
32
+ ResilientError.registerErrorClass(AgentMissconfiguredError)
33
+ ResilientError.registerErrorClass(AgentLoopExhaustedError)
@@ -0,0 +1,172 @@
1
+ import type { BaseMessage } from '@langchain/core/messages'
2
+ import type { JSONSchemaType } from 'ajv'
3
+ import type { LlmModel } from '@owlmeans/llm'
4
+ import { AgentRunStatus, DEFAULT_ADVICE_CHARS, DEFAULT_SUMMARY_CHARS, truncateAt } from '@owlmeans/agent-common'
5
+
6
+ export interface Compaction {
7
+ summary: string
8
+ advice?: string
9
+ }
10
+
11
+ export interface CompactionInput {
12
+ /** Omit to skip the model entirely and take the deterministic path. */
13
+ model?: LlmModel
14
+ /** The ask that opened the run. */
15
+ prompt: string
16
+ messages: readonly BaseMessage[]
17
+ status: AgentRunStatus
18
+ /** What happened after the loop — a validation verdict, a build result. */
19
+ note?: string
20
+ maxSummaryChars?: number
21
+ maxAdviceChars?: number
22
+ /** LangChain `runName`. Give it a value the application filters, or the summary of a run streams into the user's view of that run. */
23
+ action?: string
24
+ /** How much of the transcript to show the model. */
25
+ maxTranscriptChars?: number
26
+ }
27
+
28
+ const DEFAULT_TRANSCRIPT_CHARS = 24_000
29
+
30
+ /** The text of a message, whatever content shape it arrived in. */
31
+ export const messageText = (message: BaseMessage): string => {
32
+ const content = message.content
33
+ if (typeof content === 'string') {
34
+ return content
35
+ }
36
+ if (Array.isArray(content)) {
37
+ return content
38
+ .map(part => typeof part === 'string' ? part : (part as { text?: string }).text ?? '')
39
+ .filter(text => text !== '')
40
+ .join('\n')
41
+ }
42
+
43
+ return ''
44
+ }
45
+
46
+ /**
47
+ * A transcript the model can read, newest-biased.
48
+ *
49
+ * The tail is what matters to a compaction — how the run ENDED decides what to do next — so when
50
+ * the budget binds it is the head that goes.
51
+ */
52
+ export const renderTranscript = (
53
+ messages: readonly BaseMessage[], maxChars = DEFAULT_TRANSCRIPT_CHARS,
54
+ ): string => {
55
+ const lines: string[] = []
56
+ let used = 0
57
+
58
+ for (let i = messages.length - 1; i >= 0; --i) {
59
+ const message = messages[i]
60
+ const text = messageText(message).trim()
61
+ const calls = (message as { tool_calls?: Array<{ name: string }> }).tool_calls
62
+ const body = text !== ''
63
+ ? text
64
+ : calls != null && calls.length > 0
65
+ ? `(called ${calls.map(call => call.name).join(', ')})`
66
+ : ''
67
+ if (body === '') {
68
+ continue
69
+ }
70
+
71
+ const line = `${message.getType()}: ${body}`
72
+ if (used + line.length > maxChars) {
73
+ break
74
+ }
75
+ lines.unshift(line)
76
+ used += line.length
77
+ }
78
+
79
+ return lines.join('\n\n')
80
+ }
81
+
82
+ const COMPACTION_SCHEMA: JSONSchemaType<{ summary: string, advice: string }> = {
83
+ type: 'object',
84
+ properties: {
85
+ summary: { type: 'string' },
86
+ advice: { type: 'string' },
87
+ },
88
+ required: ['summary', 'advice'],
89
+ additionalProperties: false,
90
+ }
91
+
92
+ /**
93
+ * Compact a finished run into what the next one needs.
94
+ *
95
+ * Two parts, deliberately. A summary alone leaves the next run to re-derive the plan from the
96
+ * outcome, which is where it invents a different one; the advice is the half that carries intent
97
+ * across the gap.
98
+ *
99
+ * **Never throws, and never trusts the model's arithmetic.** The character caps are applied after
100
+ * the answer comes back, because a cap in a prompt is a request. When the model is absent or fails
101
+ * — an exhausted budget is the common case, and asking again would fail the same way — the
102
+ * deterministic fallback still produces a usable event: what was asked, and how it ended.
103
+ */
104
+ export const composeCompaction = async (input: CompactionInput): Promise<Compaction> => {
105
+ const {
106
+ model, prompt, messages, status, note,
107
+ maxSummaryChars = DEFAULT_SUMMARY_CHARS,
108
+ maxAdviceChars = DEFAULT_ADVICE_CHARS,
109
+ action = 'agent-compaction',
110
+ maxTranscriptChars,
111
+ } = input
112
+
113
+ const fallback = (): Compaction => {
114
+ const last = [...messages].reverse().find(message => messageText(message).trim() !== '')
115
+ const tail = last != null ? messageText(last).trim() : ''
116
+ const head = `Asked: ${prompt.trim()}`
117
+ const ended = status === AgentRunStatus.Ok ? 'Finished.' : 'Did not finish.'
118
+
119
+ return {
120
+ summary: truncateAt(
121
+ [head, ended, note?.trim(), tail].filter(part => part != null && part !== '').join(' '),
122
+ maxSummaryChars,
123
+ ),
124
+ }
125
+ }
126
+
127
+ if (model == null) {
128
+ return fallback()
129
+ }
130
+
131
+ try {
132
+ const result = await model.invoke<{ summary: string, advice: string }>(
133
+ `
134
+ Compact the conversation below into a handover for the next session working on the same subject.
135
+
136
+ Write two things:
137
+
138
+ - summary: what was asked, what was actually done, and how it ended. Facts only — name the files,
139
+ decisions and failures that occurred. At most ${maxSummaryChars} characters.
140
+ - advice: what the next session should do first, and what it should not repeat. If the work
141
+ finished cleanly, say what remains or say that nothing does. At most ${maxAdviceChars} characters.
142
+
143
+ Write for a reader who cannot see this conversation and will act on your words alone. Do not
144
+ address the reader, do not describe the conversation as a conversation, and do not speculate about
145
+ anything not shown.
146
+
147
+ # The ask that opened the session
148
+ ${prompt}
149
+
150
+ # How it ended
151
+ ${status === AgentRunStatus.Ok ? 'Completed' : 'Failed'}${note != null && note !== '' ? ` — ${note}` : ''}
152
+
153
+ # Conversation
154
+ ${renderTranscript(messages, maxTranscriptChars)}
155
+ `,
156
+ COMPACTION_SCHEMA,
157
+ { action },
158
+ )
159
+
160
+ const summary = truncateAt(result.summary ?? '', maxSummaryChars)
161
+ const advice = truncateAt(result.advice ?? '', maxAdviceChars)
162
+
163
+ // An empty summary is a non-answer, not a short one — take the deterministic path rather than
164
+ // storing a blank event that the next run will read as "nothing happened".
165
+ return summary === ''
166
+ ? fallback()
167
+ : { summary, ...(advice !== '' ? { advice } : {}) }
168
+ } catch (e) {
169
+ console.warn('Agent compaction failed, falling back to a deterministic summary:', e)
170
+ return fallback()
171
+ }
172
+ }
@@ -0,0 +1,3 @@
1
+ export * from './tools.js'
2
+ export * from './compaction.js'
3
+ export * from './rolling.js'
@@ -0,0 +1,68 @@
1
+ import type { LlmModel } from '@owlmeans/llm'
2
+ import { truncateAt } from '@owlmeans/agent-common'
3
+
4
+ export interface RollingSummaryInput {
5
+ /** Omit to skip the model and take the deterministic path. */
6
+ model?: LlmModel
7
+ /** The prose account so far. Empty on the first fold. */
8
+ previous: string
9
+ /** What just happened, as one line. */
10
+ event: string
11
+ /** Anything the fold may use but that need not survive into the summary. */
12
+ details?: string
13
+ /** Hard ceiling on the returned prose, in characters. */
14
+ maxChars: number
15
+ /** LangChain `runName`. Give it a value the application filters out of its user-facing stream. */
16
+ action?: string
17
+ }
18
+
19
+ /**
20
+ * Fold one event into a running account of a subject, under a hard character ceiling.
21
+ *
22
+ * **Never throws.** When the model is unavailable or refuses, the previous prose is kept and
23
+ * head-truncated to make room rather than being replaced by an error or dropped: the caller's own
24
+ * verbatim record of the event is what preserves the fact, so a failed fold costs detail, never
25
+ * the event itself. That is the property that lets a caller record history unconditionally.
26
+ */
27
+ export const composeRollingSummary = async (input: RollingSummaryInput): Promise<string> => {
28
+ const { model, previous, event, details, maxChars, action = 'agent-rolling-summary' } = input
29
+
30
+ const trimmedPrevious = previous.trim()
31
+ const fallback = (): string => trimmedPrevious === ''
32
+ ? truncateAt(event, maxChars)
33
+ : truncateAt(trimmedPrevious, maxChars)
34
+
35
+ if (model == null) {
36
+ return fallback()
37
+ }
38
+
39
+ try {
40
+ const result = await model.ask(
41
+ `
42
+ Update the running account of a project with the event below.
43
+
44
+ Write ONE account that covers the project's whole life so far, at most ${maxChars} characters. Keep
45
+ what still matters — what the project is, the decisions taken, what has been built, what failed and
46
+ was not repaired. Drop detail that later events made irrelevant. Prefer losing old detail to losing
47
+ recent facts.
48
+
49
+ Facts only. No preamble, no headings, no addressing the reader, no speculation about what happens
50
+ next. Plain prose paragraphs.
51
+
52
+ # The account so far
53
+ ${trimmedPrevious === '' ? '(nothing recorded yet)' : trimmedPrevious}
54
+
55
+ # What just happened
56
+ ${event}${details != null && details !== '' ? `\n\n# Detail\n${details}` : ''}
57
+ `,
58
+ { action },
59
+ )
60
+
61
+ const summary = truncateAt(result ?? '', maxChars)
62
+
63
+ return summary === '' ? fallback() : summary
64
+ } catch (e) {
65
+ console.warn('Rolling summary fold failed, keeping the previous account:', e)
66
+ return fallback()
67
+ }
68
+ }
@@ -0,0 +1,46 @@
1
+ import type { ToolCall } from '@langchain/core/messages'
2
+ import type { AgentToolSet } from '../types.js'
3
+
4
+ /** The shape a contained tool failure comes back as. Matches what tool bodies return themselves. */
5
+ export interface ToolErrorResponse { error: string }
6
+
7
+ export const toErrorResponse = (e: unknown): ToolErrorResponse =>
8
+ ({ error: `Error during tool call: ${(e instanceof Error) ? e.message : String(e)}` })
9
+
10
+ export const isToolError = (result: unknown): result is ToolErrorResponse =>
11
+ typeof result === 'object' && result != null && 'error' in result
12
+
13
+ /**
14
+ * Run one model-requested tool, contained.
15
+ *
16
+ * **Never throws.** The caller wraps this in a LangGraph task, and a rejected task aborts the whole
17
+ * superstep: every sibling tool call in the same parallel batch dies with AbortError and the run
18
+ * ends on "Multiple errors occurred during superstep 0", discarding work the other calls had
19
+ * already finished. A tool failure has to come back as something the model can read and correct
20
+ * instead — most of them are the model's own mistake (an argument outside an enum, a hallucinated
21
+ * tool name), and the error text already names what was expected.
22
+ *
23
+ * Awaiting `invoke` is what makes the catch reachable: the tool's schema is validated inside it, so
24
+ * a bad argument rejects asynchronously and returning the promise unawaited would carry the
25
+ * rejection straight past this handler.
26
+ *
27
+ * Resolution is by the tool's OWN name, not by the map key. `bindTools` advertises `tool.name`, so
28
+ * that is what the model calls — a map keyed by a local variable silently loses any tool whose two
29
+ * names drifted apart, leaving it advertised, callable, and permanently "not found". The key stays
30
+ * as a fallback so a caller may still address a tool by it.
31
+ */
32
+ export const safeInvokeTool = async (tools: AgentToolSet, toolCall: ToolCall): Promise<unknown> => {
33
+ const tool = tools[toolCall.name]
34
+ ?? Object.values(tools).find(entry => entry.name === toolCall.name)
35
+
36
+ if (tool == null) {
37
+ return toErrorResponse(new Error(`Tool ${toolCall.name} not found`))
38
+ }
39
+
40
+ try {
41
+ return await tool.invoke(toolCall.args)
42
+ } catch (e) {
43
+ console.warn(`Error during tool call ${toolCall.name}:`, e)
44
+ return toErrorResponse(e)
45
+ }
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export * from './consts.js'
2
+ export * from './errors.js'
3
+ export type * from './types.js'
4
+ export * from './model.js'
5
+ export * from './service.js'
6
+ export * from './helpers/index.js'
7
+ export * from './stores/index.js'
8
+ export * from './runtime/provider.js'
9
+ export * from './runtime/transport.js'
10
+ export * from './runtime/checkpoint.js'
11
+ export * from './plugins/export.js'