@agntk/core 0.1.2 → 0.3.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 (186) hide show
  1. package/README.md +56 -90
  2. package/dist/advanced/index.d.ts +9 -2
  3. package/dist/advanced/index.d.ts.map +1 -1
  4. package/dist/advanced/index.js +13 -2
  5. package/dist/advanced/index.js.map +1 -1
  6. package/dist/agent.d.ts +27 -31
  7. package/dist/agent.d.ts.map +1 -1
  8. package/dist/agent.js +251 -280
  9. package/dist/agent.js.map +1 -1
  10. package/dist/config/defaults.d.ts.map +1 -1
  11. package/dist/config/defaults.js +16 -4
  12. package/dist/config/defaults.js.map +1 -1
  13. package/dist/config/loader.d.ts +14 -6
  14. package/dist/config/loader.d.ts.map +1 -1
  15. package/dist/config/loader.js +38 -16
  16. package/dist/config/loader.js.map +1 -1
  17. package/dist/config/schema.d.ts +52 -52
  18. package/dist/config/schema.d.ts.map +1 -1
  19. package/dist/config/schema.js +1 -1
  20. package/dist/config/schema.js.map +1 -1
  21. package/dist/evals/runner.js +16 -9
  22. package/dist/evals/runner.js.map +1 -1
  23. package/dist/evals/types.d.ts +1 -1
  24. package/dist/evals/types.d.ts.map +1 -1
  25. package/dist/guardrails/runner.d.ts.map +1 -1
  26. package/dist/guardrails/runner.js +4 -0
  27. package/dist/guardrails/runner.js.map +1 -1
  28. package/dist/index.d.ts +9 -10
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +15 -10
  31. package/dist/index.js.map +1 -1
  32. package/dist/models.d.ts +24 -1
  33. package/dist/models.d.ts.map +1 -1
  34. package/dist/models.js +50 -4
  35. package/dist/models.js.map +1 -1
  36. package/dist/observability/langfuse.d.ts +2 -2
  37. package/dist/observability/langfuse.d.ts.map +1 -1
  38. package/dist/observability/langfuse.js +39 -17
  39. package/dist/observability/langfuse.js.map +1 -1
  40. package/dist/presets/sub-agent-configs.d.ts +11 -3
  41. package/dist/presets/sub-agent-configs.d.ts.map +1 -1
  42. package/dist/presets/sub-agent-configs.js +5 -10
  43. package/dist/presets/sub-agent-configs.js.map +1 -1
  44. package/dist/presets/tools.d.ts +3 -3
  45. package/dist/provider-resolver.d.ts +38 -0
  46. package/dist/provider-resolver.d.ts.map +1 -0
  47. package/dist/provider-resolver.js +142 -0
  48. package/dist/provider-resolver.js.map +1 -0
  49. package/dist/reflection.d.ts +5 -2
  50. package/dist/reflection.d.ts.map +1 -1
  51. package/dist/reflection.js +8 -3
  52. package/dist/reflection.js.map +1 -1
  53. package/dist/skills/loader.d.ts +18 -0
  54. package/dist/skills/loader.d.ts.map +1 -1
  55. package/dist/skills/loader.js +58 -2
  56. package/dist/skills/loader.js.map +1 -1
  57. package/dist/system-detect.d.ts +59 -0
  58. package/dist/system-detect.d.ts.map +1 -0
  59. package/dist/system-detect.js +193 -0
  60. package/dist/system-detect.js.map +1 -0
  61. package/dist/tools/file/tools.d.ts.map +1 -1
  62. package/dist/tools/file/tools.js +30 -1
  63. package/dist/tools/file/tools.js.map +1 -1
  64. package/dist/tools/index.d.ts +0 -1
  65. package/dist/tools/index.d.ts.map +1 -1
  66. package/dist/tools/index.js +0 -2
  67. package/dist/tools/index.js.map +1 -1
  68. package/dist/tools/plan/tools.d.ts +1 -1
  69. package/dist/tools/plan/types.d.ts +2 -2
  70. package/dist/tools/progress/index.d.ts +1 -1
  71. package/dist/tools/shell/background.d.ts.map +1 -1
  72. package/dist/tools/shell/background.js +45 -3
  73. package/dist/tools/shell/background.js.map +1 -1
  74. package/dist/tools/shell/tools.d.ts.map +1 -1
  75. package/dist/tools/shell/tools.js +9 -2
  76. package/dist/tools/shell/tools.js.map +1 -1
  77. package/dist/tools/spawn-agent/index.d.ts +7 -9
  78. package/dist/tools/spawn-agent/index.d.ts.map +1 -1
  79. package/dist/tools/spawn-agent/index.js +2 -4
  80. package/dist/tools/spawn-agent/index.js.map +1 -1
  81. package/dist/tools/utils/shell.d.ts +14 -0
  82. package/dist/tools/utils/shell.d.ts.map +1 -1
  83. package/dist/tools/utils/shell.js +171 -12
  84. package/dist/tools/utils/shell.js.map +1 -1
  85. package/dist/types/agent.d.ts +75 -195
  86. package/dist/types/agent.d.ts.map +1 -1
  87. package/dist/types/agent.js +4 -2
  88. package/dist/types/agent.js.map +1 -1
  89. package/dist/types/index.d.ts +1 -1
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/dist/types/index.js.map +1 -1
  92. package/dist/usage-limits.d.ts +1 -1
  93. package/dist/usage-limits.js +1 -1
  94. package/dist/workflow/index.d.ts +1 -5
  95. package/dist/workflow/index.d.ts.map +1 -1
  96. package/dist/workflow/index.js +1 -9
  97. package/dist/workflow/index.js.map +1 -1
  98. package/dist/wrappers/best-of-n.d.ts +1 -1
  99. package/dist/wrappers/best-of-n.d.ts.map +1 -1
  100. package/dist/wrappers/best-of-n.js +11 -6
  101. package/dist/wrappers/best-of-n.js.map +1 -1
  102. package/package.json +4 -14
  103. package/dist/pool/index.d.ts +0 -7
  104. package/dist/pool/index.d.ts.map +0 -1
  105. package/dist/pool/index.js +0 -6
  106. package/dist/pool/index.js.map +0 -1
  107. package/dist/pool/specialist-pool.d.ts +0 -59
  108. package/dist/pool/specialist-pool.d.ts.map +0 -1
  109. package/dist/pool/specialist-pool.js +0 -224
  110. package/dist/pool/specialist-pool.js.map +0 -1
  111. package/dist/pool/tools.d.ts +0 -63
  112. package/dist/pool/tools.d.ts.map +0 -1
  113. package/dist/pool/tools.js +0 -83
  114. package/dist/pool/tools.js.map +0 -1
  115. package/dist/pool/types.d.ts +0 -79
  116. package/dist/pool/types.d.ts.map +0 -1
  117. package/dist/pool/types.js +0 -5
  118. package/dist/pool/types.js.map +0 -1
  119. package/dist/presets/index.d.ts +0 -5
  120. package/dist/presets/index.d.ts.map +0 -1
  121. package/dist/presets/index.js +0 -5
  122. package/dist/presets/index.js.map +0 -1
  123. package/dist/presets/role-registry.d.ts +0 -41
  124. package/dist/presets/role-registry.d.ts.map +0 -1
  125. package/dist/presets/role-registry.js +0 -213
  126. package/dist/presets/role-registry.js.map +0 -1
  127. package/dist/presets/roles.d.ts +0 -105
  128. package/dist/presets/roles.d.ts.map +0 -1
  129. package/dist/presets/roles.js +0 -207
  130. package/dist/presets/roles.js.map +0 -1
  131. package/dist/tools/factory.d.ts +0 -109
  132. package/dist/tools/factory.d.ts.map +0 -1
  133. package/dist/tools/factory.js +0 -166
  134. package/dist/tools/factory.js.map +0 -1
  135. package/dist/workflow/builders/adapt.d.ts +0 -20
  136. package/dist/workflow/builders/adapt.d.ts.map +0 -1
  137. package/dist/workflow/builders/adapt.js +0 -33
  138. package/dist/workflow/builders/adapt.js.map +0 -1
  139. package/dist/workflow/builders/index.d.ts +0 -8
  140. package/dist/workflow/builders/index.d.ts.map +0 -1
  141. package/dist/workflow/builders/index.js +0 -7
  142. package/dist/workflow/builders/index.js.map +0 -1
  143. package/dist/workflow/builders/parallel.d.ts +0 -25
  144. package/dist/workflow/builders/parallel.d.ts.map +0 -1
  145. package/dist/workflow/builders/parallel.js +0 -60
  146. package/dist/workflow/builders/parallel.js.map +0 -1
  147. package/dist/workflow/builders/pipeline.d.ts +0 -22
  148. package/dist/workflow/builders/pipeline.d.ts.map +0 -1
  149. package/dist/workflow/builders/pipeline.js +0 -48
  150. package/dist/workflow/builders/pipeline.js.map +0 -1
  151. package/dist/workflow/builders/types.d.ts +0 -54
  152. package/dist/workflow/builders/types.d.ts.map +0 -1
  153. package/dist/workflow/builders/types.js +0 -5
  154. package/dist/workflow/builders/types.js.map +0 -1
  155. package/dist/workflow/schedulers.d.ts +0 -231
  156. package/dist/workflow/schedulers.d.ts.map +0 -1
  157. package/dist/workflow/schedulers.js +0 -250
  158. package/dist/workflow/schedulers.js.map +0 -1
  159. package/dist/workflow/team/create-team.d.ts +0 -34
  160. package/dist/workflow/team/create-team.d.ts.map +0 -1
  161. package/dist/workflow/team/create-team.js +0 -242
  162. package/dist/workflow/team/create-team.js.map +0 -1
  163. package/dist/workflow/team/index.d.ts +0 -9
  164. package/dist/workflow/team/index.d.ts.map +0 -1
  165. package/dist/workflow/team/index.js +0 -8
  166. package/dist/workflow/team/index.js.map +0 -1
  167. package/dist/workflow/team/machines.d.ts +0 -152
  168. package/dist/workflow/team/machines.d.ts.map +0 -1
  169. package/dist/workflow/team/machines.js +0 -197
  170. package/dist/workflow/team/machines.js.map +0 -1
  171. package/dist/workflow/team/task-board.d.ts +0 -47
  172. package/dist/workflow/team/task-board.d.ts.map +0 -1
  173. package/dist/workflow/team/task-board.js +0 -111
  174. package/dist/workflow/team/task-board.js.map +0 -1
  175. package/dist/workflow/team/tools.d.ts +0 -66
  176. package/dist/workflow/team/tools.d.ts.map +0 -1
  177. package/dist/workflow/team/tools.js +0 -100
  178. package/dist/workflow/team/tools.js.map +0 -1
  179. package/dist/workflow/team/types.d.ts +0 -109
  180. package/dist/workflow/team/types.d.ts.map +0 -1
  181. package/dist/workflow/team/types.js +0 -5
  182. package/dist/workflow/team/types.js.map +0 -1
  183. package/dist/workflow/templates.d.ts +0 -71
  184. package/dist/workflow/templates.d.ts.map +0 -1
  185. package/dist/workflow/templates.js +0 -132
  186. package/dist/workflow/templates.js.map +0 -1
