@agentguard-run/spend 0.4.0 → 0.4.2

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 (68) hide show
  1. package/CHANGELOG.md +8 -2
  2. package/README.es-419.md +21 -4
  3. package/README.md +24 -5
  4. package/README.pt-BR.md +21 -4
  5. package/dist/{coach → advisor}/anomaly.d.ts +5 -5
  6. package/dist/advisor/anomaly.d.ts.map +1 -0
  7. package/dist/{coach → advisor}/anomaly.js +1 -1
  8. package/dist/advisor/anomaly.js.map +1 -0
  9. package/dist/{coach → advisor}/conversation.d.ts +23 -17
  10. package/dist/advisor/conversation.d.ts.map +1 -0
  11. package/dist/{coach → advisor}/conversation.js +63 -27
  12. package/dist/advisor/conversation.js.map +1 -0
  13. package/dist/{coach → advisor}/forecast.d.ts +4 -4
  14. package/dist/advisor/forecast.d.ts.map +1 -0
  15. package/dist/{coach → advisor}/forecast.js +1 -1
  16. package/dist/advisor/forecast.js.map +1 -0
  17. package/dist/{coach → advisor}/llm-client.d.ts +10 -10
  18. package/dist/advisor/llm-client.d.ts.map +1 -0
  19. package/dist/{coach → advisor}/llm-client.js +12 -12
  20. package/dist/advisor/llm-client.js.map +1 -0
  21. package/dist/{coach → advisor}/output.d.ts +12 -12
  22. package/dist/advisor/output.d.ts.map +1 -0
  23. package/dist/{coach → advisor}/output.js +42 -13
  24. package/dist/advisor/output.js.map +1 -0
  25. package/dist/advisor/posture.d.ts +26 -0
  26. package/dist/advisor/posture.d.ts.map +1 -0
  27. package/dist/advisor/posture.js +100 -0
  28. package/dist/advisor/posture.js.map +1 -0
  29. package/dist/{coach → advisor}/system-prompt.d.ts +5 -5
  30. package/dist/advisor/system-prompt.d.ts.map +1 -0
  31. package/dist/{coach → advisor}/system-prompt.js +21 -8
  32. package/dist/advisor/system-prompt.js.map +1 -0
  33. package/dist/cli/advisor.d.ts +5 -0
  34. package/dist/cli/advisor.d.ts.map +1 -0
  35. package/dist/cli/{coach.js → advisor.js} +63 -47
  36. package/dist/cli/advisor.js.map +1 -0
  37. package/dist/cli/main.d.ts.map +1 -1
  38. package/dist/cli/main.js +7 -6
  39. package/dist/cli/main.js.map +1 -1
  40. package/dist/index.d.ts +4 -4
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +11 -11
  43. package/dist/index.js.map +1 -1
  44. package/dist/telemetry.js +1 -1
  45. package/package.json +7 -7
  46. package/src/{coach → advisor}/anomaly.ts +9 -9
  47. package/src/{coach → advisor}/conversation.ts +80 -39
  48. package/src/{coach → advisor}/forecast.ts +5 -5
  49. package/src/{coach → advisor}/llm-client.ts +21 -21
  50. package/src/{coach → advisor}/output.ts +49 -20
  51. package/src/advisor/posture.ts +112 -0
  52. package/src/{coach → advisor}/system-prompt.ts +22 -8
  53. package/src/cli/{coach.ts → advisor.ts} +74 -58
  54. package/dist/cli/coach.d.ts +0 -5
  55. package/dist/cli/coach.d.ts.map +0 -1
  56. package/dist/cli/coach.js.map +0 -1
  57. package/dist/coach/anomaly.d.ts.map +0 -1
  58. package/dist/coach/anomaly.js.map +0 -1
  59. package/dist/coach/conversation.d.ts.map +0 -1
  60. package/dist/coach/conversation.js.map +0 -1
  61. package/dist/coach/forecast.d.ts.map +0 -1
  62. package/dist/coach/forecast.js.map +0 -1
  63. package/dist/coach/llm-client.d.ts.map +0 -1
  64. package/dist/coach/llm-client.js.map +0 -1
  65. package/dist/coach/output.d.ts.map +0 -1
  66. package/dist/coach/output.js.map +0 -1
  67. package/dist/coach/system-prompt.d.ts.map +0 -1
  68. package/dist/coach/system-prompt.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * AgentGuard(TM) Spend: Coach conversation state.
2
+ * AgentGuard(TM) Spend: Advisor conversation state.
3
3
  *
4
4
  * Patent notice: Protected by U.S. patent-pending technology
