praxis-agent 0.54.0 → 0.55.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.
package/README.md CHANGED
@@ -253,11 +253,16 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
253
253
  recovery for malformed streamed tool arguments without tool execution or
254
254
  lost resumability, one default-on bounded Anthropic non-streaming replay for
255
255
  eligible stream/idle failures without exposing failed-attempt output, and
256
- token-only/no-API-dollar accounting for subscription runs. For each main user
257
- turn, failed provider attempts stay buffered; the first successful route,
258
- whether primary or fallback, is sticky and sealed through that turn's tool
259
- continuations; incompatible fallback routes fail closed, and each new main
260
- user turn starts from primary.
256
+ token-only/no-API-dollar accounting for subscription runs. Each main user
257
+ Turn and independent auxiliary Agent, Workflow, Team, recovery, or memory
258
+ Turn receives its own provider client. Session-memory requests reuse a
259
+ completion-scoped client but restart routing from primary for each request;
260
+ auto-mode critic and eval-judge requests remain independently constructed
261
+ one-shot clients. Failed attempts stay buffered, and the first successful
262
+ route, whether primary or fallback, stays sticky only through that logical
263
+ Turn's tool continuations; incompatible routes fail closed, and the next
264
+ independent Turn starts from primary. Recovery may persist only an optional
265
+ selected model, never provider route or wire state.
261
266
  - **Transactional self-update** — `praxis update` verifies the package before
262
267
  installing it, rejects concurrent updates, and can roll back after an
263
268
  interruption or crash.