package/dist/agent.js CHANGED
@@ -1,126 +1,157 @@
1
1
  /**
2
- * @agntk/core - Core Agent Factory
2
+ * @agntk/core - Agent Factory
3
3
  *
4
- * Creates agents using AI SDK's ToolLoopAgent pattern.
5
- * Provides opinionated defaults for tools, roles, and streaming.
4
+ * A fully-equipped agent that shows up ready. No roles, no tool presets,
5
+ * no feature flags. You give it a name, tell it what it's doing, and
6
+ * point it at a task. It figures out the rest.
7
+ *
8
+ * Every capability is auto-detected from the environment:
9
+ * - Tools: ALL tools, always (plus any custom tools you pass)
10
+ * - Memory: always on, stored at ~/.agntk/agents/{name}/
11
+ * - Durability: auto-detected (workflow package installed → on)
12
+ * - Telemetry: auto-detected (LANGFUSE_PUBLIC_KEY set → on)
13
+ * - Skills: auto-discovered from standard directories
14
+ * - Sub-agents: always enabled with team coordination
15
+ * - Reflection: always on (reflact strategy)
16
+ * - Guardrails: always on (output: PII content filter)
17
+ * - Model: auto-selected from available API keys
6
18
  */
7
- import { generateId, ToolLoopAgent, stepCountIs } from 'ai';
19
+ import { resolve } from 'node:path';
20
+ import { homedir } from 'node:os';
21
+ import { ToolLoopAgent, stepCountIs } from 'ai';
8
22
  import { createLogger } from '@agntk/logger';
