@agntk/core 0.1.2 → 0.2.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 (134) 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 +228 -275
  9. package/dist/agent.js.map +1 -1
  10. package/dist/config/schema.d.ts +38 -38
  11. package/dist/evals/runner.js +16 -9
  12. package/dist/evals/runner.js.map +1 -1
  13. package/dist/evals/types.d.ts +1 -1
  14. package/dist/evals/types.d.ts.map +1 -1
  15. package/dist/index.d.ts +4 -9
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +6 -9
  18. package/dist/index.js.map +1 -1
  19. package/dist/observability/langfuse.d.ts +2 -2
  20. package/dist/observability/langfuse.d.ts.map +1 -1
  21. package/dist/observability/langfuse.js +39 -17
  22. package/dist/observability/langfuse.js.map +1 -1
  23. package/dist/presets/sub-agent-configs.d.ts +11 -3
  24. package/dist/presets/sub-agent-configs.d.ts.map +1 -1
  25. package/dist/presets/sub-agent-configs.js +5 -10
  26. package/dist/presets/sub-agent-configs.js.map +1 -1
  27. package/dist/presets/tools.d.ts +5 -5
  28. package/dist/tools/browser/tool.d.ts +2 -2
  29. package/dist/tools/browser/types.d.ts +2 -2
  30. package/dist/tools/plan/tools.d.ts +1 -1
  31. package/dist/tools/plan/types.d.ts +2 -2
  32. package/dist/tools/progress/index.d.ts +2 -2
  33. package/dist/tools/spawn-agent/index.d.ts +7 -9
  34. package/dist/tools/spawn-agent/index.d.ts.map +1 -1
  35. package/dist/tools/spawn-agent/index.js +2 -4
  36. package/dist/tools/spawn-agent/index.js.map +1 -1
  37. package/dist/types/agent.d.ts +69 -199
  38. package/dist/types/agent.d.ts.map +1 -1
  39. package/dist/types/agent.js +4 -2
  40. package/dist/types/agent.js.map +1 -1
  41. package/dist/types/index.d.ts +1 -1
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/dist/types/index.js.map +1 -1
  44. package/dist/usage-limits.d.ts +1 -1
  45. package/dist/usage-limits.js +1 -1
  46. package/dist/workflow/index.d.ts +1 -5
  47. package/dist/workflow/index.d.ts.map +1 -1
  48. package/dist/workflow/index.js +1 -9
  49. package/dist/workflow/index.js.map +1 -1
  50. package/dist/wrappers/best-of-n.d.ts +1 -1
  51. package/dist/wrappers/best-of-n.d.ts.map +1 -1
  52. package/dist/wrappers/best-of-n.js +11 -6
  53. package/dist/wrappers/best-of-n.js.map +1 -1
  54. package/package.json +4 -14
  55. package/dist/pool/index.d.ts +0 -7
  56. package/dist/pool/index.d.ts.map +0 -1
  57. package/dist/pool/index.js +0 -6
  58. package/dist/pool/index.js.map +0 -1
  59. package/dist/pool/specialist-pool.d.ts +0 -59
  60. package/dist/pool/specialist-pool.d.ts.map +0 -1
  61. package/dist/pool/specialist-pool.js +0 -224
  62. package/dist/pool/specialist-pool.js.map +0 -1
  63. package/dist/pool/tools.d.ts +0 -63
  64. package/dist/pool/tools.d.ts.map +0 -1
  65. package/dist/pool/tools.js +0 -83
  66. package/dist/pool/tools.js.map +0 -1
  67. package/dist/pool/types.d.ts +0 -79
  68. package/dist/pool/types.d.ts.map +0 -1
  69. package/dist/pool/types.js +0 -5
  70. package/dist/pool/types.js.map +0 -1
  71. package/dist/presets/index.d.ts +0 -5
  72. package/dist/presets/index.d.ts.map +0 -1
  73. package/dist/presets/index.js +0 -5
  74. package/dist/presets/index.js.map +0 -1
  75. package/dist/presets/role-registry.d.ts +0 -41
  76. package/dist/presets/role-registry.d.ts.map +0 -1
  77. package/dist/presets/role-registry.js +0 -213
  78. package/dist/presets/role-registry.js.map +0 -1
  79. package/dist/presets/roles.d.ts +0 -105
  80. package/dist/presets/roles.d.ts.map +0 -1
  81. package/dist/presets/roles.js +0 -207
  82. package/dist/presets/roles.js.map +0 -1
  83. package/dist/workflow/builders/adapt.d.ts +0 -20
  84. package/dist/workflow/builders/adapt.d.ts.map +0 -1
  85. package/dist/workflow/builders/adapt.js +0 -33
  86. package/dist/workflow/builders/adapt.js.map +0 -1
  87. package/dist/workflow/builders/index.d.ts +0 -8
  88. package/dist/workflow/builders/index.d.ts.map +0 -1
  89. package/dist/workflow/builders/index.js +0 -7
  90. package/dist/workflow/builders/index.js.map +0 -1
  91. package/dist/workflow/builders/parallel.d.ts +0 -25
  92. package/dist/workflow/builders/parallel.d.ts.map +0 -1
  93. package/dist/workflow/builders/parallel.js +0 -60
  94. package/dist/workflow/builders/parallel.js.map +0 -1
  95. package/dist/workflow/builders/pipeline.d.ts +0 -22
  96. package/dist/workflow/builders/pipeline.d.ts.map +0 -1
  97. package/dist/workflow/builders/pipeline.js +0 -48
  98. package/dist/workflow/builders/pipeline.js.map +0 -1
  99. package/dist/workflow/builders/types.d.ts +0 -54
  100. package/dist/workflow/builders/types.d.ts.map +0 -1
  101. package/dist/workflow/builders/types.js +0 -5
  102. package/dist/workflow/builders/types.js.map +0 -1
  103. package/dist/workflow/schedulers.d.ts +0 -231
  104. package/dist/workflow/schedulers.d.ts.map +0 -1
  105. package/dist/workflow/schedulers.js +0 -250
  106. package/dist/workflow/schedulers.js.map +0 -1
  107. package/dist/workflow/team/create-team.d.ts +0 -34
  108. package/dist/workflow/team/create-team.d.ts.map +0 -1
  109. package/dist/workflow/team/create-team.js +0 -242
  110. package/dist/workflow/team/create-team.js.map +0 -1
  111. package/dist/workflow/team/index.d.ts +0 -9
  112. package/dist/workflow/team/index.d.ts.map +0 -1
  113. package/dist/workflow/team/index.js +0 -8
  114. package/dist/workflow/team/index.js.map +0 -1
  115. package/dist/workflow/team/machines.d.ts +0 -152
  116. package/dist/workflow/team/machines.d.ts.map +0 -1
  117. package/dist/workflow/team/machines.js +0 -197
  118. package/dist/workflow/team/machines.js.map +0 -1
  119. package/dist/workflow/team/task-board.d.ts +0 -47
  120. package/dist/workflow/team/task-board.d.ts.map +0 -1
  121. package/dist/workflow/team/task-board.js +0 -111
  122. package/dist/workflow/team/task-board.js.map +0 -1
  123. package/dist/workflow/team/tools.d.ts +0 -66
  124. package/dist/workflow/team/tools.d.ts.map +0 -1
  125. package/dist/workflow/team/tools.js +0 -100
  126. package/dist/workflow/team/tools.js.map +0 -1
  127. package/dist/workflow/team/types.d.ts +0 -109
  128. package/dist/workflow/team/types.d.ts.map +0 -1
  129. package/dist/workflow/team/types.js +0 -5
  130. package/dist/workflow/team/types.js.map +0 -1
  131. package/dist/workflow/templates.d.ts +0 -71
  132. package/dist/workflow/templates.d.ts.map +0 -1
  133. package/dist/workflow/templates.js +0 -132
  134. package/dist/workflow/templates.js.map +0 -1
