Per-key policy constraints
On top of scopes, every agent key can carry a JSON policy object that constrains what the key is allowed to do — independently of the user's IPS.
The policy shape
interface KeyPolicy {
/** Cumulative cap on amountUsd across all propose calls per UTC day. */
maxAmountUsdPerDay?: number;
/** Ticker allowlist. If set, any other symbol returns
* key_policy_asset_not_allowed. */
allowedAssets?: string[];
/** Asset-class allowlist. If set, the other class is blocked. */
allowedAssetTypes?: ('crypto' | 'tradfi')[];
/** Local-time window. start/end in hours (0–23, half-open).
* Wrap-around windows (e.g. 22 → 6) are supported. */
allowedHoursLocal?: { start: number; end: number; tz: string };
/** Hard cap on total calls per UTC day. Counts every tool call. */
dailyCallCap?: number;
}Example: a research-bot key
A key for a research agent that should only run during US market hours, only validate (no proposing), and never look at crypto:
{
"scopes": ["read", "validate"],
"policy": {
"allowedAssetTypes": ["tradfi"],
"allowedHoursLocal": { "start": 9, "end": 16, "tz": "America/New_York" },
"dailyCallCap": 500
}
}Example: a rebalance bot
A key allowed to propose trims but capped at $25k/day, only on a short list of assets, only during weekday morning hours:
{
"scopes": ["read", "validate", "propose"],
"policy": {
"maxAmountUsdPerDay": 25000,
"allowedAssets": ["BTC", "ETH", "SOL", "SPY", "QQQ"],
"allowedHoursLocal": { "start": 9, "end": 12, "tz": "America/New_York" }
}
}Violation rule names
When a key policy blocks a call, the response carries one or more of:
key_policy_asset_not_allowed— symbol isn’t inallowedAssets.key_policy_asset_type_not_allowed— class isn’t inallowedAssetTypes.key_policy_daily_amount_cap— the cumulativeamountUsdacross today’s proposes would exceedmaxAmountUsdPerDay.key_policy_daily_call_cap— the key’s call count for today is at or abovedailyCallCap.key_policy_outside_hours— current local time is outsideallowedHoursLocal.
Enforcement order
- 1 · scope check at the route guard. Missing scope returns
401. - 2 · per-key policy check before any IPS evaluation. Cheap to compute, short-circuits expensive work if the policy blocks. The decision is audit-logged.
- 3 · IPS guardrail evaluates the user’s mandate, drawdown, standing rules, asset authority.
A blocked per-key policy returns the violations under the same { allowed: false, violations: [...] } envelope as an IPS rejection — your agent code doesn’t need a separate branch to handle it.
Editing a policy
Policy is mutable. Edit at /agent in the AC app or via the management API. Changes take effect immediately — the next call from the key sees the new policy. Rotation is unnecessary unless the key itself was compromised.