5
5
  * (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
@@ -9,24 +9,26 @@
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import type { CapabilityTier, SpendCap, SpendPolicy } from '../types';
12
+ import { applyPostureCapability, normalizePosture, postureProfile, suggestPostureForVertical, type GovernancePosture } from './posture';
12
13
 
13
- export type CoachQuestionId = 'building' | 'scale' | 'tasks' | 'budget' | 'confirm';
14
+ export type AdvisorQuestionId = 'building' | 'posture' | 'scale' | 'tasks' | 'budget' | 'confirm';
14
15
 
15
- export interface CoachQuestion {
16
- id: CoachQuestionId;
16
+ export interface AdvisorQuestion {
17
+ id: AdvisorQuestionId;
17
18
  prompt: string;
18
19
  }
19
20
 
20
- export interface CoachAnswers {
21
+ export interface AdvisorAnswers {
21
22
  building?: string;
22
23
  scale?: string;
23
24
  tasks?: string;
24
25
  budget?: string;
25
26
  confirm?: string;
26
27
  language?: 'ts' | 'py';
28
+ posture?: GovernancePosture;
27
29
  }
28
30
 
29
- export interface CoachBusinessProfile {
31
+ export interface AdvisorBusinessProfile {
30
32
  vertical: string;
31
33
  tenantId: string;
32
34
  teamSize: number;
@@ -41,6 +43,7 @@ export interface CoachBusinessProfile {
41
43
  perMonthCapCents: number;
42
44
  scopeLabel: string;
43
45
  language: 'ts' | 'py';
46
+ posture: GovernancePosture;
44
47
  }
45
48
 
46
49
  export interface ProjectedSavingsRow {
@@ -58,8 +61,9 @@ export interface ProjectedSavings {
58
61
  savingsPercent: number;
59
62
  }
60
63
 
61
- export const COACH_QUESTIONS: CoachQuestion[] = [
64
+ export const ADVISOR_QUESTIONS: AdvisorQuestion[] = [
62
65
  { id: 'building', prompt: 'What are you building or running? Include your business type and the agent workflow.' },
66
+ { id: 'posture', prompt: 'Confirm or change governance posture: Velocity / Standard / Compliance.' },
63
67
  { id: 'scale', prompt: 'How big is the team and roughly how many AI calls, tickets, orders, encounters, matters, or jobs happen per month?' },
64
68
  { id: 'tasks', prompt: 'What are the top 3 AI tasks this agent will perform?' },
65
69
  { id: 'budget', prompt: 'What monthly AI budget or per-task ceiling should this stay under?' },
@@ -77,51 +81,77 @@ const VERTICAL_HINTS: Array<{ vertical: string; needles: string[]; scopeLabel: s
77
81
  { vertical: 'local-services', needles: ['salon', 'gym', 'restaurant', 'landscaping', 'construction', 'photography', 'fitness', 'pet'], scopeLabel: 'job', capability: 'data_write', primary: 'openai/gpt-5-mini', fallback: 'openai/gpt-4o-mini', perCall: 25 },
78
82
  ];
79
83
 
80
- export class CoachConversation {
81
- private answers: CoachAnswers = {};
84
+ export class AdvisorConversation {
85
+ private answers: AdvisorAnswers = {};
82
86
  private index = 0;
83
87
 
84
- constructor(initial?: CoachAnswers) {
88
+ constructor(initial?: AdvisorAnswers) {
85
89
  if (initial) this.answers = { ...initial };
86
90
  this.index = firstMissingIndex(this.answers);
87
91
  }
88
92
 
89
- currentQuestion(): CoachQuestion | null {
90
- return COACH_QUESTIONS[this.index] ?? null;
93
+ currentQuestion(): AdvisorQuestion | null {
94
+ const question = ADVISOR_QUESTIONS[this.index] ?? null;
95
+ if (question?.id !== 'posture') return question;
96
+ const vertical = detectVertical(this.answers.building ?? '');
97
+ const suggested = suggestPostureForVertical(vertical);
98
+ return {
99
+ id: 'posture',
100
+ prompt: `Based on ${vertical}, we suggest ${suggested} governance posture. Confirm or change: Velocity / Standard / Compliance.`,
101
+ };
91
102
  }
92
103
 
93
104
  answer(value: string): void {
94
105
  const question = this.currentQuestion();
95
106
  if (!question) return;
107
+ if (question.id === 'posture') {
108
+ this.answers.posture = normalizePosture(value) ?? suggestedPostureForText(this.answers.building ?? '');
109
+ return;
110
+ }
96
111
  this.answers[question.id] = value.trim();
97
112
  }
98
113
 
99
- next(): CoachQuestion | null {
100
- this.index = Math.min(this.index + 1, COACH_QUESTIONS.length);
114
+ next(): AdvisorQuestion | null {
115
+ this.index = Math.min(this.index + 1, ADVISOR_QUESTIONS.length);
101
116
  return this.currentQuestion();
102
117
  }
103
118
 
104
- back(): CoachQuestion | null {
119
+ back(): AdvisorQuestion | null {
105
120
  this.index = Math.max(0, this.index - 1);
106
121
  return this.currentQuestion();
107
122
  }
108
123
 
109
124
  isComplete(): boolean {
110
- return COACH_QUESTIONS.every((question) => Boolean(this.answers[question.id]?.trim()));
125
+ return ADVISOR_QUESTIONS.every((question) => Boolean(this.answers[question.id]?.trim()));
111
126
  }
112
127
 
113
- snapshot(): CoachAnswers {
128
+ snapshot(): AdvisorAnswers {
114
129
  return { ...this.answers };
115
130
  }
116
131
 
117
- profile(cwd = process.cwd()): CoachBusinessProfile {
132
+ setPosture(posture: GovernancePosture): void {
133
+ this.answers.posture = posture;
134
+ }
135
+
136
+ profile(cwd = process.cwd()): AdvisorBusinessProfile {
118
137
  return buildBusinessProfile(this.answers, cwd);
119
138
  }
120
139
  }
121
140
 
122
- export function buildBusinessProfile(answers: CoachAnswers, cwd = process.cwd()): CoachBusinessProfile {
141
+ export function detectVertical(text: string): string {
142
+ const joined = text.toLowerCase();
143
+ return (VERTICAL_HINTS.find((candidate) => candidate.needles.some((needle) => joined.includes(needle))) ?? VERTICAL_HINTS[0]!).vertical;
144
+ }
145
+
146
+ export function suggestedPostureForText(text: string): GovernancePosture {
147
+ return suggestPostureForVertical(detectVertical(text));
148
+ }
149
+
150
+
151
+ export function buildBusinessProfile(answers: AdvisorAnswers, cwd = process.cwd()): AdvisorBusinessProfile {
123
152
  const joined = `${answers.building ?? ''} ${answers.scale ?? ''} ${answers.tasks ?? ''}`.toLowerCase();
124
153
  const hint = VERTICAL_HINTS.find((candidate) => candidate.needles.some((needle) => joined.includes(needle))) ?? VERTICAL_HINTS[0]!;
154
+ const posture = answers.posture ?? suggestPostureForVertical(hint.vertical);
125
155
  const scale = parseScale(answers.scale ?? '');
126
156
  const budgetCents = parseBudgetCents(answers.budget ?? '', Math.max(4900, Math.ceil(scale.monthlyVolume * hint.perCall * 0.35)));
127
157
  const tasks = parseTasks(answers.tasks ?? answers.building ?? hint.vertical);
@@ -134,7 +164,7 @@ export function buildBusinessProfile(answers: CoachAnswers, cwd = process.cwd())
134
164
  monthlyVolume: scale.monthlyVolume,
135
165
  tasks,
136
166
  monthlyBudgetCents: perMonthCapCents,
137
- requiredCapability: capabilityFor(joined, hint.capability),
167
+ requiredCapability: capabilityFor(joined, hint.capability, posture),
138
168
  primaryModel: hint.primary,
139
169
  fallbackModel: hint.fallback,
140
170
  perCallCapCents: hint.perCall,
@@ -142,18 +172,31 @@ export function buildBusinessProfile(answers: CoachAnswers, cwd = process.cwd())
142
172
  perMonthCapCents,
143
173
  scopeLabel: hint.scopeLabel,
144
174
  language: answers.language ?? detectProjectLanguage(cwd),
175
+ posture,
145
176
  };
146
177
  }
147
178
 
148
- export function buildPolicyFromProfile(profile: CoachBusinessProfile): SpendPolicy {
179
+ export function buildPolicyFromProfile(profile: AdvisorBusinessProfile): SpendPolicy {
180
+ const posture = postureProfile(profile.posture);
181
+ const conservativeBlock = profile.posture === 'compliance' && profile.requiredCapability !== 'read_only';
182
+ const perCallAmount = profile.posture === 'velocity'
183
+ ? Math.max(1, Math.ceil(profile.perCallCapCents * 0.6))
184
+ : profile.perCallCapCents;
149
185
  const caps: SpendCap[] = [
150
- {
151
- amountCents: profile.perCallCapCents,
152
- window: 'per_call',
153
- action: 'downgrade',
154
- downgradeTo: profile.fallbackModel,
155
- reason: 'Per-call budget reached, route to fallback model',
156
- },
186
+ conservativeBlock
187
+ ? {
188
+ amountCents: perCallAmount,
189
+ window: 'per_call',
190
+ action: 'block',
191
+ reason: 'Per-call regulated capability budget reached',
192
+ }
193
+ : {
194
+ amountCents: perCallAmount,
195
+ window: 'per_call',
196
+ action: 'downgrade',
197
+ downgradeTo: profile.fallbackModel,
198
+ reason: 'Per-call budget reached, route to fallback model',
199
+ },
157
200
  {
158
201
  amountCents: profile.perDayCapCents,
159
202
  window: 'per_day',
@@ -168,18 +211,18 @@ export function buildPolicyFromProfile(profile: CoachBusinessProfile): SpendPoli
168
211
  },
169
212
  ];
170
213
  return {
171
- id: `coach-${profile.vertical}-v1`,
172
- name: `Coach generated ${profile.vertical} policy`,
214
+ id: `advisor-${profile.vertical}-v1`,
215
+ name: `Advisor generated ${profile.vertical} policy`,
173
216
  scope: { tenantId: profile.tenantId },
174
217
  caps,
175
- mode: 'enforce',
218
+ mode: posture.defaultMode,
176
219
  requiredCapability: profile.requiredCapability,
177
220
  version: 1,
178
221
  effectiveFrom: new Date().toISOString(),
179
222
  };
180
223
  }
181
224
 
182
- export function projectedSavings(profile: CoachBusinessProfile): ProjectedSavings {
225
+ export function projectedSavings(profile: AdvisorBusinessProfile): ProjectedSavings {
183
226
  const heavyPerCall = Math.max(profile.perCallCapCents * 4, 100);
184
227
  const routedPerCall = Math.max(1, Math.ceil(profile.perCallCapCents * 0.45));
185
228
  const monthlyBeforeCents = profile.monthlyVolume * heavyPerCall;
@@ -211,9 +254,9 @@ export function detectProjectLanguage(cwd: string): 'ts' | 'py' {
211
254
  return 'ts';
212
255
  }
213
256
 
214
- function firstMissingIndex(answers: CoachAnswers): number {
215
- const index = COACH_QUESTIONS.findIndex((question) => !answers[question.id]?.trim());
216
- return index === -1 ? COACH_QUESTIONS.length : index;
257
+ function firstMissingIndex(answers: AdvisorAnswers): number {
258
+ const index = ADVISOR_QUESTIONS.findIndex((question) => !answers[question.id]?.trim());
259
+ return index === -1 ? ADVISOR_QUESTIONS.length : index;
217
260
  }
218
261
 
219
262
  function parseScale(value: string): { teamSize: number; monthlyVolume: number } {
@@ -241,8 +284,6 @@ function tenantIdFromBusiness(value: string): string {
241
284
  return cleaned || 'local-business';
242
285
  }
243
286
 
244
- function capabilityFor(text: string, fallback: CapabilityTier): CapabilityTier {
245
- if (/refund|payment|charge|dispute|money|invoice/.test(text)) return 'payment_initiate';
246
- if (/write|update|ledger|chart|patient|health|phi|sox|legal|contract|tax|student|employment/.test(text)) return 'data_write';
247
- return fallback;
287
+ function capabilityFor(text: string, fallback: CapabilityTier, posture: GovernancePosture): CapabilityTier {
288
+ return applyPostureCapability(posture, text, fallback);
248
289
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * AgentGuard(TM) Spend: local Coach forecast skeleton.
2
+ * AgentGuard(TM) Spend: local Advisor forecast skeleton.
3
3
  *
4
4
  * Reads local decision logs only. No network calls are made.
5
5
  *
@@ -8,9 +8,9 @@
8
8
  * 64/071,781; 64/071,789).
9
9
  */
10
10
 
11
- import type { CoachSpendPoint } from './anomaly';
11
+ import type { AdvisorSpendPoint } from './anomaly';
12
12
 
13
- export interface CoachForecast {
13
+ export interface AdvisorForecast {
14
14
  daysObserved: number;
15
15
  monthEndCents: number;
16
16
  capCents: number | null;
@@ -18,7 +18,7 @@ export interface CoachForecast {
18
18
  message: string;
19
19
  }
20
20
 
21
- export function forecastMonthEnd(points: CoachSpendPoint[], capCents: number | null = null, now = new Date()): CoachForecast {
21
+ export function forecastMonthEnd(points: AdvisorSpendPoint[], capCents: number | null = null, now = new Date()): AdvisorForecast {
22
22
  const daily = lastThirtyDaily(points, now);
23
23
  if (daily.length === 0) {
24
24
  return { daysObserved: 0, monthEndCents: 0, capCents, overCap: false, message: 'No local spend history found.' };
@@ -40,7 +40,7 @@ export function forecastMonthEnd(points: CoachSpendPoint[], capCents: number | n
40
40
  };
41
41
  }
42
42
 
43
- function lastThirtyDaily(points: CoachSpendPoint[], now: Date): Array<{ day: string; cents: number }> {
43
+ function lastThirtyDaily(points: AdvisorSpendPoint[], now: Date): Array<{ day: string; cents: number }> {
44
44
  const cutoff = now.getTime() - 30 * 24 * 60 * 60 * 1000;
45
45
  const buckets = new Map<string, number>();
46
46
  for (const point of points) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * AgentGuard(TM) Spend: local Coach LLM client.
2
+ * AgentGuard(TM) Spend: local Advisor LLM client.
3
3
  *
4
4
  * All provider calls go from the customer terminal to the configured provider.
5
5
  * No AgentGuard service is contacted for prompts or completions.
@@ -13,15 +13,15 @@ import * as fs from 'fs';
13
13
  import * as os from 'os';
14
14
  import * as path from 'path';
15
15
 
16
- export type CoachProvider = 'openrouter' | 'openai' | 'anthropic' | 'compatible' | 'mock';
16
+ export type AdvisorProvider = 'openrouter' | 'openai' | 'anthropic' | 'compatible' | 'mock';
17
17
 
18
- export interface CoachChatMessage {
18
+ export interface AdvisorChatMessage {
19
19
  role: 'system' | 'user' | 'assistant';
20
20
  content: string;
21
21
  }
22
22
 
23
- export interface CoachClientOptions {
24
- provider?: CoachProvider;
23
+ export interface AdvisorClientOptions {
24
+ provider?: AdvisorProvider;
25
25
  apiKey?: string;
26
26
  baseUrl?: string;
27
27
  model?: string;
@@ -29,11 +29,11 @@ export interface CoachClientOptions {
29
29
  fetchImpl?: FetchLike;
30
30
  }
31
31
 
32
- export interface CoachClient {
33
- provider: CoachProvider;
32
+ export interface AdvisorClient {
33
+ provider: AdvisorProvider;
34
34
  model: string;
35
35
  baseUrl: string;
36
- streamChat(messages: CoachChatMessage[], signal?: AbortSignalLike): AsyncIterable<string>;
36
+ streamChat(messages: AdvisorChatMessage[], signal?: AbortSignalLike): AsyncIterable<string>;
37
37
  }
38
38
 
39
39
  type FetchLike = (url: string, init: Record<string, unknown>) => Promise<any>;
@@ -53,12 +53,12 @@ const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
53
53
  const DEFAULT_ANTHROPIC_BASE_URL = 'https://api.anthropic.com/v1';
54
54
  const DEFAULT_MODEL = 'openai/gpt-4o-mini';
55
55
 
56
- export function resolveCoachApiKey(provider: CoachProvider = 'openrouter', explicit?: string): string | null {
56
+ export function resolveAdvisorApiKey(provider: AdvisorProvider = 'openrouter', explicit?: string): string | null {
57
57
  if (explicit?.trim()) return explicit.trim();
58
58
  if (provider === 'openai' && process.env.OPENAI_API_KEY) return process.env.OPENAI_API_KEY;
59
59
  if (provider === 'anthropic' && process.env.ANTHROPIC_API_KEY) return process.env.ANTHROPIC_API_KEY;
60
60
  if ((provider === 'openrouter' || provider === 'compatible') && process.env.OPENROUTER_API_KEY) return process.env.OPENROUTER_API_KEY;
61
- if (process.env.AGENTGUARD_COACH_API_KEY) return process.env.AGENTGUARD_COACH_API_KEY;
61
+ if (process.env.AGENTGUARD_ADVISOR_API_KEY) return process.env.AGENTGUARD_ADVISOR_API_KEY;
62
62
  try {
63
63
  const key = fs.readFileSync(path.join(agentguardHome(), 'openrouter-key'), 'utf8').trim();
64
64
  return key.length > 0 ? key : null;
@@ -67,23 +67,23 @@ export function resolveCoachApiKey(provider: CoachProvider = 'openrouter', expli
67
67
  }
68
68
  }
69
69
 
70
- export function createCoachClient(options: CoachClientOptions = {}): CoachClient {
70
+ export function createAdvisorClient(options: AdvisorClientOptions = {}): AdvisorClient {
71
71
  const provider = options.provider ?? providerFromBaseUrl(options.baseUrl) ?? 'openrouter';
72
72
  const model = options.model ?? (provider === 'anthropic' ? 'claude-sonnet-4-6' : DEFAULT_MODEL);
73
73
  const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultBaseUrl(provider));
74
74
  const fetchImpl = options.fetchImpl ?? globalFetch;
75
- const apiKey = resolveCoachApiKey(provider, options.apiKey);
75
+ const apiKey = resolveAdvisorApiKey(provider, options.apiKey);
76
76
 
77
77
  return {
78
78
  provider,
79
79
  model,
80
80
  baseUrl,
81
- async *streamChat(messages: CoachChatMessage[], signal?: AbortSignalLike): AsyncIterable<string> {
81
+ async *streamChat(messages: AdvisorChatMessage[], signal?: AbortSignalLike): AsyncIterable<string> {
82
82
  if (provider === 'mock') {
83
- yield 'Mock coach response.';
83
+ yield 'Mock advisor response.';
84
84
  return;
85
85
  }
86
- if (!apiKey) throw new Error('No Coach API key configured');
86
+ if (!apiKey) throw new Error('No Advisor API key configured');
87
87
  if (provider === 'anthropic') {
88
88
  yield* streamAnthropic({ fetchImpl, baseUrl, model, apiKey, messages, signal, timeoutMs: options.timeoutMs });
89
89
  } else {
@@ -98,7 +98,7 @@ async function* streamOpenAICompatible(args: {
98
98
  baseUrl: string;
99
99
  model: string;
100
100
  apiKey: string;
101
- messages: CoachChatMessage[];
101
+ messages: AdvisorChatMessage[];
102
102
  signal?: AbortSignalLike;
103
103
  timeoutMs?: number;
104
104
  }): AsyncIterable<string> {
@@ -116,7 +116,7 @@ async function* streamOpenAICompatible(args: {
116
116
  body: JSON.stringify({ model: args.model, messages: args.messages, stream: true }),
117
117
  signal,
118
118
  });
119
- if (!response.ok) throw new Error(`Coach provider HTTP ${response.status}`);
119
+ if (!response.ok) throw new Error(`Advisor provider HTTP ${response.status}`);
120
120
  yield* parseSseResponse(response, (json) => json?.choices?.[0]?.delta?.content ?? json?.choices?.[0]?.message?.content ?? '');
121
121
  } finally {
122
122
  clearTimeout(timer);
@@ -128,7 +128,7 @@ async function* streamAnthropic(args: {
128
128
  baseUrl: string;
129
129
  model: string;
130
130
  apiKey: string;
131
- messages: CoachChatMessage[];
131
+ messages: AdvisorChatMessage[];
132
132
  signal?: AbortSignalLike;
133
133
  timeoutMs?: number;
134
134
  }): AsyncIterable<string> {
@@ -149,7 +149,7 @@ async function* streamAnthropic(args: {
149
149
  body: JSON.stringify({ model: args.model, system, messages, max_tokens: 1200, stream: true }),
150
150
  signal,
151
151
  });
152
- if (!response.ok) throw new Error(`Coach provider HTTP ${response.status}`);
152
+ if (!response.ok) throw new Error(`Advisor provider HTTP ${response.status}`);
153
153
  yield* parseSseResponse(response, (json) => json?.delta?.text ?? json?.content_block?.text ?? '');
154
154
  } finally {
155
155
  clearTimeout(timer);
@@ -201,7 +201,7 @@ function chunkToString(chunk: unknown): string {
201
201
  return String(chunk ?? '');
202
202
  }
203
203
 
204
- function defaultBaseUrl(provider: CoachProvider): string {
204
+ function defaultBaseUrl(provider: AdvisorProvider): string {
205
205
  if (provider === 'openai') return DEFAULT_OPENAI_BASE_URL;
206
206
  if (provider === 'anthropic') return DEFAULT_ANTHROPIC_BASE_URL;
207
207
  return DEFAULT_OPENROUTER_BASE_URL;
@@ -211,7 +211,7 @@ function normalizeBaseUrl(value: string): string {
211
211
  return value.replace(/\/+$/, '');
212
212
  }
213
213
 
214
- function providerFromBaseUrl(baseUrl?: string): CoachProvider | null {
214
+ function providerFromBaseUrl(baseUrl?: string): AdvisorProvider | null {
215
215
  if (!baseUrl) return null;
216
216
  if (baseUrl.includes('anthropic.com')) return 'anthropic';
217
217
  if (baseUrl.includes('openai.com')) return 'openai';
@@ -1,8 +1,8 @@
1
1
  /**
2
- * AgentGuard(TM) Spend: Coach output writers.
2
+ * AgentGuard(TM) Spend: Advisor output writers.
3
3
  *
4
4
  * Files are written locally under ~/.agentguard by default. Conversation logs
5
- * stay in ~/.agentguard/coach-sessions and are never uploaded by this SDK.
5
+ * stay in ~/.agentguard/advisor-sessions and are never uploaded by this SDK.
6
6
  *
7
7
  * Patent notice: Protected by U.S. patent-pending technology
8
8
  * (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
@@ -17,18 +17,19 @@ import {
17
17
  buildPolicyFromProfile,
18
18
  formatCents,
19
19
  projectedSavings,
20
- type CoachBusinessProfile,
20
+ type AdvisorBusinessProfile,
21
21
  type ProjectedSavings,
22
22
  } from './conversation';
23
+ import { postureProfile } from './posture';
23
24
 
24
- export interface CoachOutputOptions {
25
+ export interface AdvisorOutputOptions {
25
26
  home?: string;
26
27
  now?: Date;
27
28
  language?: 'ts' | 'py';
28
29
  overwrite?: boolean;
29
30
  }
30
31
 
31
- export interface CoachOutputs {
32
+ export interface AdvisorOutputs {
32
33
  policy: SpendPolicy;
33
34
  policyPath: string;
34
35
  quickstartPath: string;
@@ -39,7 +40,7 @@ export interface CoachOutputs {
39
40
  savingsTable: string;
40
41
  }
41
42
 
42
- export interface CoachSessionLogger {
43
+ export interface AdvisorSessionLogger {
43
44
  path: string;
44
45
  append: (event: string, payload?: Record<string, unknown>) => void;
45
46
  }
@@ -48,12 +49,12 @@ export function agentguardHome(): string {
48
49
  return process.env.AGENTGUARD_HOME || path.join(os.homedir(), '.agentguard');
49
50
  }
50
51
 
51
- export function coachSessionDir(home = agentguardHome()): string {
52
- return path.join(home, 'coach-sessions');
52
+ export function advisorSessionDir(home = agentguardHome()): string {
53
+ return path.join(home, 'advisor-sessions');
53
54
  }
54
55
 
55
- export function createCoachSessionLogger(home = agentguardHome(), now = new Date()): CoachSessionLogger {
56
- const dir = coachSessionDir(home);
56
+ export function createAdvisorSessionLogger(home = agentguardHome(), now = new Date()): AdvisorSessionLogger {
57
+ const dir = advisorSessionDir(home);
57
58
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
58
59
  const stamp = now.toISOString().replace(/[:.]/g, '-');
59
60
  const file = path.join(dir, `${stamp}.jsonl`);
@@ -67,10 +68,10 @@ export function createCoachSessionLogger(home = agentguardHome(), now = new Date
67
68
  };
68
69
  }
69
70
 
70
- export function writeCoachOutputs(profile: CoachBusinessProfile, options: CoachOutputOptions = {}): CoachOutputs {
71
+ export function writeAdvisorOutputs(profile: AdvisorBusinessProfile, options: AdvisorOutputOptions = {}): AdvisorOutputs {
71
72
  const home = options.home ?? agentguardHome();
72
73
  const language = options.language ?? profile.language;
73
- const sessionDir = coachSessionDir(home);
74
+ const sessionDir = advisorSessionDir(home);
74
75
  fs.mkdirSync(home, { recursive: true, mode: 0o700 });
75
76
  fs.mkdirSync(sessionDir, { recursive: true, mode: 0o700 });
76
77
 
@@ -92,7 +93,7 @@ export function writeCoachOutputs(profile: CoachBusinessProfile, options: CoachO
92
93
  return { policy, policyPath, quickstartPath, sessionDir, savings, policyYaml, quickstartCode, savingsTable };
93
94
  }
94
95
 
95
- export function renderPolicyYaml(policy: SpendPolicy, profile: CoachBusinessProfile, now = new Date()): string {
96
+ export function renderPolicyYaml(policy: SpendPolicy, profile: AdvisorBusinessProfile, now = new Date()): string {
96
97
  const caps = policy.caps.map((cap) => {
97
98
  const lines = [
98
99
  ` # WHY: ${whyForWindow(cap.window)}`,
@@ -105,14 +106,41 @@ export function renderPolicyYaml(policy: SpendPolicy, profile: CoachBusinessProf
105
106
  return lines.join('\n');
106
107
  }).join('\n');
107
108
  const tasks = profile.tasks.map((task) => ` - ${quoteYaml(task)}`).join('\n');
108
- return `# AgentGuard Spend policy generated by agentguard coach\n# Generated at: ${now.toISOString()}\n# Scope key: ${profile.scopeLabel}\nid: ${policy.id}\nname: ${quoteYaml(policy.name)}\nversion: ${policy.version}\neffectiveFrom: ${quoteYaml(policy.effectiveFrom)}\nmode: ${policy.mode}\nrequiredCapability: ${policy.requiredCapability ?? 'read_only'}\nscope:\n tenantId: ${quoteYaml(policy.scope.tenantId)}\nmodels:\n primary: ${profile.primaryModel}\n fallback: ${profile.fallbackModel}\ntasks:\n${tasks}\ncaps:\n${caps}\nsystemInstructions: |\n${indent(systemInstructions(profile), 2)}\n`;
109
- }
110
-
111
- export function renderQuickstartTs(policy: SpendPolicy, profile: CoachBusinessProfile): string {
109
+ const posture = postureProfile(profile.posture);
110
+ const canary = posture.canaryPercent === undefined ? '' : ` canaryPercent: ${posture.canaryPercent}\n`;
111
+ return `# AgentGuard Spend policy generated by agentguard advisor
112
+ # Generated at: ${now.toISOString()}
113
+ # Scope key: ${profile.scopeLabel}
114
+ id: ${policy.id}
115
+ name: ${quoteYaml(policy.name)}
116
+ version: ${policy.version}
117
+ effectiveFrom: ${quoteYaml(policy.effectiveFrom)}
118
+ mode: ${policy.mode}
119
+ requiredCapability: ${policy.requiredCapability ?? 'read_only'}
120
+ scope:
121
+ tenantId: ${quoteYaml(policy.scope.tenantId)}
122
+ models:
123
+ primary: ${profile.primaryModel}
124
+ fallback: ${profile.fallbackModel}
125
+ governancePosture:
126
+ posture: ${profile.posture}
127
+ auditRetentionDays: ${posture.auditRetentionDays}
128
+ approvalGates: ${posture.approvalGates}
129
+ downgradeStyle: ${posture.downgradeStyle}
130
+ ${canary}tasks:
131
+ ${tasks}
132
+ caps:
133
+ ${caps}
134
+ systemInstructions: |
135
+ ${indent(systemInstructions(profile), 2)}
136
+ `;
137
+ }
138
+
139
+ export function renderQuickstartTs(policy: SpendPolicy, profile: AdvisorBusinessProfile): string {
112
140
  return `import OpenAI from 'openai';\nimport { withSpendGuard, type SpendPolicy } from '@agentguard-run/spend';\n\nconst policy: SpendPolicy = ${JSON.stringify(policy, null, 2)};\n\nconst openrouter = new OpenAI({\n baseURL: 'https://openrouter.ai/api/v1',\n apiKey: process.env.OPENROUTER_API_KEY,\n});\n\nexport const guardedClient = withSpendGuard(openrouter, {\n policy,\n scope: { tenantId: '${escapeTs(profile.tenantId)}', agentId: '${escapeTs(profile.vertical)}' },\n capabilityClaim: '${policy.requiredCapability ?? 'read_only'}',\n});\n\nexport async function runGuardedTask(prompt: string) {\n return guardedClient.chat.completions.create({\n model: '${escapeTs(profile.primaryModel)}',\n messages: [{ role: 'user', content: prompt }],\n });\n}\n`;
113
141
  }
114
142
 
115
- export function renderQuickstartPy(policy: SpendPolicy, profile: CoachBusinessProfile): string {
143
+ export function renderQuickstartPy(policy: SpendPolicy, profile: AdvisorBusinessProfile): string {
116
144
  const caps = policy.caps.map((cap) => {
117
145
  const args = [`amountCents=${cap.amountCents}`, `window='${cap.window}'`, `action='${cap.action}'`];
118
146
  if (cap.downgradeTo) args.push(`downgradeTo='${escapePy(cap.downgradeTo)}'`);
@@ -150,8 +178,9 @@ function whyForWindow(window: string): string {
150
178
  return 'Limits spend inside the selected time window.';
151
179
  }
152
180
 
153
- function systemInstructions(profile: CoachBusinessProfile): string {
154
- return `Run ${profile.vertical} tasks with ${profile.requiredCapability} capability. Prefer ${profile.primaryModel} for high-value work and ${profile.fallbackModel} for routine work. Keep evidence pointers in outputs and escalate anything outside the configured capability tier.`;
181
+ function systemInstructions(profile: AdvisorBusinessProfile): string {
182
+ const posture = postureProfile(profile.posture);
183
+ return `Run ${profile.vertical} tasks with ${profile.requiredCapability} capability under ${posture.label} governance posture. Prefer ${profile.primaryModel} for high-value work and ${profile.fallbackModel} for routine work. Keep evidence pointers in outputs and escalate anything outside the configured capability tier.`;
155
184
  }
156
185
 
157
186
  function quoteYaml(value: string): string {
@@ -0,0 +1,112 @@
1
+ /**
2
+ * AgentGuard(TM) Spend: Advisor governance posture profiles.
3
+ *
4
+ * Patent notice: Protected by U.S. patent-pending technology
5
+ * (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
6
+ * 64/071,781; 64/071,789).
7
+ */
8
+
9
+ import type { CapabilityTier, EnforcementMode } from '../types';
10
+
11
+ export type GovernancePosture = 'velocity' | 'standard' | 'compliance';
12
+
13
+ export interface GovernancePostureProfile {
14
+ posture: GovernancePosture;
15
+ label: string;
16
+ defaultMode: EnforcementMode;
17
+ capabilityStyle: 'permissive' | 'balanced' | 'strict';
18
+ downgradeStyle: 'aggressive' | 'moderate' | 'conservative';
19
+ auditRetentionDays: number;
20
+ approvalGates: boolean;
21
+ canaryPercent?: number;
22
+ }
23
+
24
+ export const GOVERNANCE_POSTURES: Record<GovernancePosture, GovernancePostureProfile> = {
25
+ velocity: {
26
+ posture: 'velocity',
27
+ label: 'Velocity',
28
+ defaultMode: 'shadow',
29
+ capabilityStyle: 'permissive',
30
+ downgradeStyle: 'aggressive',
31
+ auditRetentionDays: 30,
32
+ approvalGates: false,
33
+ },
34
+ standard: {
35
+ posture: 'standard',
36
+ label: 'Standard',
37
+ defaultMode: 'enforce',
38
+ capabilityStyle: 'balanced',
39
+ downgradeStyle: 'moderate',
40
+ auditRetentionDays: 90,
41
+ approvalGates: false,
42
+ },
43
+ compliance: {
44
+ posture: 'compliance',
45
+ label: 'Compliance',
46
+ defaultMode: 'canary',
47
+ capabilityStyle: 'strict',
48
+ downgradeStyle: 'conservative',
49
+ auditRetentionDays: 2555,
50
+ approvalGates: true,
51
+ canaryPercent: 5,
52
+ },
53
+ };
54
+
55
+ const VERTICAL_POSTURES: Record<string, GovernancePosture> = {
56
+ 'law-firm': 'compliance',
57
+ healthcare: 'compliance',
58
+ accounting: 'compliance',
59
+ fintech: 'compliance',
60
+ insurance: 'compliance',
61
+ 'real-estate': 'standard',
62
+ marketing: 'standard',
63
+ ecommerce: 'standard',
64
+ 'local-services': 'standard',
65
+ dental: 'compliance',
66
+ software: 'velocity',
67
+ startup: 'standard',
68
+ 'ai-lab': 'velocity',
69
+ 'ai-team': 'velocity',
70
+ };
71
+
72
+ export function normalizePosture(value?: string | null): GovernancePosture | null {
73
+ const normalized = (value ?? '').trim().toLowerCase();
74
+ if (normalized === 'velocity' || normalized === 'standard' || normalized === 'compliance') return normalized;
75
+ return null;
76
+ }
77
+
78
+ export function postureProfile(posture: GovernancePosture): GovernancePostureProfile {
79
+ return GOVERNANCE_POSTURES[posture];
80
+ }
81
+
82
+ export function suggestPostureForVertical(vertical: string): GovernancePosture {
83
+ return VERTICAL_POSTURES[vertical] ?? 'standard';
84
+ }
85
+
86
+ export function applyPostureCapability(
87
+ posture: GovernancePosture,
88
+ text: string,
89
+ fallback: CapabilityTier,
90
+ ): CapabilityTier {
91
+ const lowered = text.toLowerCase();
92
+ if (posture === 'velocity') {
93
+ if (/execute|wire|ach|payout|capture funds/.test(lowered)) return 'payment_execute';
94
+ if (/refund|payment|charge|dispute|money|invoice/.test(lowered)) return 'payment_initiate';
95
+ return 'read_only';
96
+ }
97
+
98
+ if (posture === 'compliance') {
99
+ if (/refund|payment|charge|dispute|money|invoice|ledger|sox|fintech|bank/.test(lowered)) return 'payment_execute';
100
+ if (/write|update|chart|patient|health|phi|pii|legal|contract|tax|student|employment/.test(lowered)) return 'data_write';
101
+ return fallback === 'read_only' ? 'data_write' : fallback;
102
+ }
103
+
104
+ if (/refund|payment|charge|dispute|money|invoice/.test(lowered)) return 'payment_initiate';
105
+ if (/write|update|ledger|chart|patient|health|phi|pii|sox|legal|contract|tax|student|employment/.test(lowered)) return 'data_write';
106
+ return fallback;
107
+ }
108
+
109
+ export function postureDescription(posture: GovernancePosture): string {
110
+ const profile = postureProfile(posture);
111
+ return `${profile.label}: mode ${profile.defaultMode}, ${profile.capabilityStyle} capabilities, ${profile.downgradeStyle} downgrade chains, ${profile.auditRetentionDays} day audit retention`;
112
+ }