package/dist/agent.js CHANGED
@@ -1,126 +1,154 @@
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';
19
- import { applyApproval, resolveApprovalConfig } from './tools/approval.js';
20
- import { wrapWithGuardrails } from './guardrails/runner.js';
32
+ import { runGuardrails, handleGuardrailResults } from './guardrails/runner.js';
33
+ import { contentFilter } from './guardrails/built-ins.js';
21
34
  import { MarkdownMemoryStore } from './memory/store.js';
22
35
  import { loadMemoryContext } from './memory/loader.js';
23
36
  import { createMemoryTools } from './memory/tools.js';
24
37
  import { initObservability, createTelemetrySettings } from './observability/index.js';
38
+ import { buildDynamicSystemPrompt } from './prompts/context.js';
25
39
  // ============================================================================
26
40
  // Logger
27
41
  // ============================================================================
28
42
  const log = createLogger('@agntk/core:agent');
29
43
  // ============================================================================
30
- // Tool Building
44
+ // Constants
31
45
  // ============================================================================
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)));
46
+ const DEFAULT_MAX_STEPS = 25;
47
+ const SUB_AGENT_MAX_STEPS = 15;
48
+ const DEFAULT_MAX_SPAWN_DEPTH = 2;
49
+ const AGENT_STATE_BASE = '.agntk/agents';
50
+ // ============================================================================
51
+ // Helpers
52
+ // ============================================================================
53
+ /**
54
+ * Resolve the persistent state directory for a named agent.
55
+ * ~/.agntk/agents/{name}/
56
+ */
57
+ function resolveAgentStatePath(name) {
58
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase();
59
+ return resolve(homedir(), AGENT_STATE_BASE, safeName);
60
+ }
61
+ /**
62
+ * Detect if telemetry should be enabled from env vars.
63
+ */
64
+ function detectTelemetry() {
65
+ return !!(process.env.LANGFUSE_PUBLIC_KEY &&
66
+ process.env.LANGFUSE_SECRET_KEY);
67
+ }
68
+ /**
69
+ * Build the base instructions for the agent.
70
+ */
71
+ function buildBaseInstructions(name, userInstructions, skillsPrompt) {
72
+ const parts = [];
73
+ parts.push(`You are ${name}, a capable AI agent.`);
74
+ if (userInstructions) {
75
+ parts.push('');
76
+ parts.push(userInstructions);
45
77
  }
46
- // Remove disabled tools
47
- if (disableTools?.length) {
48
- for (const name of disableTools) {
49
- delete allTools[name];
50
- }
78
+ parts.push('');
79
+ parts.push('You have access to a full suite of tools including file operations, ' +
80
+ 'shell commands, code search (grep, glob, ast-grep), a browser, ' +
81
+ 'deep reasoning, planning, and persistent memory. ' +
82
+ 'You can spawn sub-agents for complex tasks that benefit from delegation. ' +
83
+ 'Use the remember tool to persist important findings across sessions. ' +
84
+ 'Use the recall tool to search your memory for relevant context.');
85
+ if (skillsPrompt) {
86
+ parts.push('');
87
+ parts.push(skillsPrompt);
51
88
  }
52
- log.debug('Tools ready', { tools: Object.keys(allTools) });
53
- return allTools;
89
+ return parts.join('\n');
54
90
  }
55
91
  // ============================================================================
56
- // Create Agent Factory
92
+ // Agent Factory
57
93
  // ============================================================================
58
94
  /**
59
- * Creates an agent with the given options.
95
+ * Create an agent fully equipped, zero config.
60
96
  *
61
97
  * @example
62
98
  * ```typescript
63
99
  * const agent = createAgent({
64
- * role: 'coder',
65
- * workspaceRoot: '/my/project',
66
- * toolPreset: 'standard',
100
+ * name: 'deploy-bot',
101
+ * instructions: 'You manage deployments for our k8s cluster.',
67
102
  * });
68
103
  *
69
- * const result = await agent.generate({ prompt: 'Create a hello world function' });
70
- * console.log(result.text);
104
+ * const result = await agent.stream({ prompt: 'Roll back staging to yesterday' });
105
+ * for await (const chunk of result.fullStream) {
106
+ * if (chunk.type === 'text-delta') process.stdout.write(chunk.text ?? '');
107
+ * }
71
108
  * ```
72
109
  */
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
- }
110
+ export function createAgent(options, _internal = {}) {
111
+ const { name, instructions, workspaceRoot = process.cwd(), } = options;
112
+ const spawnDepth = _internal._spawnDepth ?? 0;
113
+ const isSubAgent = spawnDepth > 0;
114
+ const maxSteps = options.maxSteps ?? (isSubAgent ? SUB_AGENT_MAX_STEPS : DEFAULT_MAX_STEPS);
115
+ log.info('Creating agent', { name, maxSteps, workspaceRoot, spawnDepth });
116
+ // ── 1. Resolve model ──────────────────────────────────────────────────
117
+ const model = options.model ?? resolveModel({ tier: 'standard' });
118
+ log.debug('Model resolved', { hasExplicitModel: !!options.model });
119
+ // ── 2. Build ALL tools ────────────────────────────────────────────────
120
+ let tools = createToolPreset('full', { workspaceRoot });
121
+ // Merge user-provided tools (escape hatch for testing / custom tools)
122
+ if (options.tools) {
123
+ Object.assign(tools, options.tools);
93
124
  }
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,
125
+ log.debug('Base tools built', { count: Object.keys(tools).length });
126
+ // ── 3. Memory — always on ─────────────────────────────────────────────
127
+ const agentStatePath = resolveAgentStatePath(name);
128
+ const memoryStore = new MarkdownMemoryStore({
129
+ projectDir: agentStatePath,
130
+ globalDir: '.agntk',
131
+ workspaceRoot,
104
132
  });
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 });
133
+ const memoryTools = createMemoryTools({ store: memoryStore, model });
134
+ Object.assign(tools, memoryTools);
135
+ log.info('Memory enabled', { agentStatePath });
136
+ // ── 4. Sub-agents — recursive creation ────────────────────────────────
137
+ if (spawnDepth < DEFAULT_MAX_SPAWN_DEPTH) {
110
138
  const spawnTool = createSpawnAgentTool({
111
- maxSpawnDepth: options.maxSpawnDepth ?? 2,
112
- currentDepth: 0,
139
+ maxSpawnDepth: DEFAULT_MAX_SPAWN_DEPTH,
140
+ currentDepth: spawnDepth,
113
141
  createAgent: (subAgentOptions) => {
114
- log.info('Spawning sub-agent', {
115
- parentId: agentId,
116
- role: subAgentOptions.role,
117
- });
142
+ const subName = `${name}/${subAgentOptions.role}`;
143
+ log.info('Spawning sub-agent', { parentName: name, subName });
118
144
  const subAgent = createAgent({
119
- role: subAgentOptions.role,
120
- systemPrompt: subAgentOptions.instructions,
121
- enableSubAgents: false, // Prevent recursion
145
+ name: subName,
146
+ instructions: subAgentOptions.instructions,
122
147
  workspaceRoot,
123
- maxSteps: subAgentOptions.maxSpawnDepth ?? 5,
148
+ maxSteps: SUB_AGENT_MAX_STEPS,
149
+ model: options.model,
150
+ }, {
151
+ _spawnDepth: spawnDepth + 1,
124
152
  });
125
153
  return {
126
154
  stream: (input) => {
@@ -132,7 +160,7 @@ export function createAgent(options = {}) {
132
160
  yield chunk;
133
161
  }
134
162
  })(),
135
- text: streamPromise.then(r => r.text),
163
+ text: streamPromise.then((r) => r.text),
136
164
  };
137
165
  },
138
166
  };
@@ -140,241 +168,166 @@ export function createAgent(options = {}) {
140
168
  });