9
23
  import { usageLimitStop } from './usage-limits.js';
10
24
  import { resolveModel } from './models.js';
11
- import { getRole } from './presets/role-registry.js';
12
25
  import { createToolPreset } from './presets/tools.js';
13
26
  import { createSpawnAgentTool } from './tools/spawn-agent/index.js';
14
27
  import { wrapAllToolsWithRetry } from './tools/model-retry.js';
15
- import { loadSkills, buildSkillsSystemPrompt } from './skills/index.js';
28
+ import { discoverSkills, filterEligibleSkills, buildSkillsSystemPrompt, loadSkillContent } from './skills/index.js';
16
29
  import { checkWorkflowAvailability } from './workflow/utils.js';
17
30
  import { wrapToolsAsDurable } from './workflow/durable-tool.js';
18
31
  import { createReflectionPrepareStep } from './reflection.js';
32
+ import { runGuardrails, handleGuardrailResults } from './guardrails/runner.js';
33
+ import { contentFilter } from './guardrails/built-ins.js';
19
34
  import { applyApproval, resolveApprovalConfig } from './tools/approval.js';
20
- import { wrapWithGuardrails } from './guardrails/runner.js';
21
35
  import { MarkdownMemoryStore } from './memory/store.js';
22
36
  import { loadMemoryContext } from './memory/loader.js';
23
37
  import { createMemoryTools } from './memory/tools.js';
24
38
  import { initObservability, createTelemetrySettings } from './observability/index.js';
39
+ import { buildDynamicSystemPrompt } from './prompts/context.js';
25
40
  // ============================================================================
26
41
  // Logger
27
42
  // ============================================================================
28
43
  const log = createLogger('@agntk/core:agent');
29
44
  // ============================================================================
30
- // Tool Building
45
+ // Constants
31
46
  // ============================================================================