@@ -7,6 +7,8 @@ export interface NativeSidechainMetadata {
7
7
  readonly spawnDepth: number;
8
8
  readonly cwd: string;
9
9
  readonly promptId: string;
10
+ /** Provider-neutral selected model identifier for recovery. */
11
+ readonly model?: string;
10
12
  readonly name?: string;
11
13
  readonly permissionMode?: NativeSidechainPermissionMode;
12
14
  readonly isolation?: 'worktree';
@@ -19,6 +19,7 @@ const requiredKeys = [
19
19
  'promptId',
20
20
  ];
21
21
  const optionalKeys = [
22
+ 'model',
22
23
  'name',
23
24
  'permissionMode',
24
25
  'isolation',
@@ -74,6 +75,8 @@ function validateMetadata(value) {
74
75
  throw new Error('native sidechain metadata cwd is invalid');
75
76
  if (!nonBlank(record.promptId))
76
77
  throw new Error('native sidechain metadata promptId is invalid');
78
+ if (record.model !== undefined && !nonBlank(record.model))
79
+ throw new Error('native sidechain metadata model is invalid');
77
80
  if (record.name !== undefined && !nonBlank(record.name))
78
81
  throw new Error('native sidechain metadata name is invalid');
79
82
  if (record.permissionMode !== undefined &&
@@ -1117,6 +1117,9 @@ export class ClaudeSessionService {
1117
1117
  ...(this.options.providerForModel
1118
1118
  ? { providerForModel: this.options.providerForModel }
1119
1119
  : {}),
1120
+ ...(this.options.providerForTurn
1121
+ ? { providerForTurn: this.options.providerForTurn }
1122
+ : {}),
1120
1123
  baseTools: wrappedBase,
1121
1124
  ...(this.options.deferMcpTools === undefined
1122
1125
  ? {}
@@ -2858,6 +2861,9 @@ export class ClaudeSessionService {
2858
2861
  ...(this.options.providerForModel
2859
2862
  ? { providerForModel: this.options.providerForModel }
2860
2863
  : {}),
2864
+ ...(this.options.providerForTurn
2865
+ ? { providerForTurn: this.options.providerForTurn }
2866
+ : {}),
2861
2867
  baseTools,
2862
2868
  ...(this.options.deferMcpTools === undefined
2863
2869
  ? {}
@@ -76,6 +76,7 @@ export interface ClaudeSubagentExecutorOptions {
76
76
  maxCalls?: number;
77
77
  maxOutputBytes?: number;
78
78
  providerForModel?: (model: string) => ModelProvider;
79
+ providerForTurn?: (model?: string) => ModelProvider;
79
80
  toolNames?: readonly string[];
80
81
  backgroundTaskNotifications?: (waitForRunning: boolean) => Promise<string[]>;
81
82
  notificationDelivered?: (notification: {
@@ -117,6 +118,7 @@ export declare class ClaudeSubagentExecutor {
117
118
  sendBackgroundMessage(agentId: string, message: string, summary: string | undefined, toolUseId: string): string;
118
119
  stopAllBackgroundTasks(): readonly string[];
119
120
  private cwd;
121
+ private providerForTurn;
120
122
  private agentDefinition;
121
123
  private resolveAgentInput;
122
124
  registry(sessionId: string, depth: number, promptIdForCall: (callId: string) => string | null, parentAgentId?: string): ToolRegistry;
@@ -586,6 +586,15 @@ export class ClaudeSubagentExecutor {
586
586
  cwd() {
587
587
  return this.options.cwdProvider?.() ?? this.options.cwd;
588
588
  }
589
+ providerForTurn(model) {
590
+ if (this.options.providerForTurn) {
591
+ return this.options.providerForTurn(model);
592
+ }
593
+ if (model !== undefined) {
594
+ return this.options.providerForModel?.(model) ?? this.options.provider;
595
+ }
596
+ return this.options.provider;
597
+ }
589
598
  agentDefinition(input) {
590
599
  return this.options.extensions?.agent(input.subagentType) ?? null;
591
600
  }
@@ -753,7 +762,9 @@ export class ClaudeSubagentExecutor {
753
762
  !this.options.extensions?.agent(input.subagentType)) {
754
763
  throw new Error(`Unknown Claude agent ${input.subagentType}`);
755
764
  }
756
- if (input.model && !this.options.providerForModel) {
765
+ if (input.model &&
766
+ !this.options.providerForModel &&
767
+ !this.options.providerForTurn) {
757
768
  throw new Error('Agent model overrides are unavailable for this provider');
758
769
  }
759
770
  if (input.permissionMode &&
@@ -850,6 +861,7 @@ export class ClaudeSubagentExecutor {
850
861
  ...(input.permissionMode
851
862
  ? { permissionMode: input.permissionMode }
852
863
  : {}),
864
+ ...(input.model ? { model: input.model } : {}),
853
865
  ...(input.isolation ? { isolation: input.isolation } : {}),
854
866
  ...(parentAgentId ? { parentAgentId } : {}),
855
867
  ...(initialIsolation ? { worktreePath: initialIsolation.cwd } : {}),
@@ -863,9 +875,7 @@ export class ClaudeSubagentExecutor {
863
875
  catch (error) {
864
876
  return settleInitialSetupFailure(error, initialIsolation);
865
877
  }
866
- const provider = input.model
867
- ? (this.options.providerForModel?.(input.model) ?? this.options.provider)
868
- : this.options.provider;
878
+ const provider = this.providerForTurn(input.model);
869
879
  const backgroundRun = this.createBackgroundAgentRun({
870
880
  input,
871
881
  parentCwd,
@@ -874,11 +884,14 @@ export class ClaudeSubagentExecutor {
874
884
  ...(initialIsolation ? { initialIsolation } : {}),
875
885
  createIsolation: () => this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd),
876
886
  execute: async (cwd, message, signal, continuation) => {
887
+ const turnProvider = continuation
888
+ ? this.providerForTurn(input.model)
889
+ : provider;
877
890
  const run = (lease) => this.runSidechain({
878
891
  ...lease,
879
892
  sessionId,
880
893
  input,
881
- provider,
894
+ provider: turnProvider,
882
895
  agentId,
883
896
  spawnDepth,
884
897
  promptId,
@@ -1346,13 +1359,12 @@ export class ClaudeSubagentExecutor {
1346
1359
  !this.options.extensions?.agent(options.agentType)) {
1347
1360
  throw new Error(`Unknown Claude agent ${options.agentType}`);
1348
1361
  }
1349
- if (options.model && !this.options.providerForModel) {
1362
+ if (options.model &&
1363
+ !this.options.providerForModel &&
1364
+ !this.options.providerForTurn) {
1350
1365
  throw new Error('Workflow agent model overrides are unavailable for this provider');
1351
1366
  }
1352
- const provider = options.model
1353
- ? (this.options.providerForModel?.(options.model) ??
1354
- this.options.provider)
1355
- : this.options.provider;
1367
+ const provider = this.providerForTurn(options.model);
1356
1368
  const input = {
1357
1369
  description: options.label ?? 'Workflow agent',
1358
1370
  prompt: options.prompt,
@@ -1395,6 +1407,7 @@ export class ClaudeSubagentExecutor {
1395
1407
  spawnDepth: 1,
1396
1408
  cwd: agentCwd,
1397
1409
  promptId: options.promptId,
1410
+ ...(input.model ? { model: input.model } : {}),
1398
1411
  ...(options.isolation ? { isolation: options.isolation } : {}),
1399
1412
  ...(isolation ? { worktreePath: isolation.cwd } : {}),
1400
1413
  });
@@ -1716,11 +1729,11 @@ export class ClaudeSubagentExecutor {
1716
1729
  prompt,
1717
1730
  subagentType: agentType,
1718
1731
  ...(name ? { name } : {}),
1732
+ ...(metadata?.model ? { model: metadata.model } : {}),
1719
1733
  ...(permissionMode ? { permissionMode } : {}),
1720
1734
  ...(isolation ? { isolation } : {}),
1721
1735
  runInBackground: true,
1722
1736
  };
1723
- const provider = this.options.provider;
1724
1737
  const recoveredPromptId = metadata?.promptId ?? randomUUID();
1725
1738
  const backgroundRun = this.createBackgroundAgentRun({
1726
1739
  input,
@@ -1731,6 +1744,9 @@ export class ClaudeSubagentExecutor {
1731
1744
  ...(restoredIsolation ? { initialIsolation: restoredIsolation } : {}),
1732
1745
  createIsolation: () => this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd),
1733
1746
  execute: async (cwd, message, signal, continuation) => {
1747
+ // Recovery never restores provider-native route state. Each recovered
1748
+ // execution, including every later follow-up, gets a fresh turn.
1749
+ const provider = this.providerForTurn(input.model);
1734
1750
  const run = (lease) => this.runSidechain({
1735
1751
  ...lease,
1736
1752
  sessionId,
@@ -1757,7 +1773,7 @@ export class ClaudeSubagentExecutor {
1757
1773
  prompt,
1758
1774
  toolUseId,
1759
1775
  outputFile: sidechainPaths.transcriptFile,
1760
- resolvedModel: provider.model ?? 'praxis/provider',
1776
+ resolvedModel: metadata?.model ?? this.options.provider.model ?? 'praxis/provider',
1761
1777
  lifecycle: backgroundRun.lifecycle,
1762
1778
  run: backgroundRun.run,
1763
1779
  markBackground: backgroundRun.markBackground,
@@ -14,6 +14,7 @@ export interface ClaudeTeamAgentRuntimeOptions {
14
14
  readonly hooks?: ClaudeHookRunner;
15
15
  readonly contextAssembler?: ContextAssembler;
16
16
  readonly providerForModel?: (model: string) => ModelProvider;
17
+ readonly providerForTurn?: (model?: string) => ModelProvider;
17
18
  readonly permissionResolverForMode?: (mode: AgentPermissionMode) => PermissionResolver;
18
19
  readonly eventSink?: RuntimeEventSink;
19
20
  readonly approveTool?: (call: ModelToolCall, originalCall?: ModelToolCall, decision?: PermissionDecision) => PermissionApproval | Promise<PermissionApproval>;
@@ -79,6 +79,9 @@ export class ClaudeTeamAgentRuntime {
79
79
  ...(this.options.providerForModel
80
80
  ? { providerForModel: this.options.providerForModel }
81
81
  : {}),
82
+ ...(this.options.providerForTurn
83
+ ? { providerForTurn: this.options.providerForTurn }
84
+ : {}),
82
85
  ...(this.options.permissionResolverForMode
83
86
  ? {
84
87
  permissionResolverForMode: this.options.permissionResolverForMode,
@@ -1477,9 +1477,11 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1477
1477
  });
1478
1478
  if (memoryDirectory)
1479
1479
  await mkdir(memoryDirectory, { recursive: true });
1480
- const projectMemoryProviderFactory = providerForMainModel && model
1481
- ? () => providerForMainModel(model)
1482
- : undefined;
1480
+ const projectMemoryProviderFactory = providerForTurn
1481
+ ? () => providerForTurn()
1482
+ : providerForMainModel && model
1483
+ ? () => providerForMainModel(model)
1484
+ : undefined;
1483
1485
  const projectMemoryRecall = projectMemoryPolicy.recall &&
1484
1486
  memoryDirectory &&
1485
1487
  projectMemoryProviderFactory
@@ -1911,6 +1913,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1911
1913
  ...(hooks ? { hooks } : {}),
1912
1914
  ...(contextAssembler ? { contextAssembler } : {}),
1913
1915
  ...(providerForModel ? { providerForModel } : {}),
1916
+ ...(providerForTurn ? { providerForTurn } : {}),
1914
1917
  permissionResolverForMode,
1915
1918
  eventSink: runtimeEventSink,
1916
1919
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.54.0",
3
+ "version": "0.55.0",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",