141
169
  tools = { ...tools, spawn_agent: spawnTool };
142
170
  }
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(() => { });
171
+ // ── 5. ModelRetry always on ─────────────────────────────────────────
172
+ tools = wrapAllToolsWithRetry(tools, 3);
173
+ // ── 6. Auto-discover skills ───────────────────────────────────────────
174
+ let skillsPrompt = '';
175
+ try {
176
+ const discovered = discoverSkills(undefined, workspaceRoot);
177
+ const eligible = filterEligibleSkills(discovered);
178
+ if (eligible.length > 0) {
179
+ const loaded = eligible.map((s) => loadSkillContent(s));
180
+ skillsPrompt = buildSkillsSystemPrompt(loaded);
181
+ log.info('Skills discovered', { count: eligible.length });
182
+ }
185
183
  }
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 });
184
+ catch (err) {
185
+ log.warn('Skill discovery failed', { error: err instanceof Error ? err.message : String(err) });
191
186
  }
192
- // Build stop conditions
187
+ // ── 7. Build system prompt ────────────────────────────────────────────
188
+ let augmentedSystemPrompt = buildBaseInstructions(name, instructions, skillsPrompt);
189
+ // ── 8. Stop conditions ────────────────────────────────────────────────
193
190
  const stopConditions = [
194
191
  stepCountIs(maxSteps),
195
192
  ];
