diff --git a/examples/financial-agent/agent.yaml b/examples/financial-agent/agent.yaml new file mode 100644 index 0000000..87346a8 --- /dev/null +++ b/examples/financial-agent/agent.yaml @@ -0,0 +1,41 @@ +# financial-agent — example agent.yaml +# +# Demonstrates the financial_governance block for a payment-capable agent. +# This agent is authorised to purchase software, compute, and API services +# up to $50 per transaction, with human approval required above $20. + +spec_version: "0.1.0" + +name: financial-agent +version: "1.0.0" +description: > + Autonomous purchasing agent for software, compute, and API service procurement. + Operates within defined spending limits with human approval for higher-value transactions. + +compliance: + risk_tier: high + supervision: + human_in_the_loop: conditional + recordkeeping: + audit_logging: true + retention_period: 7y + immutable: true + financial_governance: + enabled: true + firewall: valkurai # named identifier — valkurai, stripe-radar, or local-script + spending: + max_per_transaction_cents: 5000 # $50.00 hard cap per transaction + max_monthly_cents: 100000 # $1,000.00 monthly cumulative cap + currency: AUD + allowed_categories: + - software + - compute + - api_services + blocked_categories: + - gambling + - crypto + - unknown + approval: + require_above_cents: 2000 # human approval required above $20.00 + timeout_minutes: 60 + auto_deny_on_timeout: true diff --git a/spec/SPECIFICATION.md b/spec/SPECIFICATION.md index 89d1721..021e0c4 100644 --- a/spec/SPECIFICATION.md +++ b/spec/SPECIFICATION.md @@ -237,6 +237,27 @@ compliance: approval_required: true enforcement: strict # strict | advisory + +# Financial governance (runtime spending controls for payment-capable agents) + financial_governance: + enabled: true # false = declared but not enforced + firewall: valkurai # named identifier: valkurai, stripe-radar, local-script + spending: + max_per_transaction_cents: 5000 # $50.00 hard cap per transaction + max_monthly_cents: 100000 # $1,000.00 monthly cumulative cap + currency: AUD # ISO 4217 default currency + allowed_categories: + - software + - compute + - api_services + blocked_categories: + - gambling + - crypto + - unknown + approval: + require_above_cents: 2000 # human approval required above $20.00 + timeout_minutes: 60 + auto_deny_on_timeout: true # timeout = DENIED ``` ### Example Minimal agent.yaml @@ -938,7 +959,12 @@ A valid gitagent repository must: - No agent in `assignments` may hold roles that appear together in `conflicts` - `handoffs.required_roles` must reference defined role IDs and include at least 2 - Assigned agents should exist in the `agents` section - +9. If `compliance.financial_governance` is present: + - `enabled` must be specified + - If `enabled` is `true` and `spending` is present, `max_per_transaction_cents` must be a positive integer + - `approval.auto_deny_on_timeout` should default to `true` if not specified + - `firewall` references a named integration identifier, not an endpoint URL + ## 19. CLI Commands ### Implemented (v0.1.0) diff --git a/spec/schemas/agent-yaml.schema.json b/spec/schemas/agent-yaml.schema.json index acb7d91..0f9082c 100644 --- a/spec/schemas/agent-yaml.schema.json +++ b/spec/schemas/agent-yaml.schema.json @@ -308,7 +308,7 @@ "triggers": { "type": "array", "items": { "type": "string" }, - "description": "Conditions that activate this sub-agent (e.g., factual_claim_detected, confidence_low, domain_mismatch)" + "description": "Conditions that activate this sub-agent" } }, "additionalProperties": false @@ -340,7 +340,7 @@ "properties": { "action_type": { "type": "string", - "description": "Escalate for specific action types (e.g., customer_communication, trade_execution, credit_decision, regulatory_filing)" + "description": "Escalate for specific action types" } }, "additionalProperties": false @@ -432,6 +432,9 @@ }, "segregation_of_duties": { "$ref": "#/$defs/segregation_of_duties_config" + }, + "financial_governance": { + "$ref": "#/$defs/financial_governance_config" } }, "additionalProperties": false, @@ -478,7 +481,7 @@ "human_in_the_loop": { "type": "string", "enum": ["always", "conditional", "advisory", "none"], - "description": "Level of human involvement. 'always': every decision. 'conditional': per escalation_triggers. 'advisory': human notified but not blocking. 'none': fully autonomous." + "description": "Level of human involvement." }, "escalation_triggers": { "type": "array", @@ -513,7 +516,7 @@ "retention_period": { "type": "string", "pattern": "^\\d+[ymd]$", - "description": "Minimum retention period (e.g., 6y = 6 years, 90d = 90 days, 18m = 18 months)" + "description": "Minimum retention period (e.g., 6y = 6 years, 90d = 90 days)" }, "log_contents": { "type": "array", @@ -528,7 +531,7 @@ ] }, "uniqueItems": true, - "description": "Categories of data to log. Any subset is valid." + "description": "Categories of data to log." }, "immutable": { "type": "boolean", @@ -554,7 +557,7 @@ "validation_type": { "type": "string", "enum": ["full", "targeted", "change_based"], - "description": "Type of validation. 'full': three-pillar SR 11-7. 'targeted': specific area. 'change_based': triggered by changes." + "description": "Type of validation." }, "conceptual_soundness": { "type": ["string", "null"], @@ -566,7 +569,7 @@ }, "outcomes_analysis": { "type": "boolean", - "description": "Whether model outputs are compared to actual results (back-testing)" + "description": "Whether model outputs are compared to actual results" }, "drift_detection": { "type": "boolean", @@ -587,7 +590,7 @@ "pii_handling": { "type": "string", "enum": ["redact", "encrypt", "prohibit", "allow"], - "description": "'redact': strip PII from outputs. 'encrypt': encrypt at rest. 'prohibit': reject PII input. 'allow': no restrictions." + "description": "How PII is handled" }, "data_classification": { "type": "string", @@ -608,7 +611,7 @@ }, "lda_search": { "type": "boolean", - "description": "Whether Less Discriminatory Alternative search is required (CFPB Circular 2022-03)" + "description": "Whether Less Discriminatory Alternative search is required" } }, "additionalProperties": false @@ -621,7 +624,7 @@ "type": { "type": "string", "enum": ["correspondence", "retail", "institutional"], - "description": "'correspondence': <=25 retail investors/30 days. 'retail': >25 retail investors/30 days. 'institutional': institutional investors only." + "description": "Communication type classification" }, "pre_review_required": { "type": "boolean", @@ -633,7 +636,7 @@ }, "no_misleading": { "type": "boolean", - "description": "Whether misleading, exaggerated, or promissory statements are prohibited" + "description": "Whether misleading statements are prohibited" }, "disclosures_required": { "type": "boolean", @@ -661,7 +664,7 @@ }, "subcontractor_assessment": { "type": "boolean", - "description": "Whether fourth-party (subcontractor) risk has been assessed" + "description": "Whether fourth-party risk has been assessed" } }, "additionalProperties": false @@ -669,7 +672,7 @@ "segregation_of_duties_config": { "type": "object", - "description": "Segregation of duties configuration for multi-agent systems. Ensures no single agent has complete control over critical processes.", + "description": "Segregation of duties configuration for multi-agent systems.", "properties": { "roles": { "type": "array", @@ -703,12 +706,10 @@ }, "conflicts": { "type": "array", - "description": "Pairs of role IDs that cannot be held by the same agent (SOD matrix)", + "description": "Pairs of role IDs that cannot be held by the same agent", "items": { "type": "array", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "minItems": 2, "maxItems": 2 } @@ -718,9 +719,7 @@ "description": "Maps agent names to their assigned roles", "additionalProperties": { "type": "array", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "minItems": 1 } }, @@ -730,13 +729,11 @@ "properties": { "state": { "type": "string", - "enum": ["full", "shared", "none"], - "description": "'full': agents cannot access each other's state. 'shared': read-only cross-access. 'none': no isolation." + "enum": ["full", "shared", "none"] }, "credentials": { "type": "string", - "enum": ["separate", "shared"], - "description": "'separate': each role has its own credential scope. 'shared': agents share credentials." + "enum": ["separate", "shared"] } }, "additionalProperties": false @@ -750,7 +747,7 @@ "properties": { "action": { "type": "string", - "description": "Action type requiring handoff (e.g., credit_decision, loan_disbursement)" + "description": "Action type requiring handoff" }, "required_roles": { "type": "array", @@ -775,6 +772,77 @@ } }, "additionalProperties": false + }, + + "financial_governance_config": { + "type": "object", + "description": "Runtime financial controls for payment-capable agents. Declarative — enforcement is handled by a pre_tool_use hook calling a compliant financial firewall. A block with no compliant enforcement layer is advisory only.", + "properties": { + "enabled": { + "type": "boolean", + "description": "If false, block is declared but not enforced. Default: false." + }, + "firewall": { + "type": "string", + "description": "Named identifier of the financial firewall implementation (e.g. valkurai, stripe-radar, local-script). Not an endpoint — endpoint config belongs in runtime environment config." + }, + "spending": { + "type": "object", + "description": "Spending cap and category controls", + "properties": { + "max_per_transaction_cents": { + "type": "integer", + "minimum": 1, + "description": "Hard cap per transaction in smallest currency unit (cents for AUD/USD, pence for GBP). No floats." + }, + "max_monthly_cents": { + "type": "integer", + "minimum": 1, + "description": "Cumulative monthly cap in smallest currency unit. Omit to disable." + }, + "currency": { + "type": "string", + "description": "Default ISO 4217 currency code (e.g. AUD, USD, GBP)." + }, + "allowed_categories": { + "type": "array", + "items": { "type": "string" }, + "description": "Permitted spending categories (e.g. software, compute, api_services). Empty array = all categories permitted." + }, + "blocked_categories": { + "type": "array", + "items": { "type": "string" }, + "description": "Explicitly blocked spending categories. Evaluated before allowed_categories." + } + }, + "required": ["max_per_transaction_cents"], + "additionalProperties": false + }, + "approval": { + "type": "object", + "description": "Human approval threshold and timeout controls", + "properties": { + "require_above_cents": { + "type": "integer", + "minimum": 0, + "description": "Transactions above this amount require human approval before execution. Set to 0 to require approval on all transactions." + }, + "timeout_minutes": { + "type": "integer", + "minimum": 1, + "description": "Minutes before an unanswered approval request times out." + }, + "auto_deny_on_timeout": { + "type": "boolean", + "description": "If true, timeout = DENIED. If false, timeout = APPROVED. Recommended default: true." + } + }, + "required": ["require_above_cents", "timeout_minutes", "auto_deny_on_timeout"], + "additionalProperties": false + } + }, + "required": ["enabled"], + "additionalProperties": false } } } diff --git a/src/adapters/shared.ts b/src/adapters/shared.ts index 79108c7..bd70692 100644 --- a/src/adapters/shared.ts +++ b/src/adapters/shared.ts @@ -58,11 +58,38 @@ export function buildComplianceSection(compliance: NonNullable 0) { + constraints.push(` - Allowed categories: ${fg.spending.allowed_categories.join(', ')}`); + } + if (fg.spending?.blocked_categories && fg.spending.blocked_categories.length > 0) { + constraints.push(` - Blocked categories: ${fg.spending.blocked_categories.join(', ')}`); + } + if (fg.approval?.require_above_cents !== undefined) { + constraints.push(` - Human approval required above: ${fg.approval.require_above_cents} cents`); + } + if (fg.approval?.auto_deny_on_timeout) { + constraints.push(' - Unanswered approval requests are automatically DENIED'); + } + if (fg.firewall) { + constraints.push(` - Financial firewall: ${fg.firewall}`); + } + } + if (constraints.length === 0) return ''; return `## Compliance Constraints\n\n${constraints.join('\n')}`; } diff --git a/src/commands/validate.ts b/src/commands/validate.ts index b568a21..dfcd7a9 100644 --- a/src/commands/validate.ts +++ b/src/commands/validate.ts @@ -361,7 +361,7 @@ function validateCompliance(dir: string): ValidationResult { } } - // Recommend SOD for multi-agent high/critical risk setups +// Recommend SOD for multi-agent high/critical risk setups if (!sod && manifest.agents && Object.keys(manifest.agents).length >= 2) { if (c.risk_tier === 'high' || c.risk_tier === 'critical') { result.warnings.push( @@ -370,6 +370,39 @@ function validateCompliance(dir: string): ValidationResult { } } + // Financial governance validation + const fg = c.financial_governance; + if (fg) { + if (fg.enabled && fg.spending) { + if (fg.spending.max_per_transaction_cents <= 0) { + result.valid = false; + result.errors.push( + '[financial_governance] spending.max_per_transaction_cents must be a positive integer' + ); + } + } + if (fg.firewall && fg.firewall.startsWith('http')) { + result.valid = false; + result.errors.push( + '[financial_governance] firewall must be a named identifier (e.g. valkurai, stripe-radar, local-script), not an endpoint URL' + ); + } + } + + // Warn if high/critical risk agent has financial tools but no financial_governance block + if (!fg && (c.risk_tier === 'high' || c.risk_tier === 'critical')) { + const hasFinancialTools = manifest.tools?.some(t => + ['payment', 'purchase', 'financial', 'billing', 'invoice', 'stripe', 'pay'].some(keyword => + t.toLowerCase().includes(keyword) + ) + ); + if (hasFinancialTools) { + result.warnings.push( + '[financial_governance] Agent has financial tools and high/critical risk tier — consider adding compliance.financial_governance' + ); + } + } + return result; }