praxis-agent 0.28.0 → 0.29.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.
@@ -208,6 +208,7 @@ export declare class ClaudeSessionService {
208
208
  private applyPermissionUpdates;
209
209
  close(): Promise<void>;
210
210
  transitionHookSession(sessionId: string, reason: Exclude<HookSessionEndReason, 'other'>): Promise<void>;
211
+ reloadContextResources(sessionId: string): void;
211
212
  createHostedToolRegistry(sessionId: string): ToolRegistry;
212
213
  run(prompt: string, signal?: AbortSignal, sessionId?: string, name?: string, images?: readonly ModelImage[], documents?: readonly ModelDocument[]): Promise<SessionRunResult>;
213
214
  runShell(command: string, signal?: AbortSignal, sessionId?: string, name?: string): Promise<SessionRunResult>;
@@ -756,6 +756,16 @@ export class ClaudeSessionService {
756
756
  }
757
757
  async transitionHookSession(sessionId, reason) {
758
758
  await this.hookLifecycle.transition(sessionId, reason);
759
+ this.options.contextAssembler?.invalidate?.({
760
+ lifecycleId: sessionId,
761
+ reason: reason === 'resume' ? 'restore' : 'clear',
762
+ });
763
+ }
764
+ reloadContextResources(sessionId) {
765
+ this.options.contextAssembler?.invalidate?.({
766
+ lifecycleId: sessionId,
767
+ reason: 'resource-reload',
768
+ });
759
769
  }
760
770
  createHostedToolRegistry(sessionId) {
761
771
  const baseTools = this.options.tools;
@@ -996,6 +1006,7 @@ export class ClaudeSessionService {
996
1006
  }
997
1007
  const assembledContext = await this.options.contextAssembler?.assemble({
998
1008
  cwd: this.activeCwd(),
1009
+ lifecycleId: activeSessionId,
999
1010
  });
1000
1011
  const messages = [
1001
1012
  ...(assembledContext?.systemMessages ?? []),
@@ -1106,13 +1117,18 @@ export class ClaudeSessionService {
1106
1117
  const agent = this.resolveAgent(agentName);
1107
1118
  const provider = this.providerForAgent(agent);
1108
1119
  this.activeProvider = provider;
1120
+ const agentSystem = await this.mainAgentSystemPrompt(agent);
1109
1121
  const assembledContext = await this.options.contextAssembler?.assemble({
1110
1122
  cwd: this.activeCwd(),
1123
+ lifecycleId: sessionId,
1124
+ ...(agentSystem ? { mode: 'agent', baseSystemPrompt: agentSystem } : {}),
1111
1125
  });
1112
- const agentSystem = await this.mainAgentSystemPrompt(agent);
1113
- const assembledSystemMessages = this.assembledSystemMessages(agent, assembledContext?.systemMessages ?? []);
1126
+ const hasPromptManifest = assembledContext?.promptSections !== undefined;
1127
+ const assembledSystemMessages = hasPromptManifest
1128
+ ? (assembledContext?.systemMessages ?? [])
1129
+ : this.assembledSystemMessages(agent, assembledContext?.systemMessages ?? []);
1114
1130
  const contextMessages = [
1115
- ...(agentSystem
1131
+ ...(!hasPromptManifest && agentSystem
1116
1132
  ? [{ role: 'system', content: agentSystem }]
1117
1133
  : []),
1118
1134
  ...assembledSystemMessages,
@@ -1410,12 +1426,22 @@ export class ClaudeSessionService {
1410
1426
  this.sessionCwds.set(sessionId, cwd);
1411
1427
  this.runtimeCwd = cwd;
1412
1428
  this.options.workspace?.setCwd(cwd);
1429
+ this.options.contextAssembler?.invalidate?.({
1430
+ lifecycleId: sessionId,
1431
+ reason: 'cwd',
1432
+ });
1413
1433
  return cwd;
1414
1434
  }
1415
1435
  this.runtimeCwd = cwd;
1416
1436
  this.options.workspace?.setCwd(cwd);
1417
1437
  if (sessionId)
1418
1438
  this.sessionCwds.set(sessionId, cwd);
1439
+ if (sessionId) {
1440
+ this.options.contextAssembler?.invalidate?.({
1441
+ lifecycleId: sessionId,
1442
+ reason: 'cwd',
1443
+ });
1444
+ }
1419
1445
  return cwd;
1420
1446
  }
1421
1447
  async recordCdUsage(sessionId) {
@@ -1791,6 +1817,10 @@ export class ClaudeSessionService {
1791
1817
  if (result.status === 'conflict') {
1792
1818
  throw new Error('Generated Claude fork session already exists');
1793
1819
  }
1820
+ this.options.contextAssembler?.invalidate?.({
1821
+ lifecycleId: sessionId,
1822
+ reason: 'fork',
1823
+ });
1794
1824
  return { sessionId, parentSessionId };
1795
1825
  }
1796
1826
  async ensureFork(parentSessionId, sessionId, checkpoint) {
@@ -1799,8 +1829,13 @@ export class ClaudeSessionService {
1799
1829
  const expected = await this.nativeForkEntries(parentSessionId, sessionId, checkpoint?.resumeSessionAt, checkpoint?.entryCount);
1800
1830
  const target = this.store(sessionId);
1801
1831
  const created = await target.create(expected);
1802
- if (created.status === 'created')
1832
+ if (created.status === 'created') {
1833
+ this.options.contextAssembler?.invalidate?.({
1834
+ lifecycleId: sessionId,
1835
+ reason: 'fork',
1836
+ });
1803
1837
  return { sessionId, parentSessionId };
1838
+ }
1804
1839
  const existing = await target.withLease((lease) => lease.load());
1805
1840
  if (existing.status === 'conflict') {
1806
1841
  throw new Error(`Claude transcript fork conflict: ${existing.reason}`);
@@ -1810,6 +1845,10 @@ export class ClaudeSessionService {
1810
1845
  JSON.stringify(entry))) {
1811
1846
  throw new Error('Claude handoff target is not the expected native fork');
1812
1847
  }
1848
+ this.options.contextAssembler?.invalidate?.({
1849
+ lifecycleId: sessionId,
1850
+ reason: 'fork',
1851
+ });
1813
1852
  return { sessionId, parentSessionId };
1814
1853
  }
1815
1854
  async nativeForkEntries(parentSessionId, sessionId, resumeSessionAt, sourceEntryCount) {
@@ -2442,6 +2481,10 @@ export class ClaudeSessionService {
2442
2481
  currentTurnToolCalls += 1;
2443
2482
  const transition = this.worktreeManager?.consumeTransition(call.id);
2444
2483
  if (transition) {
2484
+ this.options.contextAssembler?.invalidate?.({
2485
+ lifecycleId: sessionId,
2486
+ reason: 'worktree',
2487
+ });
2445
2488
  const stateEntry = {
2446
2489
  type: 'worktree-state',
2447
2490
  worktreeSession: transition.state,
@@ -2584,29 +2627,83 @@ export class ClaudeSessionService {
2584
2627
  let planModeMessage;
2585
2628
  let sessionMemoryMessage = null;
2586
2629
  let contextMessages = [];
2630
+ let stableSystemMessageCount;
2587
2631
  const refreshRuntimeContext = async () => {
2588
- assembledContext = await this.options.contextAssembler?.assemble({
2589
- cwd: this.activeCwd(),
2590
- });
2591
2632
  agentSystem = await this.mainAgentSystemPrompt(agent);
2592
- const assembledSystemMessages = this.assembledSystemMessages(agent, assembledContext?.systemMessages ?? []);
2593
2633
  planModeMessage =
2594
2634
  this.options.interactiveTools?.contextMessage(sessionId);
2595
2635
  sessionMemoryMessage = sessionMemory
2596
2636
  ? this.sessionMemoryMessage(await sessionMemory.summary())
2597
2637
  : null;
2598
- contextMessages = [
2638
+ const additionalSections = [
2639
+ ...(planModeMessage
2640
+ ? [
2641
+ {
2642
+ id: 'plan-mode',
2643
+ placement: 'system',
2644
+ stability: 'volatile',
2645
+ content: planModeMessage,
2646
+ },
2647
+ ]
2648
+ : []),
2649
+ ...(sessionMemoryMessage
2650
+ ? [
2651
+ {
2652
+ id: 'session-memory',
2653
+ placement: 'system',
2654
+ stability: 'volatile',
2655
+ content: sessionMemoryMessage,
2656
+ },
2657
+ ]
2658
+ : []),
2659
+ ...(this.options.brief
2660
+ ? [
2661
+ {
2662
+ id: 'brief-output',
2663
+ placement: 'system',
2664
+ stability: 'volatile',
2665
+ content: CLAUDE_USER_MESSAGE_PROMPT,
2666
+ },
2667
+ ]
2668
+ : []),
2669
+ ...(this.options.structuredOutputSchema
2670
+ ? [
2671
+ {
2672
+ id: 'structured-output',
2673
+ placement: 'system',
2674
+ stability: 'volatile',
2675
+ content: 'You MUST call StructuredOutput exactly once at the end with a value matching the requested JSON Schema.',
2676
+ },
2677
+ ]
2678
+ : []),
2679
+ ];
2680
+ assembledContext = await this.options.contextAssembler?.assemble({
2681
+ cwd: this.activeCwd(),
2682
+ lifecycleId: sessionId,
2599
2683
  ...(agentSystem
2684
+ ? { mode: 'agent', baseSystemPrompt: agentSystem }
2685
+ : {}),
2686
+ additionalSections,
2687
+ });
2688
+ const hasPromptManifest = assembledContext?.promptSections !== undefined;
2689
+ stableSystemMessageCount = hasPromptManifest
2690
+ ? assembledContext?.stableSystemSectionCount
2691
+ : undefined;
2692
+ const assembledSystemMessages = hasPromptManifest
2693
+ ? (assembledContext?.systemMessages ?? [])
2694
+ : this.assembledSystemMessages(agent, assembledContext?.systemMessages ?? []);
2695
+ contextMessages = [
2696
+ ...(!hasPromptManifest && agentSystem
2600
2697
  ? [{ role: 'system', content: agentSystem }]
2601
2698
  : []),
2602
2699
  ...assembledSystemMessages,
2603
- ...(planModeMessage
2700
+ ...(!hasPromptManifest && planModeMessage
2604
2701
  ? [{ role: 'system', content: planModeMessage }]
2605
2702
  : []),
2606
- ...(sessionMemoryMessage
2703
+ ...(!hasPromptManifest && sessionMemoryMessage
2607
2704
  ? [{ role: 'system', content: sessionMemoryMessage }]
2608
2705
  : []),
2609
- ...(this.options.brief
2706
+ ...(!hasPromptManifest && this.options.brief
2610
2707
  ? [
2611
2708
  {
2612
2709
  role: 'system',
@@ -2614,7 +2711,7 @@ export class ClaudeSessionService {
2614
2711
  },
2615
2712
  ]
2616
2713
  : []),
2617
- ...(this.options.structuredOutputSchema
2714
+ ...(!hasPromptManifest && this.options.structuredOutputSchema
2618
2715
  ? [
2619
2716
  {
2620
2717
  role: 'system',
@@ -2915,6 +3012,10 @@ export class ClaudeSessionService {
2915
3012
  if (outcome)
2916
3013
  await recordHookOutcome(outcome);
2917
3014
  }
3015
+ this.options.contextAssembler?.invalidate?.({
3016
+ lifecycleId: sessionId,
3017
+ reason: 'compact',
3018
+ });
2918
3019
  await refreshRuntimeContext();
2919
3020
  this.options.eventSink?.({
2920
3021
  type: 'compact-boundary',
@@ -3086,6 +3187,9 @@ export class ClaudeSessionService {
3086
3187
  ...contextMessages,
3087
3188
  ...injectTurnContext(projectClaudeModelMessages(snapshot.entries)),
3088
3189
  ],
3190
+ ...(stableSystemMessageCount === undefined
3191
+ ? {}
3192
+ : { stableSystemMessageCount }),
3089
3193
  cwd: this.activeCwd(),
3090
3194
  toolResultDirectory,
3091
3195
  observer,
@@ -3099,6 +3203,12 @@ export class ClaudeSessionService {
3099
3203
  : {}),
3100
3204
  reloadMessages: async () => {
3101
3205
  await compactIfNeeded([], currentTurnUserMessages ?? []);
3206
+ if (stableSystemMessageCount === undefined) {
3207
+ delete runtimeRequest.stableSystemMessageCount;
3208
+ }
3209
+ else {
3210
+ runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
3211
+ }
3102
3212
  return [
3103
3213
  ...contextMessages,
3104
3214
  ...injectTurnContext(projectClaudeModelMessages(snapshot.entries)),
@@ -3213,6 +3323,12 @@ export class ClaudeSessionService {
3213
3323
  ...contextMessages,
3214
3324
  ...injectTurnContext(projectClaudeModelMessages(snapshot.entries)),
3215
3325
  ];
3326
+ if (stableSystemMessageCount === undefined) {
3327
+ delete runtimeRequest.stableSystemMessageCount;
3328
+ }
3329
+ else {
3330
+ runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
3331
+ }
3216
3332
  try {
3217
3333
  result = await attemptMainTurn();
3218
3334
  }
@@ -1493,10 +1493,16 @@ export class ClaudeSubagentExecutor {
1493
1493
  const assembleMessages = async () => {
1494
1494
  const assembledContext = await this.options.contextAssembler?.assemble({
1495
1495
  cwd,
1496
+ lifecycleId: options.agentId,
1497
+ mode: 'subagent',
1498
+ baseSystemPrompt: system,
1496
1499
  });
1500
+ const composedSubagentPolicy = assembledContext?.promptSections?.some((section) => section.id === 'subagent-policy');
1497
1501
  const messages = [
1498
1502
  ...(assembledContext?.systemMessages ?? []),
1499
- { role: 'system', content: system },
1503
+ ...(composedSubagentPolicy
1504
+ ? []
1505
+ : [{ role: 'system', content: system }]),
1500
1506
  ...injectFirstUserMessageContext(projectClaudeModelMessages(snapshot.entries), assembledContext?.firstUserMessageContext),
1501
1507
  ...preloadedSkills,
1502
1508
  ];
@@ -802,12 +802,6 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
802
802
  ? {}
803
803
  : { permissionMode: interactivePermissionMode }),
804
804
  }, cwd);
805
- const effectiveAppendSystemPrompt = [
806
- runtimeSettingsPrompt,
807
- cli.appendSystemPrompt,
808
- ]
809
- .filter((value) => value !== undefined)
810
- .join('\n');
811
805
  const debug = cli.debug !== undefined || cli.debugFile !== undefined
812
806
  ? createCliDebugSink(eventSink, {
813
807
  cwd,
@@ -1175,6 +1169,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1175
1169
  });
1176
1170
  return filterDisabledMcpResources(candidates, await management.disabled());
1177
1171
  };
1172
+ let contextAssembler;
1178
1173
  const mcpTools = await ClaudeMcpToolRegistry.connect({
1179
1174
  base: simpleMode
1180
1175
  ? localTools
@@ -1219,6 +1214,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1219
1214
  configRoot,
1220
1215
  onWarning: (message) => runtimeEventSink({ type: 'warning', message }),
1221
1216
  onPromptsChanged: (prompts) => extensions.setMcpPrompts(prompts),
1217
+ onInstructionsChanged: () => contextAssembler?.invalidate({ reason: 'tool-pool' }),
1222
1218
  authenticateServer: async (name) => {
1223
1219
  const record = await new ClaudeMcpManagement({
1224
1220
  dataPlane,
@@ -1374,6 +1370,54 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1374
1370
  },
1375
1371
  })
1376
1372
  : undefined;
1373
+ contextAssembler = new ClaudeContextAssembler({
1374
+ loadResources: loadContextResources,
1375
+ loadDynamicContext: (runtimeCwd = workspace.cwd()) => loadClaudeDynamicContext({
1376
+ cwd: runtimeCwd,
1377
+ ...(memoryDirectory ? { memoryDirectory } : {}),
1378
+ }),
1379
+ loadMcpInstructions: async () => mcpTools.instructions(),
1380
+ loadSessionGuidance: async () => {
1381
+ const toolNames = [
1382
+ ...new Set([
1383
+ ...filteredTools.definitions().map((definition) => definition.name),
1384
+ ...selectedTaskRuntimeTools,
1385
+ ...selectedScheduledTools,
1386
+ ...selectedWorkflowTools,
1387
+ ...selectedWorktreeTools,
1388
+ ...selectedInteractiveTools,
1389
+ ...(enableSubagents ? selectedAgentTools : []),
1390
+ ]),
1391
+ ].sort();
1392
+ const skillNames = extensions
1393
+ .modelInvocableSkills()
1394
+ .map((skill) => skill.name)
1395
+ .sort();
1396
+ if (toolNames.length === 0 &&
1397
+ skillNames.length === 0 &&
1398
+ runtimeSettingsPrompt === undefined)
1399
+ return undefined;
1400
+ return [
1401
+ '# Session capabilities',
1402
+ ...(toolNames.length > 0
1403
+ ? [`Enabled tools: ${toolNames.join(', ')}`]
1404
+ : []),
1405
+ ...(skillNames.length > 0
1406
+ ? [`Model-invocable skills: ${skillNames.join(', ')}`]
1407
+ : []),
1408
+ 'Use a capability only when it directly helps complete the request, and follow its declared input contract.',
1409
+ ...(runtimeSettingsPrompt ? ['', runtimeSettingsPrompt] : []),
1410
+ ].join('\n');
1411
+ },
1412
+ excludeDynamicSystemPromptSections: cli.excludeDynamicSystemPromptSections,
1413
+ ...(cli.systemPrompt === undefined
1414
+ ? {}
1415
+ : { systemPrompt: cli.systemPrompt }),
1416
+ ...(cli.appendSystemPrompt
1417
+ ? { appendSystemPrompt: cli.appendSystemPrompt }
1418
+ : {}),
1419
+ ...(simpleMode ? { bare: true } : {}),
1420
+ });
1377
1421
  const service = new ClaudeSessionService({
1378
1422
  ...options,
1379
1423
  provider: hostedToolProvider,
@@ -1409,20 +1453,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1409
1453
  ...(interactiveTools ? { interactiveTools } : {}),
1410
1454
  ...(hooks ? { hooks } : {}),
1411
1455
  ...(selectedMainAgent ? { agent: selectedMainAgent } : {}),
1412
- contextAssembler: new ClaudeContextAssembler({
1413
- loadResources: loadContextResources,
1414
- loadDynamicContext: (runtimeCwd = workspace.cwd()) => loadClaudeDynamicContext({
1415
- cwd: runtimeCwd,
1416
- ...(memoryDirectory ? { memoryDirectory } : {}),
1417
- }),
1418
- excludeDynamicSystemPromptSections: cli.excludeDynamicSystemPromptSections,
1419
- ...(cli.systemPrompt === undefined
1420
- ? {}
1421
- : { systemPrompt: cli.systemPrompt }),
1422
- ...(effectiveAppendSystemPrompt
1423
- ? { appendSystemPrompt: effectiveAppendSystemPrompt }
1424
- : {}),
1425
- }),
1456
+ contextAssembler,
1426
1457
  conditionalRuleResolver: new ClaudeConditionalRuleResolver({
1427
1458
  loadResources: loadContextResources,
1428
1459
  }),
@@ -1,12 +1,20 @@
1
- import type { AssembledContext, ContextAssembler } from '../../core/context.js';
1
+ import type { AssembledContext, ContextAssembler, ContextAssemblyOptions, ContextInvalidationOptions } from '../../core/context.js';
2
2
  import { type ClaudeDynamicContextSections } from './dynamic-context.js';
3
3
  import type { ClaudeContextResources, ClaudeConditionalRule } from './shared-resources.js';
4
4
  export interface ClaudeContextAssemblerOptions {
5
5
  loadResources(cwd?: string): Promise<ClaudeContextResources>;
6
6
  loadDynamicContext?(cwd?: string): Promise<ClaudeDynamicContextSections>;
7
+ loadMcpInstructions?(): Promise<readonly ClaudeMcpInstruction[]>;
8
+ loadSessionGuidance?(): Promise<string | undefined>;
7
9
  excludeDynamicSystemPromptSections?: boolean;
8
10
  systemPrompt?: string;
9
11
  appendSystemPrompt?: string;
12
+ bare?: boolean;
13
+ now?(): Date;
14
+ }
15
+ export interface ClaudeMcpInstruction {
16
+ server: string;
17
+ instructions: string;
10
18
  }
11
19
  export type ClaudeConditionalRuleResolverOptions = ClaudeContextAssemblerOptions;
12
20
  export declare class ClaudeConditionalRuleResolver {
@@ -16,9 +24,16 @@ export declare class ClaudeConditionalRuleResolver {
16
24
  }
17
25
  export declare class ClaudeContextAssembler implements ContextAssembler {
18
26
  private readonly options;
27
+ private readonly composer;
28
+ private readonly snapshots;
19
29
  constructor(options: ClaudeContextAssemblerOptions);
20
- assemble(options?: {
21
- cwd?: string;
22
- }): Promise<AssembledContext>;
30
+ assemble(options?: ContextAssemblyOptions): Promise<AssembledContext>;
31
+ invalidate(options: ContextInvalidationOptions): void;
32
+ private snapshot;
33
+ private loadResources;
34
+ private loadDynamicContext;
35
+ private loadMcpInstructions;
36
+ private loadSessionGuidance;
37
+ private currentDate;
23
38
  }
24
39
  //# sourceMappingURL=context.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { isAbsolute, matchesGlob, relative, resolve, sep } from 'node:path';
2
+ import { PromptComposer, } from '../../core/prompt-composer.js';
2
3
  import { renderClaudeDynamicSystemContext, renderClaudeDynamicUserContext, } from './dynamic-context.js';
3
4
  function renderResources(title, resources) {
4
5
  const rendered = resources
@@ -40,22 +41,32 @@ export class ClaudeConditionalRuleResolver {
40
41
  }
41
42
  export class ClaudeContextAssembler {
42
43
  options;
44
+ composer = new PromptComposer();
45
+ snapshots = new Map();
43
46
  constructor(options) {
44
47
  this.options = options;
45
48
  }
46
49
  async assemble(options = {}) {
47
- const resources = await this.options.loadResources(options.cwd);
50
+ const snapshot = this.snapshot(options);
51
+ const mode = options.mode ??
52
+ (this.options.systemPrompt !== undefined
53
+ ? 'custom'
54
+ : this.options.bare
55
+ ? 'bare'
56
+ : 'default');
57
+ const resources = mode === 'bare'
58
+ ? { instructions: [], conditionalRules: [], memoryIndex: null }
59
+ : await this.loadResources(snapshot, options.cwd);
48
60
  const sections = [
49
61
  renderResources('Instructions', resources.instructions),
50
62
  renderResources('Auto-memory', resources.memoryIndex ? [limitMemoryIndex(resources.memoryIndex)] : []),
51
63
  ].filter((section) => section !== null);
52
- const messages = [];
53
- if (this.options.systemPrompt !== undefined) {
54
- messages.push({ role: 'system', content: this.options.systemPrompt });
55
- }
64
+ const sessionSections = [];
56
65
  if (sections.length > 0) {
57
- messages.push({
58
- role: 'system',
66
+ sessionSections.push({
67
+ id: 'shared-resources',
68
+ placement: 'system',
69
+ stability: 'session',
59
70
  content: `# Shared Claude context
60
71
 
61
72
  Instructions are ordered from broadest to most specific. Auto-memory is background context and does not override instructions.
@@ -63,36 +74,199 @@ Instructions are ordered from broadest to most specific. Auto-memory is backgrou
63
74
  ${sections.join('\n\n')}`,
64
75
  });
65
76
  }
66
- let firstUserMessageContext;
67
- if (this.options.systemPrompt === undefined &&
68
- this.options.loadDynamicContext !== undefined) {
69
- const dynamic = await this.options.loadDynamicContext(options.cwd);
77
+ const tailSections = [];
78
+ if (mode !== 'bare') {
79
+ sessionSections.push({
80
+ id: 'current-date',
81
+ placement: 'system',
82
+ stability: 'session',
83
+ content: `# Current date\n${this.currentDate(snapshot)}`,
84
+ });
85
+ }
86
+ if (mode !== 'bare') {
87
+ const guidance = await this.loadSessionGuidance(snapshot);
88
+ if (guidance?.trim()) {
89
+ sessionSections.push({
90
+ id: 'session-guidance',
91
+ placement: 'system',
92
+ stability: 'session',
93
+ content: guidance,
94
+ });
95
+ }
96
+ const mcpInstructions = await this.loadMcpInstructions(snapshot);
97
+ tailSections.push(...mcpInstructions
98
+ .filter(({ instructions }) => instructions.trim().length > 0)
99
+ .sort((left, right) => left.server.localeCompare(right.server))
100
+ .map(({ server, instructions }) => ({
101
+ id: `mcp-instructions:${server}`,
102
+ placement: 'system',
103
+ stability: 'volatile',
104
+ content: `# MCP server instructions: ${server}\n${instructions.trimEnd()}`,
105
+ })));
106
+ }
107
+ if (mode !== 'bare' && this.options.loadDynamicContext !== undefined) {
108
+ const dynamic = await this.loadDynamicContext(snapshot, options.cwd);
70
109
  if (this.options.excludeDynamicSystemPromptSections) {
71
110
  if (dynamic.memory) {
72
- messages.push({ role: 'system', content: dynamic.memory });
111
+ sessionSections.push({
112
+ id: 'memory-mechanics',
113
+ placement: 'system',
114
+ stability: 'session',
115
+ content: dynamic.memory,
116
+ });
73
117
  }
74
- firstUserMessageContext = renderClaudeDynamicUserContext({
75
- environment: dynamic.environment,
76
- ...(dynamic.gitStatus ? { gitStatus: dynamic.gitStatus } : {}),
118
+ tailSections.push({
119
+ id: 'relocated-runtime-context',
120
+ placement: 'first-user',
121
+ stability: 'session',
122
+ content: renderClaudeDynamicUserContext({
123
+ environment: dynamic.environment,
124
+ ...(dynamic.gitStatus ? { gitStatus: dynamic.gitStatus } : {}),
125
+ }),
77
126
  });
78
127
  }
79
128
  else {
80
- messages.push({
81
- role: 'system',
129
+ sessionSections.push({
130
+ id: 'runtime-context',
131
+ placement: 'system',
132
+ stability: 'session',
82
133
  content: renderClaudeDynamicSystemContext(dynamic),
83
134
  });
84
135
  }
85
136
  }
86
- if (this.options.appendSystemPrompt !== undefined) {
87
- messages.push({
88
- role: 'system',
89
- content: this.options.appendSystemPrompt,
90
- });
137
+ for (const section of options.additionalSections ?? []) {
138
+ const target = section.stability === 'volatile' || section.placement === 'first-user'
139
+ ? tailSections
140
+ : sessionSections;
141
+ target.push(section);
91
142
  }
143
+ const composition = this.composer.compose({
144
+ mode,
145
+ ...(options.baseSystemPrompt !== undefined
146
+ ? { baseSystemPrompt: options.baseSystemPrompt }
147
+ : this.options.systemPrompt !== undefined
148
+ ? { baseSystemPrompt: this.options.systemPrompt }
149
+ : {}),
150
+ ...(this.options.appendSystemPrompt !== undefined
151
+ ? { appendSystemPrompt: this.options.appendSystemPrompt }
152
+ : {}),
153
+ sessionSections,
154
+ tailSections,
155
+ });
92
156
  return {
93
- systemMessages: messages,
94
- ...(firstUserMessageContext ? { firstUserMessageContext } : {}),
157
+ systemMessages: composition.systemMessages,
158
+ promptSections: composition.sections,
159
+ stableSystemSectionCount: composition.stableSystemSectionCount,
160
+ ...(composition.firstUserMessageContext
161
+ ? { firstUserMessageContext: composition.firstUserMessageContext }
162
+ : {}),
95
163
  };
96
164
  }
165
+ invalidate(options) {
166
+ for (const [key, snapshot] of this.snapshots) {
167
+ if (options.lifecycleId !== undefined &&
168
+ snapshot.lifecycleId !== options.lifecycleId)
169
+ continue;
170
+ if (options.reason === 'resource-reload' ||
171
+ options.reason === 'compact') {
172
+ delete snapshot.resources;
173
+ continue;
174
+ }
175
+ if (options.reason === 'tool-pool') {
176
+ delete snapshot.mcpInstructions;
177
+ delete snapshot.sessionGuidance;
178
+ continue;
179
+ }
180
+ this.snapshots.delete(key);
181
+ }
182
+ }
183
+ snapshot(options) {
184
+ if (!options.lifecycleId)
185
+ return undefined;
186
+ const key = JSON.stringify([options.lifecycleId, options.cwd ?? null]);
187
+ let snapshot = this.snapshots.get(key);
188
+ if (!snapshot) {
189
+ snapshot = {
190
+ lifecycleId: options.lifecycleId,
191
+ ...(options.cwd ? { cwd: options.cwd } : {}),
192
+ };
193
+ this.snapshots.set(key, snapshot);
194
+ }
195
+ return snapshot;
196
+ }
197
+ loadResources(snapshot, cwd) {
198
+ if (!snapshot)
199
+ return this.options.loadResources(cwd);
200
+ if (!snapshot.resources) {
201
+ const pending = this.options.loadResources(cwd);
202
+ snapshot.resources = pending;
203
+ void pending.catch(() => {
204
+ if (snapshot.resources === pending)
205
+ delete snapshot.resources;
206
+ });
207
+ }
208
+ return snapshot.resources;
209
+ }
210
+ loadDynamicContext(snapshot, cwd) {
211
+ const load = this.options.loadDynamicContext;
212
+ if (!load)
213
+ throw new Error('Dynamic context loader is unavailable');
214
+ if (!snapshot)
215
+ return load(cwd);
216
+ if (!snapshot.dynamic) {
217
+ const pending = load(cwd);
218
+ snapshot.dynamic = pending;
219
+ void pending.catch(() => {
220
+ if (snapshot.dynamic === pending)
221
+ delete snapshot.dynamic;
222
+ });
223
+ }
224
+ return snapshot.dynamic;
225
+ }
226
+ loadMcpInstructions(snapshot) {
227
+ const load = this.options.loadMcpInstructions;
228
+ if (!load)
229
+ return Promise.resolve([]);
230
+ if (!snapshot)
231
+ return load();
232
+ if (!snapshot.mcpInstructions) {
233
+ const pending = load();
234
+ snapshot.mcpInstructions = pending;
235
+ void pending.catch(() => {
236
+ if (snapshot.mcpInstructions === pending)
237
+ delete snapshot.mcpInstructions;
238
+ });
239
+ }
240
+ return snapshot.mcpInstructions;
241
+ }
242
+ loadSessionGuidance(snapshot) {
243
+ const load = this.options.loadSessionGuidance;
244
+ if (!load)
245
+ return Promise.resolve(undefined);
246
+ if (!snapshot)
247
+ return load();
248
+ if (!snapshot.sessionGuidance) {
249
+ const pending = load();
250
+ snapshot.sessionGuidance = pending;
251
+ void pending.catch(() => {
252
+ if (snapshot.sessionGuidance === pending)
253
+ delete snapshot.sessionGuidance;
254
+ });
255
+ }
256
+ return snapshot.sessionGuidance;
257
+ }
258
+ currentDate(snapshot) {
259
+ if (snapshot?.currentDate)
260
+ return snapshot.currentDate;
261
+ const now = (this.options.now ?? (() => new Date()))();
262
+ const date = [
263
+ String(now.getFullYear()).padStart(4, '0'),
264
+ String(now.getMonth() + 1).padStart(2, '0'),
265
+ String(now.getDate()).padStart(2, '0'),
266
+ ].join('-');
267
+ if (snapshot)
268
+ snapshot.currentDate = date;
269
+ return date;
270
+ }
97
271
  }
98
272
  //# sourceMappingURL=context.js.map
@@ -1,16 +1,29 @@
1
1
  import type { ModelMessage } from './runtime.js';
2
+ import type { PromptCompositionMode, PromptSection } from './prompt-composer.js';
2
3
  export type SystemContextMessage = Extract<ModelMessage, {
3
4
  role: 'system';
4
5
  }>;
5
6
  export interface AssembledContext {
6
7
  systemMessages: readonly SystemContextMessage[];
7
8
  firstUserMessageContext?: string;
9
+ promptSections?: readonly PromptSection[];
10
+ stableSystemSectionCount?: number;
8
11
  }
9
12
  export interface ContextAssemblyOptions {
10
13
  cwd?: string;
14
+ lifecycleId?: string;
15
+ mode?: PromptCompositionMode;
16
+ baseSystemPrompt?: string;
17
+ additionalSections?: readonly PromptSection[];
18
+ }
19
+ export type ContextInvalidationReason = 'clear' | 'compact' | 'fork' | 'resource-reload' | 'restore' | 'tool-pool' | 'cwd' | 'worktree';
20
+ export interface ContextInvalidationOptions {
21
+ lifecycleId?: string;
22
+ reason: ContextInvalidationReason;
11
23
  }
12
24
  export interface ContextAssembler {
13
25
  assemble(options?: ContextAssemblyOptions): Promise<AssembledContext>;
26
+ invalidate?(options: ContextInvalidationOptions): void;
14
27
  }
15
28
  export declare function injectFirstUserMessageContext(messages: readonly ModelMessage[], context: string | undefined): ModelMessage[];
16
29
  //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,27 @@
1
+ import type { SystemContextMessage } from './context.js';
2
+ export type PromptSectionStability = 'static' | 'session' | 'volatile';
3
+ export type PromptSectionPlacement = 'system' | 'first-user';
4
+ export type PromptCompositionMode = 'default' | 'custom' | 'bare' | 'agent' | 'subagent';
5
+ export interface PromptSection {
6
+ id: string;
7
+ content: string;
8
+ stability: PromptSectionStability;
9
+ placement: PromptSectionPlacement;
10
+ }
11
+ export interface PromptComposition {
12
+ sections: readonly PromptSection[];
13
+ systemMessages: readonly SystemContextMessage[];
14
+ firstUserMessageContext?: string;
15
+ stableSystemSectionCount: number;
16
+ }
17
+ export interface PromptCompositionOptions {
18
+ mode: PromptCompositionMode;
19
+ baseSystemPrompt?: string;
20
+ appendSystemPrompt?: string;
21
+ sessionSections?: readonly PromptSection[];
22
+ tailSections?: readonly PromptSection[];
23
+ }
24
+ export declare class PromptComposer {
25
+ compose(options: PromptCompositionOptions): PromptComposition;
26
+ }
27
+ //# sourceMappingURL=prompt-composer.d.ts.map
@@ -0,0 +1,99 @@
1
+ const PRODUCT_POLICY = `# Praxis
2
+
3
+ You are Praxis, a local CLI coding agent. Work until the user's request is genuinely handled or a concrete blocker requires their input.
4
+
5
+ - Inspect relevant context before acting and use available tools deliberately.
6
+ - Preserve user data, existing changes, and project-specific instructions.
7
+ - Prefer focused, verifiable changes and report important outcomes clearly.
8
+ - Use a scratch or work area when the runtime provides one; keep temporary artifacts out of the user's project.
9
+ - Summarize or clear bulky intermediate results once their useful facts have been retained.
10
+ - Treat the context window as bounded: preserve decisions and evidence before reducing detail.
11
+ - Keep responses concise and use the user's language unless they request otherwise.`;
12
+ function baseSection(mode, content) {
13
+ if (mode === 'bare')
14
+ return undefined;
15
+ if (mode === 'default') {
16
+ return {
17
+ id: 'product-policy',
18
+ content: PRODUCT_POLICY,
19
+ placement: 'system',
20
+ stability: 'static',
21
+ };
22
+ }
23
+ if (content === undefined || content.trim().length === 0) {
24
+ throw new Error(`${mode} prompt mode requires a base system prompt`);
25
+ }
26
+ return {
27
+ id: mode === 'custom'
28
+ ? 'custom-system'
29
+ : mode === 'agent'
30
+ ? 'agent-policy'
31
+ : 'subagent-policy',
32
+ content,
33
+ placement: 'system',
34
+ stability: 'session',
35
+ };
36
+ }
37
+ function normalizedSections(sections, group) {
38
+ return sections
39
+ .filter((section) => section.content.trim().length > 0)
40
+ .map((section) => {
41
+ if (!section.id.trim())
42
+ throw new Error('Prompt section id is required');
43
+ if (group === 'sessionSections' && section.stability === 'volatile') {
44
+ throw new Error('sessionSections cannot contain volatile prompt sections');
45
+ }
46
+ if (group === 'tailSections' &&
47
+ section.placement === 'system' &&
48
+ section.stability !== 'volatile') {
49
+ throw new Error('tailSections system entries must be volatile prompt sections');
50
+ }
51
+ return { ...section };
52
+ });
53
+ }
54
+ export class PromptComposer {
55
+ compose(options) {
56
+ const base = baseSection(options.mode, options.baseSystemPrompt);
57
+ const sessionSections = normalizedSections(options.sessionSections ?? [], 'sessionSections');
58
+ const append = options.appendSystemPrompt?.trim()
59
+ ? {
60
+ id: 'append-system',
61
+ content: options.appendSystemPrompt,
62
+ placement: 'system',
63
+ stability: 'session',
64
+ }
65
+ : undefined;
66
+ const tailSections = normalizedSections(options.tailSections ?? [], 'tailSections');
67
+ const sections = [
68
+ ...(base ? [base] : []),
69
+ ...sessionSections,
70
+ ...(append ? [append] : []),
71
+ ...tailSections,
72
+ ];
73
+ const identities = new Set();
74
+ for (const section of sections) {
75
+ if (identities.has(section.id)) {
76
+ throw new Error(`Duplicate prompt section ${section.id}`);
77
+ }
78
+ identities.add(section.id);
79
+ }
80
+ const systemSections = sections.filter((section) => section.placement === 'system');
81
+ const firstVolatileSystemIndex = systemSections.findIndex((section) => section.stability === 'volatile');
82
+ const firstUserMessageContext = sections
83
+ .filter((section) => section.placement === 'first-user')
84
+ .map((section) => section.content)
85
+ .join('\n\n');
86
+ return {
87
+ sections,
88
+ systemMessages: systemSections.map((section) => ({
89
+ role: 'system',
90
+ content: section.content,
91
+ })),
92
+ stableSystemSectionCount: firstVolatileSystemIndex < 0
93
+ ? systemSections.length
94
+ : firstVolatileSystemIndex,
95
+ ...(firstUserMessageContext ? { firstUserMessageContext } : {}),
96
+ };
97
+ }
98
+ }
99
+ //# sourceMappingURL=prompt-composer.js.map
@@ -119,6 +119,8 @@ export type ModelStreamEvent = {
119
119
  };
120
120
  export interface ModelRequest {
121
121
  messages: readonly ModelMessage[];
122
+ /** Number of leading system messages that form the stable prompt prefix. */
123
+ stableSystemMessageCount?: number;
122
124
  tools?: readonly ModelToolDefinition[];
123
125
  webSearch?: ModelWebSearch;
124
126
  signal?: AbortSignal;
@@ -450,6 +452,8 @@ export interface AgentRuntimeOptions {
450
452
  }
451
453
  export interface AgentRunRequest {
452
454
  messages: readonly ModelMessage[];
455
+ /** Number of leading system messages that form the stable prompt prefix. */
456
+ stableSystemMessageCount?: number;
453
457
  cwd?: string;
454
458
  toolResultDirectory?: string;
455
459
  observer?: AgentRunObserver;
@@ -342,6 +342,11 @@ export class AgentRuntime {
342
342
  this.emit({ type: 'state', state: 'awaiting-model' });
343
343
  const providerRequest = {
344
344
  messages: prepareProviderMessages(messages, this.provider.capabilities.images === true, this.provider.capabilities.documents === true),
345
+ ...(request.stableSystemMessageCount === undefined
346
+ ? {}
347
+ : {
348
+ stableSystemMessageCount: request.stableSystemMessageCount,
349
+ }),
345
350
  };
346
351
  if (definitions.length > 0)
347
352
  providerRequest.tools = definitions;
@@ -35,6 +35,7 @@ export interface ClaudeMcpRuntime {
35
35
  authenticate(name: string): Promise<void>;
36
36
  reload(): Promise<void>;
37
37
  tools(name: string): Promise<readonly ClaudeMcpToolInspection[]>;
38
+ instructions?(): readonly ClaudeMcpServerInstruction[];
38
39
  connectAgent?(options: {
39
40
  specs: readonly unknown[];
40
41
  base: ToolRegistry;
@@ -47,6 +48,10 @@ export interface ClaudeMcpRuntime {
47
48
  /** Release MCP transports and child processes owned by this runtime. */
48
49
  close?(): Promise<void>;
49
50
  }
51
+ export interface ClaudeMcpServerInstruction {
52
+ server: string;
53
+ instructions: string;
54
+ }
50
55
  export interface ClaudeMcpConfigurationStatus {
51
56
  name: string;
52
57
  path: string;
@@ -67,6 +72,7 @@ export interface ClaudeMcpToolRegistryOptions {
67
72
  signal?: AbortSignal;
68
73
  eventSink?: RuntimeEventSink;
69
74
  onPromptsChanged?: (prompts: readonly ClaudeMcpPromptDefinition[]) => void;
75
+ onInstructionsChanged?: (instructions: readonly ClaudeMcpServerInstruction[]) => void;
70
76
  authenticateServer?: (name: string) => Promise<void>;
71
77
  reloadResources?: () => Promise<readonly ClaudeJsonResource[]>;
72
78
  onElicitation?: (request: {
@@ -93,6 +99,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
93
99
  private readonly serverClients;
94
100
  private readonly reconnectableServers;
95
101
  private readonly serverCapabilities;
102
+ private readonly serverInstructions;
96
103
  private readonly reconnectingServers;
97
104
  private readonly promptOperations;
98
105
  private promptResultDirectoryPromise;
@@ -104,6 +111,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
104
111
  definitions(): readonly ModelToolDefinition[];
105
112
  schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
106
113
  serverStatuses(): readonly ClaudeMcpServerStatus[];
114
+ instructions(): readonly ClaudeMcpServerInstruction[];
107
115
  inspect(): Promise<readonly ClaudeMcpServerStatus[]>;
108
116
  reconnect(name: string): Promise<void>;
109
117
  authenticate(name: string): Promise<void>;
@@ -137,6 +145,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
137
145
  private ensurePromptConnected;
138
146
  private reconnectServer;
139
147
  private publishPrompts;
148
+ private publishInstructions;
140
149
  private assertOpenGeneration;
141
150
  private trackPromptOperation;
142
151
  private promptResultDirectory;
@@ -680,6 +680,7 @@ export class ClaudeMcpToolRegistry {
680
680
  serverClients = new Map();
681
681
  reconnectableServers = new Map();
682
682
  serverCapabilities = new Map();
683
+ serverInstructions = new Map();
683
684
  reconnectingServers = new Map();
684
685
  promptOperations = new Set();
685
686
  promptResultDirectoryPromise;
@@ -732,6 +733,11 @@ export class ClaudeMcpToolRegistry {
732
733
  status,
733
734
  }));
734
735
  }
736
+ instructions() {
737
+ return [...this.serverInstructions]
738
+ .map(([server, instructions]) => ({ server, instructions }))
739
+ .sort((left, right) => left.server.localeCompare(right.server));
740
+ }
735
741
  async inspect() {
736
742
  return this.runtimeStatuses();
737
743
  }
@@ -763,6 +769,7 @@ export class ClaudeMcpToolRegistry {
763
769
  this.statuses.clear();
764
770
  this.reconnectableServers.clear();
765
771
  this.serverCapabilities.clear();
772
+ this.serverInstructions.clear();
766
773
  const ambientSensitiveValues = sensitiveEnvironmentValues(process.env);
767
774
  const warn = (message) => this.options.onWarning?.(redactSensitiveText(message, ambientSensitiveValues));
768
775
  await this.configure(this.options.reloadResources
@@ -962,6 +969,7 @@ export class ClaudeMcpToolRegistry {
962
969
  await Promise.allSettled([...this.clients].map((client) => client.close()));
963
970
  this.clients.clear();
964
971
  this.serverClients.clear();
972
+ this.serverInstructions.clear();
965
973
  const directory = await this.promptResultDirectoryPromise?.catch(() => undefined);
966
974
  if (directory)
967
975
  await rm(directory, { recursive: true, force: true });
@@ -1052,7 +1060,9 @@ export class ClaudeMcpToolRegistry {
1052
1060
  if (this.closed || this.serverClients.get(serverName) !== client)
1053
1061
  return;
1054
1062
  this.serverCapabilities.delete(serverName);
1063
+ this.serverInstructions.delete(serverName);
1055
1064
  this.statuses.set(serverName, { name: serverName, status: 'failed' });
1065
+ this.publishInstructions();
1056
1066
  };
1057
1067
  client.setRequestHandler(ElicitRequestSchema, async (request) => {
1058
1068
  if (!this.options.onElicitation)
@@ -1131,6 +1141,13 @@ export class ClaudeMcpToolRegistry {
1131
1141
  ...(capabilities?.resources ? ['resources'] : []),
1132
1142
  ...(capabilities?.prompts ? ['prompts'] : []),
1133
1143
  ]);
1144
+ const serverInstruction = client.getInstructions()?.trim();
1145
+ if (serverInstruction) {
1146
+ this.serverInstructions.set(serverName, redactSensitiveText(serverInstruction, sensitiveValues));
1147
+ }
1148
+ else {
1149
+ this.serverInstructions.delete(serverName);
1150
+ }
1134
1151
  for (const [name, tool] of connectedTools)
1135
1152
  this.connectedTools.set(name, tool);
1136
1153
  if (capabilities?.resources) {
@@ -1157,6 +1174,7 @@ export class ClaudeMcpToolRegistry {
1157
1174
  status: 'connected',
1158
1175
  statusDetail: 'connected',
1159
1176
  });
1177
+ this.publishInstructions();
1160
1178
  }
1161
1179
  catch (error) {
1162
1180
  await client.close().catch(() => undefined);
@@ -1383,6 +1401,7 @@ export class ClaudeMcpToolRegistry {
1383
1401
  this.resourceServers.delete(serverName);
1384
1402
  this.promptServers.delete(serverName);
1385
1403
  this.serverCapabilities.delete(serverName);
1404
+ this.serverInstructions.delete(serverName);
1386
1405
  await this.connectServer(serverName, reconnectable.config, reconnectable.sensitiveValues, expectedGeneration);
1387
1406
  if (this.statuses.get(serverName)?.status !== 'connected') {
1388
1407
  throw new Error(`MCP server ${serverName} could not reconnect`);
@@ -1393,6 +1412,10 @@ export class ClaudeMcpToolRegistry {
1393
1412
  if (!this.closed)
1394
1413
  this.options.onPromptsChanged?.(this.prompts());
1395
1414
  }
1415
+ publishInstructions() {
1416
+ if (!this.closed)
1417
+ this.options.onInstructionsChanged?.(this.instructions());
1418
+ }
1396
1419
  assertOpenGeneration(expectedGeneration) {
1397
1420
  if (this.closed || expectedGeneration !== this.generation) {
1398
1421
  throw new Error('MCP registry is closed');
@@ -555,6 +555,20 @@ function serializeMessages(messages) {
555
555
  }
556
556
  return { system: system.join('\n\n'), messages: serialized };
557
557
  }
558
+ function validateStableSystemPrefix(request) {
559
+ const count = request.stableSystemMessageCount;
560
+ if (count === undefined)
561
+ return;
562
+ const systemMessageCount = request.messages.filter((message) => message.role === 'system').length;
563
+ if (!Number.isSafeInteger(count) || count < 0 || count > systemMessageCount) {
564
+ throw new Error('Stable system message count must identify a valid system-message prefix');
565
+ }
566
+ if (request.messages
567
+ .slice(0, count)
568
+ .some((message) => message.role !== 'system')) {
569
+ throw new Error('Stable system message count must identify a valid system-message prefix');
570
+ }
571
+ }
558
572
  export class AnthropicCompatibleProvider {
559
573
  options;
560
574
  capabilities;
@@ -609,6 +623,7 @@ export class AnthropicCompatibleProvider {
609
623
  this.maxErrorBodyBytes = options.maxErrorBodyBytes ?? 64 * 1024;
610
624
  }
611
625
  async *complete(request) {
626
+ validateStableSystemPrefix(request);
612
627
  if (request.webSearch && !this.capabilities.webSearch) {
613
628
  throw new Error('Provider does not support web search');
614
629
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",