196
193
  if (options.usageLimits) {
197
- log.debug('Usage limits configured', { limits: options.usageLimits });
198
194
  stopConditions.push(usageLimitStop(options.usageLimits));
199
195
  }
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 });
207
- }
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
- })
214
- : undefined;
215
- // Create the ToolLoopAgent
216
- log.debug('Creating ToolLoopAgent', {
217
- promptLength: augmentedSystemPrompt.length,
218
- toolCount: Object.keys(tools).length,
219
- telemetry: !!telemetrySettings,
196
+ // ── 9. Reflection always on (reflact strategy) ──────────────────────
197
+ const prepareStep = createReflectionPrepareStep(augmentedSystemPrompt, {
198
+ strategy: 'reflact',
220
199
  });
200
+ // ── 10. Guardrails — always on (output: PII content filter) ───────────
201
+ const outputGuardrails = [contentFilter()];
202
+ // ── 11. Telemetry — auto-detect ───────────────────────────────────────
203
+ const telemetryEnabled = detectTelemetry();
204
+ const telemetrySettings = telemetryEnabled
205
+ ? createTelemetrySettings({ functionId: `agent:${name}` })
206
+ : undefined;
207
+ // ── 12. Build the ToolLoopAgent ───────────────────────────────────────
221
208
  const toolLoopAgent = new ToolLoopAgent({
222
209
  model,
223
210
  instructions: augmentedSystemPrompt,
224
211
  tools,
225
212
  stopWhen: stopConditions,
226
- // prepareCall: dynamically inject the current system prompt (may be updated by memory loading)
227
213
  prepareCall: (opts) => ({ ...opts, instructions: augmentedSystemPrompt }),
228
- ...(prepareStep ? { prepareStep } : {}),
214
+ prepareStep,
229
215
  ...(telemetrySettings ? { experimental_telemetry: telemetrySettings } : {}),
230
216
  });
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 () => {
217
+ log.debug('ToolLoopAgent created', {
218
+ toolCount: Object.keys(tools).length,
219
+ telemetry: !!telemetrySettings,
220
+ });
221
+ // ── Lazy initializers ─────────────────────────────────────────────────
222
+ const agentLog = log.child({ agent: name });
223
+ let initialized = false;
224
+ let initPromise = null;
225
+ async function ensureInit() {
226
+ if (initialized)
227
+ return;
228
+ if (initPromise)
229
+ return initPromise;
230
+ initPromise = (async () => {
231
+ // Load memory context into system prompt
243
232
  try {
244
233
  const memoryContext = await loadMemoryContext(memoryStore);
245
234
  if (memoryContext) {
246
235
  augmentedSystemPrompt = memoryContext + '\n\n' + augmentedSystemPrompt;
247
- // prepareCall() will pick up the updated augmentedSystemPrompt on next generate/stream
248
236
  agentLog.debug('Memory context injected', { chars: memoryContext.length });
249
237
  }
250
238
  }
251
239
  catch (err) {
252
- agentLog.warn('Failed to load memory context', {
240
+ agentLog.warn('Memory context loading failed', {
253
241
  error: err instanceof Error ? err.message : String(err),
254
242
  });
255
243
  }
256
- finally {
257
- memoryContextLoaded = true;
244
+ // Inject dynamic environment context
245
+ try {
246
+ augmentedSystemPrompt = await buildDynamicSystemPrompt(augmentedSystemPrompt, {
247
+ workspaceRoot,
248
+ includeWorkspaceMap: true,
249
+ });
258
250
  }
259
- })();
260
- return memoryInitPromise;
261
- }
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 });
251
+ catch (err) {
252
+ agentLog.warn('Dynamic context injection failed', {
253
+ error: err instanceof Error ? err.message : String(err),
254
+ });
273
255
  }
274
- })
275
- .catch((err) => {
276
- agentLog.warn('Telemetry initialization failed', {
277
- error: err instanceof Error ? err.message : String(err),
278
- });
279
- });
280
- return telemetryInitPromise;
256
+ // Apply durable wrapping if workflow runtime is available
257
+ try {
258
+ const workflowAvailable = await checkWorkflowAvailability();
259
+ if (workflowAvailable) {
260
+ tools = wrapToolsAsDurable(tools, { retryCount: 3 });
261
+ agentLog.info('Durable tool wrapping active');
262
+ }
263
+ }
264
+ catch {
265
+ agentLog.debug('Workflow detection failed — skipping durable wrapping');
266
+ }
267
+ // Initialize telemetry if detected
268
+ if (telemetryEnabled) {
269
+ try {
270
+ await initObservability({ provider: 'langfuse' });
271
+ agentLog.info('Telemetry initialized');
272
+ }
273
+ catch (err) {
274
+ agentLog.warn('Telemetry initialization failed', {
275
+ error: err instanceof Error ? err.message : String(err),
276
+ });
277
+ }
278
+ }
279
+ initialized = true;
280
+ })();
281
+ return initPromise;
281
282
  }