32
- function buildTools(options, workspaceRoot) {
33
- const { toolPreset = 'standard', tools = {}, enableTools, disableTools } = options;
34
- log.debug('Building tools', { preset: toolPreset, customTools: Object.keys(tools).length });
35
- // Create preset tools using factory
36
- let allTools = createToolPreset(toolPreset, {
37
- workspaceRoot,
38
- });
39
- // Add custom tools
40
- Object.assign(allTools, tools);
41
- // Filter enabled tools
42
- if (enableTools?.length) {
43
- const enabledSet = new Set(enableTools);
44
- allTools = Object.fromEntries(Object.entries(allTools).filter(([name]) => enabledSet.has(name)));
47
+ const DEFAULT_MAX_STEPS = 25;
48
+ const SUB_AGENT_MAX_STEPS = 15;
49
+ const DEFAULT_MAX_SPAWN_DEPTH = 2;
50
+ const AGENT_STATE_BASE = '.agntk/agents';
51
+ // ============================================================================
52
+ // Helpers
53
+ // ============================================================================
54
+ /**
55
+ * Resolve the persistent state directory for a named agent.
56
+ * ~/.agntk/agents/{name}/
57
+ */
58
+ function resolveAgentStatePath(name) {
59
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase();
60
+ return resolve(homedir(), AGENT_STATE_BASE, safeName);
61
+ }
62
+ /**
63
+ * Detect if telemetry should be enabled from env vars.
64
+ */
65
+ function detectTelemetry() {
66
+ return !!(process.env.LANGFUSE_PUBLIC_KEY &&
67
+ process.env.LANGFUSE_SECRET_KEY);
68
+ }
69
+ /**
70
+ * Build the base instructions for the agent.
71
+ */
72
+ function buildBaseInstructions(name, userInstructions, skillsPrompt) {
73
+ const parts = [];
74
+ parts.push(`You are ${name}, a capable AI agent.`);
75
+ if (userInstructions) {
76
+ parts.push('');
77
+ parts.push(userInstructions);
45
78
  }
46
- // Remove disabled tools
47
- if (disableTools?.length) {
48
- for (const name of disableTools) {
49
- delete allTools[name];
50
- }
79
+ parts.push('');
80
+ parts.push('You have access to a full suite of tools including file operations, ' +
81
+ 'shell commands, code search (grep, glob, ast-grep), a browser, ' +
82
+ 'deep reasoning, planning, and persistent memory. ' +
83
+ 'You can spawn sub-agents for complex tasks that benefit from delegation. ' +
84
+ 'Use the remember tool to persist important findings across sessions. ' +
85
+ 'Use the recall tool to search your memory for relevant context. ' +
86
+ 'If the user\'s request is vague or conversational (e.g., greetings, ' +
87
+ '"whats up", "hello"), respond conversationally without using tools.');
88
+ if (skillsPrompt) {
89
+ parts.push('');
90
+ parts.push(skillsPrompt);
51
91
  }
52
- log.debug('Tools ready', { tools: Object.keys(allTools) });
53
- return allTools;
92
+ return parts.join('\n');
54
93
  }
55
94
  // ============================================================================
56
- // Create Agent Factory
95
+ // Agent Factory
57
96
  // ============================================================================
58
97
  /**
59
- * Creates an agent with the given options.
98
+ * Create an agent fully equipped, zero config.
60
99
  *
61
100
  * @example
62
101
  * ```typescript
63
102
  * const agent = createAgent({
64
- * role: 'coder',
65
- * workspaceRoot: '/my/project',
66
- * toolPreset: 'standard',
103
+ * name: 'deploy-bot',
104
+ * instructions: 'You manage deployments for our k8s cluster.',
67
105
  * });
68
106
  *
69
- * const result = await agent.generate({ prompt: 'Create a hello world function' });
70
- * console.log(result.text);
107
+ * const result = await agent.stream({ prompt: 'Roll back staging to yesterday' });
108
+ * for await (const chunk of result.fullStream) {
109
+ * if (chunk.type === 'text-delta') process.stdout.write(chunk.text ?? '');
110
+ * }
71
111
  * ```
72
112
  */
73
- export function createAgent(options = {}) {
74
- const { role = 'generic', agentId = generateId(), systemPrompt, maxSteps = 10, enableSubAgents = false, workspaceRoot = process.cwd(), telemetry, } = options;
75
- log.info('Creating agent', { agentId, role, maxSteps, enableSubAgents });
76
- // Get role configuration from registry
77
- const roleConfig = getRole(role);
78
- // Resolve system prompt
79
- const baseSystemPrompt = systemPrompt ?? roleConfig.systemPrompt;
80
- const finalSystemPrompt = options.systemPromptPrefix
81
- ? `${options.systemPromptPrefix}\n\n${baseSystemPrompt}`
82
- : baseSystemPrompt;
83
- // Load and inject skills
84
- let augmentedSystemPrompt = finalSystemPrompt;
85
- if (options.skills) {
86
- log.debug('Loading skills', { config: options.skills });
87
- const skills = loadSkills(options.skills, workspaceRoot);
88
- if (skills.length > 0) {
89
- const skillsPrompt = buildSkillsSystemPrompt(skills);
90
- augmentedSystemPrompt += skillsPrompt;
91
- log.info('Skills injected', { count: skills.length, names: skills.map(s => s.name) });
92
- }
113
+ export function createAgent(options, _internal = {}) {
114
+ const { name, instructions, workspaceRoot = process.cwd(), } = options;
115
+ const spawnDepth = _internal._spawnDepth ?? 0;
116
+ const isSubAgent = spawnDepth > 0;
117
+ const maxSteps = options.maxSteps ?? (isSubAgent ? SUB_AGENT_MAX_STEPS : DEFAULT_MAX_STEPS);
118
+ log.info('Creating agent', { name, maxSteps, workspaceRoot, spawnDepth });
119
+ // ── 1. Resolve model ──────────────────────────────────────────────────
120
+ const model = options.model ?? resolveModel({ tier: 'standard' });
121
+ log.debug('Model resolved', { hasExplicitModel: !!options.model });
122
+ // ── 2. Build ALL tools ────────────────────────────────────────────────
123
+ let tools = createToolPreset('full', { workspaceRoot });
124
+ // Merge user-provided tools (escape hatch for testing / custom tools)
125
+ if (options.tools) {
126
+ Object.assign(tools, options.tools);
93
127
  }
94
- // Resolve model
95
- log.debug('Resolving model', {
96
- tier: roleConfig.recommendedModel,
97
- provider: options.modelProvider,
98
- modelName: options.modelName,
99
- });
100
- const model = options.model ?? resolveModel({
101
- tier: (roleConfig.recommendedModel ?? 'standard'),
102
- provider: options.modelProvider,
103
- modelName: options.modelName,
128
+ log.debug('Base tools built', { count: Object.keys(tools).length });
129
+ // ── 3. Memory — always on ─────────────────────────────────────────────
130
+ const agentStatePath = resolveAgentStatePath(name);
131
+ const memoryStore = new MarkdownMemoryStore({
132
+ projectDir: agentStatePath,
133
+ globalDir: '.agntk',
134
+ workspaceRoot,
104
135
  });
105
- // Build tools
106
- let tools = buildTools(options, workspaceRoot);
107
- // Add spawn_agent tool if enabled
108
- if (enableSubAgents) {
109
- log.debug('Enabling sub-agents', { maxSpawnDepth: options.maxSpawnDepth ?? 2 });
136
+ const memoryTools = createMemoryTools({ store: memoryStore, model });
137
+ Object.assign(tools, memoryTools);
138
+ log.info('Memory enabled', { agentStatePath });
139
+ // ── 4. Sub-agents — recursive creation ────────────────────────────────
140
+ if (spawnDepth < DEFAULT_MAX_SPAWN_DEPTH) {
110
141
  const spawnTool = createSpawnAgentTool({
111
- maxSpawnDepth: options.maxSpawnDepth ?? 2,
112
- currentDepth: 0,
142
+ maxSpawnDepth: DEFAULT_MAX_SPAWN_DEPTH,
143
+ currentDepth: spawnDepth,
113
144
  createAgent: (subAgentOptions) => {
114
- log.info('Spawning sub-agent', {
115
- parentId: agentId,
116
- role: subAgentOptions.role,
117
- });
145
+ const subName = `${name}/${subAgentOptions.role}`;
146
+ log.info('Spawning sub-agent', { parentName: name, subName });
118
147
  const subAgent = createAgent({
119
- role: subAgentOptions.role,
120
- systemPrompt: subAgentOptions.instructions,
121
- enableSubAgents: false, // Prevent recursion
148
+ name: subName,
149
+ instructions: subAgentOptions.instructions,
122
150
  workspaceRoot,
123
- maxSteps: subAgentOptions.maxSpawnDepth ?? 5,
151
+ maxSteps: SUB_AGENT_MAX_STEPS,
152
+ model: options.model,
153
+ }, {
154
+ _spawnDepth: spawnDepth + 1,
124
155
  });
125
156
  return {
126
157
  stream: (input) => {
@@ -132,7 +163,7 @@ export function createAgent(options = {}) {
132
163
  yield chunk;
133
164
  }
134
165
  })(),
135
- text: streamPromise.then(r => r.text),
166
+ text: streamPromise.then((r) => r.text),
136
167
  };
137
168
  },
138
169
  };
@@ -140,241 +171,181 @@ export function createAgent(options = {}) {
140
171
  });
141
172
  tools = { ...tools, spawn_agent: spawnTool };
142
173
  }
143
- // Memory: create store, add memory tools, prepare lazy context loading
144
- let memoryStore = null;
145
- let memoryContextLoaded = false;
146
- if (options.enableMemory) {
147
- const memOpts = options.memoryOptions ?? {};
148
- memoryStore = memOpts.store ??
149
- new MarkdownMemoryStore({
150
- projectDir: memOpts.projectDir,
151
- globalDir: memOpts.globalDir,
152
- workspaceRoot,
153
- });
154
- const memoryTools = createMemoryTools({ store: memoryStore, model });
155
- Object.assign(tools, memoryTools);
156
- log.info('Memory enabled', {
157
- projectPath: memoryStore.getProjectPath(),
158
- globalPath: memoryStore.getGlobalPath(),
159
- tools: Object.keys(memoryTools),
160
- });
161
- }
162
- // Apply approval to dangerous tools if configured
163
- const approvalConfig = resolveApprovalConfig(options.approval);
164
- if (approvalConfig?.enabled) {
165
- log.debug('Applying tool approval', { tools: approvalConfig.tools ?? 'default dangerous set' });
166
- tools = applyApproval(tools, approvalConfig);
167
- }
168
- // Wrap tools as durable steps if configured
169
- if (options.durable) {
170
- log.debug('Wrapping tools as durable steps', {
171
- toolCount: Object.keys(tools).length,
172
- workflowOptions: options.workflowOptions,
173
- });
174
- const durableConfig = {
175
- retryCount: options.workflowOptions?.defaultRetryCount ?? 3,
176
- };
177
- tools = wrapToolsAsDurable(tools, durableConfig);
178
- // Eagerly check workflow availability
179
- checkWorkflowAvailability().then((available) => {
180
- if (!available) {
181
- log.warn('Workflow package not installed. Durable tool wrapping is inert without the runtime. ' +
182
- 'Install with: npm install workflow');
183
- }
184
- }).catch(() => { });
174
+ // ── 5. ModelRetry always on ─────────────────────────────────────────
175
+ tools = wrapAllToolsWithRetry(tools, 3);
176
+ // ── 6. Auto-discover skills ───────────────────────────────────────────
177
+ let skillsPrompt = '';
178
+ try {
179
+ const discovered = discoverSkills(undefined, workspaceRoot);
180
+ const eligible = filterEligibleSkills(discovered);
181
+ if (eligible.length > 0) {
182
+ const loaded = eligible.map((s) => loadSkillContent(s));
183
+ skillsPrompt = buildSkillsSystemPrompt(loaded);
184
+ log.info('Skills discovered', { count: eligible.length });
185
+ }
185
186
  }
186
- // Wrap tools with ModelRetry handling
187
- const maxToolRetries = options.maxToolRetries;
188
- if (maxToolRetries !== 0) {
189
- tools = wrapAllToolsWithRetry(tools, maxToolRetries);
190
- log.debug('Tools wrapped with ModelRetry handling', { maxToolRetries: maxToolRetries ?? 3 });
187
+ catch (err) {
188
+ log.warn('Skill discovery failed', { error: err instanceof Error ? err.message : String(err) });
191
189
  }
192
- // Build stop conditions
190
+ // ── 7. Build system prompt ────────────────────────────────────────────
191
+ let augmentedSystemPrompt = buildBaseInstructions(name, instructions, skillsPrompt);
192
+ // ── 8. Stop conditions ────────────────────────────────────────────────
193
193
  const stopConditions = [
194
194
  stepCountIs(maxSteps),
195
195
  ];
196
196
  if (options.usageLimits) {
197
- log.debug('Usage limits configured', { limits: options.usageLimits });
198
197
  stopConditions.push(usageLimitStop(options.usageLimits));
199
198
  }
200
- // Build reflection prepareStep if configured
201
- const reflectionConfig = options.reflection;
202
- const prepareStep = reflectionConfig && reflectionConfig.strategy !== 'none'
203
- ? createReflectionPrepareStep(augmentedSystemPrompt, reflectionConfig)
204
- : undefined;
205
- if (prepareStep) {
206
- log.debug('Reflection enabled', { strategy: reflectionConfig.strategy });
199
+ // ── 9. Reflection always on (reflact strategy) ──────────────────────
200
+ // Pass a getter so reflection always reads the current augmentedSystemPrompt,
201
+ // even after memory/context is injected during ensureInit() (fixes DESIGN-001).
202
+ const prepareStep = createReflectionPrepareStep(() => augmentedSystemPrompt, {
203
+ strategy: 'reflact',
204
+ });
205
+ // ── 10. Guardrails always on (output: PII content filter) ───────────
206
+ const outputGuardrails = [contentFilter()];
207
+ // ── 11. Apply approval wrapping if requested ───────────────────────────
208
+ const approvalConfig = resolveApprovalConfig(options.approval);
209
+ if (approvalConfig) {
210
+ tools = applyApproval(tools, approvalConfig);
211
+ log.info('Approval system enabled', { tools: approvalConfig.tools ?? 'default dangerous tools' });
207
212
  }
208
- // Build telemetry settings (sync just creates the config object)
209
- const telemetrySettings = telemetry
210
- ? createTelemetrySettings({
211
- functionId: telemetry.functionId ?? `agent:${agentId}`,
212
- metadata: telemetry.metadata,
213
- })
213
+ // ── 12. Telemetryauto-detect ──────────────────────────────────────
214
+ const telemetryEnabled = detectTelemetry();
215
+ const telemetrySettings = telemetryEnabled
216
+ ? createTelemetrySettings({ functionId: `agent:${name}` })
214
217
  : undefined;
215
- // Create the ToolLoopAgent
216
- log.debug('Creating ToolLoopAgent', {
217
- promptLength: augmentedSystemPrompt.length,
218
- toolCount: Object.keys(tools).length,
219
- telemetry: !!telemetrySettings,
220
- });
218
+ // ── 13. Build the ToolLoopAgent ──────────────────────────────────────
221
219
  const toolLoopAgent = new ToolLoopAgent({
222
220
  model,
223
221
  instructions: augmentedSystemPrompt,
224
222
  tools,
225
223
  stopWhen: stopConditions,
226
- // prepareCall: dynamically inject the current system prompt (may be updated by memory loading)
227
224
  prepareCall: (opts) => ({ ...opts, instructions: augmentedSystemPrompt }),
228
- ...(prepareStep ? { prepareStep } : {}),
225
+ prepareStep,
229
226
  ...(telemetrySettings ? { experimental_telemetry: telemetrySettings } : {}),
230
227
  });
231
- // Create a child logger for this agent instance
232
- const agentLog = log.child({ agentId });
233
- // Lazy memory context loader — runs once, cached as a singleton promise
234
- let memoryInitPromise = null;
235
- function ensureMemoryContext() {
236
- if (!memoryStore)
237
- return Promise.resolve();
238
- if (memoryContextLoaded)
239
- return Promise.resolve();
240
- if (memoryInitPromise)
241
- return memoryInitPromise;
242
- memoryInitPromise = (async () => {
228
+ log.debug('ToolLoopAgent created', {
229
+ toolCount: Object.keys(tools).length,
230
+ telemetry: !!telemetrySettings,
231
+ });
232
+ // ── Lazy initializers ─────────────────────────────────────────────────
233
+ const agentLog = log.child({ agent: name });
234
+ let initialized = false;
235
+ let initPromise = null;
236
+ async function ensureInit() {
237
+ if (initialized)
238
+ return;
239
+ if (initPromise)
240
+ return initPromise;
241
+ initPromise = (async () => {
243
242
  try {
244
- const memoryContext = await loadMemoryContext(memoryStore);
245
- if (memoryContext) {
246
- augmentedSystemPrompt = memoryContext + '\n\n' + augmentedSystemPrompt;
247
- // prepareCall() will pick up the updated augmentedSystemPrompt on next generate/stream
248
- agentLog.debug('Memory context injected', { chars: memoryContext.length });
243
+ // Load memory context into system prompt
244
+ try {
245
+ const memoryContext = await loadMemoryContext(memoryStore);
246
+ if (memoryContext) {
247
+ augmentedSystemPrompt = memoryContext + '\n\n' + augmentedSystemPrompt;
248
+ agentLog.debug('Memory context injected', { chars: memoryContext.length });
249
+ }
250
+ }
251
+ catch (err) {
252
+ agentLog.warn('Memory context loading failed', {
253
+ error: err instanceof Error ? err.message : String(err),
254
+ });
255
+ }
256
+ // Inject dynamic environment context
257
+ try {
258
+ augmentedSystemPrompt = await buildDynamicSystemPrompt(augmentedSystemPrompt, {
259
+ workspaceRoot,
260
+ includeWorkspaceMap: true,
261
+ });
262
+ }
263
+ catch (err) {
264
+ agentLog.warn('Dynamic context injection failed', {
265
+ error: err instanceof Error ? err.message : String(err),
266
+ });
267
+ }
268
+ // Apply durable wrapping if workflow runtime is available
269
+ try {
270
+ const workflowAvailable = await checkWorkflowAvailability();
271
+ if (workflowAvailable) {
272
+ tools = wrapToolsAsDurable(tools, { retryCount: 3 });
273
+ agentLog.info('Durable tool wrapping active');
274
+ }
275
+ }
276
+ catch {
277
+ agentLog.debug('Workflow detection failed — skipping durable wrapping');
249
278
  }
279
+ // Initialize telemetry if detected
280
+ if (telemetryEnabled) {
281
+ try {
282
+ await initObservability({ provider: 'langfuse' });
283
+ agentLog.info('Telemetry initialized');
284
+ }
285
+ catch (err) {
286
+ agentLog.warn('Telemetry initialization failed', {
287
+ error: err instanceof Error ? err.message : String(err),
288
+ });
289
+ }
290
+ }
291
+ initialized = true;
250
292
  }
251
293
  catch (err) {
252
- agentLog.warn('Failed to load memory context', {
253
- error: err instanceof Error ? err.message : String(err),
254
- });
255
- }
256
- finally {
257
- memoryContextLoaded = true;
294
+ // E-5: Reset so callers can retry instead of permanently failing
295
+ initPromise = null;
296
+ throw err;
258
297
  }
259
298
  })();
260
- return memoryInitPromise;
299
+ return initPromise;
261
300
  }
262
- // Lazy telemetry init registers OTel provider before first LLM call
263
- let telemetryInitPromise = null;
264
- function ensureTelemetryInit() {
265
- if (!telemetry?.provider)
266
- return Promise.resolve();
267
- if (telemetryInitPromise)
268
- return telemetryInitPromise;
269
- telemetryInitPromise = initObservability(telemetry.provider)
270
- .then((ok) => {
271
- if (ok) {
272
- agentLog.info('Telemetry initialized', { provider: telemetry.provider.provider });
273
- }
274
- })
275
- .catch((err) => {
276
- agentLog.warn('Telemetry initialization failed', {
277
- error: err instanceof Error ? err.message : String(err),
278
- });
279
- });
280
- return telemetryInitPromise;
281
- }
282
- // Create the agent instance
301
+ // ── Build the agent instance ──────────────────────────────────────────
283
302
  const agent = {
284
- agentId,
285
- role,
286
- init: async () => {
287
- await Promise.all([ensureMemoryContext(), ensureTelemetryInit()]);
288
- },
289
- getToolLoopAgent: () => toolLoopAgent,
303
+ name,
304
+ init: ensureInit,
290
305
  getSystemPrompt: () => augmentedSystemPrompt,
306
+ getToolNames: () => Object.keys(tools),
291
307
  stream: async (input) => {
292
- await Promise.all([ensureMemoryContext(), ensureTelemetryInit()]);
308
+ await ensureInit();
293
309
  agentLog.info('stream() called', { promptLength: input.prompt.length });
294
- const done = agentLog.time('stream');
295
- const result = toolLoopAgent.stream({ prompt: input.prompt });
296
- return result;
297
- },
298
- generate: async (input) => {
299
- await Promise.all([ensureMemoryContext(), ensureTelemetryInit()]);
300
- agentLog.info('generate() called', {
301
- promptLength: input.prompt.length,
302
- prompt: input.prompt.slice(0, 500) + (input.prompt.length > 500 ? '...' : ''),
303
- });
304
- const done = agentLog.time('generate');
305
- try {
306
- const result = await toolLoopAgent.generate({ prompt: input.prompt });
307
- done();
308
- // Log each step with tool calls
309
- if (result.steps) {
310
- for (let i = 0; i < result.steps.length; i++) {
311
- const step = result.steps[i];
312
- // Log each tool call individually at trace level (full details)
313
- if (step.toolCalls) {
314
- for (const tc of step.toolCalls) {
315
- agentLog.trace(`Tool call: ${tc.toolName}`, {
316
- tool: tc.toolName,
317
- input: tc.args ?? tc.input,
318
- });
319
- }
320
- }
321
- // Log each tool result individually at trace level (full output)
322
- if (step.toolResults) {
323
- for (const tr of step.toolResults) {
324
- const output = tr.result ?? tr.output ?? '';
325
- agentLog.trace(`Tool result: ${tr.toolName}`, {
326
- tool: tr.toolName,
327
- output: typeof output === 'string' ? output.slice(0, 1000) : output,
328
- outputLength: typeof output === 'string' ? output.length : undefined,
329
- });
330
- }
331
- }
332
- // Summary log for step
333
- agentLog.debug(`Step ${i + 1}/${result.steps.length}`, {
334
- toolCalls: step.toolCalls?.map(tc => tc.toolName) ?? [],
335
- toolResults: step.toolResults?.map(tr => tr.toolName) ?? [],
336
- textLength: step.text?.length ?? 0,
310
+ const result = await toolLoopAgent.stream({ prompt: input.prompt });
311
+ // Apply output guardrails to the final text
312
+ const guardedText = result.text.then(async (text) => {
313
+ if (!text || outputGuardrails.length === 0)
314
+ return text;
315
+ try {
316
+ const { results, filteredText } = await runGuardrails(outputGuardrails, text, {
317
+ prompt: input.prompt,
318
+ phase: 'output',
319
+ });
320
+ const check = handleGuardrailResults(results, text, filteredText, 'output', 'filter');
321
+ if (check.blocked) {
322
+ agentLog.info('Output guardrails filtered content', {
323
+ guards: results.filter((r) => !r.passed).map((r) => r.name),
337
324
  });
325
+ return check.text;
338
326
  }
339
327
  }
340
- // Log token usage
341
- if (result.totalUsage) {
342
- agentLog.info('Token usage', {
343
- inputTokens: result.totalUsage.inputTokens,
344
- outputTokens: result.totalUsage.outputTokens,
345
- totalTokens: result.totalUsage.totalTokens,
346
- reasoningTokens: result.totalUsage.reasoningTokens ?? 0,
347
- cachedInputTokens: result.totalUsage.cachedInputTokens ?? 0,
328
+ catch (err) {
329
+ agentLog.warn('Output guardrails failed', {
330
+ error: err instanceof Error ? err.message : String(err),
348
331
  });
349
332
  }
350
- agentLog.info('generate() completed', {
351
- steps: result.steps?.length ?? 0,
352
- textLength: result.text?.length ?? 0,
353
- response: result.text?.slice(0, 500) + ((result.text?.length ?? 0) > 500 ? '...' : ''),
354
- });
355
- return result;
356
- }
357
- catch (error) {
358
- done();
359
- agentLog.error('generate() failed', {
360
- error: error instanceof Error ? error.message : String(error),
361
- stack: error instanceof Error ? error.stack : undefined,
362
- });
363
- throw error;
364
- }
333
+ return text;
334
+ });
335
+ return {
336
+ fullStream: result.fullStream,
337
+ text: guardedText,
338
+ usage: result.totalUsage,
339
+ };
365
340
  },
366
341
  };
367
- // Wrap with guardrails if configured
368
- if (options.guardrails && (options.guardrails.input?.length || options.guardrails.output?.length)) {
369
- log.debug('Applying guardrails', {
370
- inputCount: options.guardrails.input?.length ?? 0,
371
- outputCount: options.guardrails.output?.length ?? 0,
372
- onBlock: options.guardrails.onBlock ?? 'throw',
373
- });
374
- const originalGenerate = agent.generate.bind(agent);
375
- agent.generate = wrapWithGuardrails(originalGenerate, options.guardrails);
376
- }
377
- log.info('Agent created', { agentId, role, durable: !!options.durable });
342
+ log.info('Agent created', {
343
+ name,
344
+ spawnDepth,
345
+ toolCount: Object.keys(tools).length,
346
+ memoryPath: agentStatePath,
347
+ telemetry: telemetryEnabled,
348
+ });
378
349
  return agent;
379
350
  }
380
351
  export default createAgent;