282
- // Create the agent instance
283
+ // ── Build the agent instance ──────────────────────────────────────────
283
284
  const agent = {
284
- agentId,
285
- role,
286
- init: async () => {
287
- await Promise.all([ensureMemoryContext(), ensureTelemetryInit()]);
288
- },
289
- getToolLoopAgent: () => toolLoopAgent,
285
+ name,
286
+ init: ensureInit,
290
287
  getSystemPrompt: () => augmentedSystemPrompt,
288
+ getToolNames: () => Object.keys(tools),
291
289
  stream: async (input) => {
292
- await Promise.all([ensureMemoryContext(), ensureTelemetryInit()]);
290
+ await ensureInit();
293
291
  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,
292
+ const result = await toolLoopAgent.stream({ prompt: input.prompt });
293
+ // Apply output guardrails to the final text
294
+ const guardedText = result.text.then(async (text) => {
295
+ if (!text || outputGuardrails.length === 0)
296
+ return text;
297
+ try {
298
+ const { results, filteredText } = await runGuardrails(outputGuardrails, text, {
299
+ prompt: input.prompt,
300
+ phase: 'output',
301
+ });
302
+ const check = handleGuardrailResults(results, text, filteredText, 'output', 'filter');
303
+ if (check.blocked) {
304
+ agentLog.info('Output guardrails filtered content', {
305
+ guards: results.filter((r) => !r.passed).map((r) => r.name),
337
306
  });
307
+ return check.text;
338
308
  }
339
309
  }
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,
310
+ catch (err) {
311
+ agentLog.warn('Output guardrails failed', {
312
+ error: err instanceof Error ? err.message : String(err),
348
313
  });
349
314
  }
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
- }
315
+ return text;
316
+ });
317
+ return {
318
+ fullStream: result.fullStream,
319
+ text: guardedText,
320
+ usage: result.totalUsage,
321
+ };
365
322
  },
366
323
  };
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 });
324
+ log.info('Agent created', {
325
+ name,
326
+ spawnDepth,
327
+ toolCount: Object.keys(tools).length,
328
+ memoryPath: agentStatePath,
329
+ telemetry: telemetryEnabled,
330
+ });
378
331
  return agent;
379
332
  }
380
333
  export default createAgent;