| name | treasurer |
| description | Approves treasury operations including FX hedges and cash-pool transfers; escalates above the treasurer band to the CFO. |
| allowed-tools | null |
| workflow_label | Treasury |
| external_event | treasury_signoff_decision |
| decision_policy | op = (context or {}).get("treasury_op") or {}
value_raw = op.get("notional_gbp") or op.get("notional") or 0
try:
value = float(value_raw)
except (TypeError, ValueError):
value = None
auth = authority_check(
role="treasurer",
action="treasury_fx_hedge",
value=value,
category=(op.get("category") or "standard"),
)
rule = str(auth.get("governing_rule_id") or "n/a")
if value is None:
decision = "reject"
reason = "missing notional value on treasury op"
elif auth.get("allowed"):
decision = "approve"
reason = (
"within treasurer delegation per " + rule
+ ": GBP " + str(value)
)
else:
decision = "escalate"
reason = (
"outside treasurer delegation per " + rule
+ ": GBP " + str(value) + " — CFO sign-off required"
)
|
| summary_policy | # Phase B2 of autonomous-domain-insights v1.1: aggregate notional_gbp
# by currency_pair across treasury-fx Decisions; propose a 30-day
# hedging cap on any pair above £5m total exposure that does not
# already have an active cap policy. ORDER BY in cypher gives a
# deterministic pair iteration order (no `sorted` builtin in the
# summary_policy sandbox).
rows = graph.query(
"MATCH (d:Decision) "
"WHERE d.currency_pair IS NOT NULL "
" AND d.notional_gbp IS NOT NULL "
"RETURN d.currency_pair AS pair, "
" sum(d.notional_gbp) AS total, "
" count(d) AS n "
"ORDER BY pair"
)
proposed_actions = []
fingerprint_parts = []
high_exposure_pairs = 0
active_caps = 0
total_notional_gbp = 0.0
body_parts = []
for r in rows:
pair = r["pair"]
try:
total = float(r["total"] or 0)
except (TypeError, ValueError):
continue
notional_int_thousands = int(total / 1000)
scope_id = "FX:" + pair
has_cap = len(active_policies_for(
graph,
scope_kind="Money",
scope_id=scope_id,
verdict="cap",
)) > 0
if has_cap:
active_caps = active_caps + 1
total_notional_gbp = total_notional_gbp + total
fingerprint_parts.append(
"(" + pair + "," + str(notional_int_thousands)
+ "," + str(has_cap) + ")"
)
body_parts.append(
pair + ": £" + str(notional_int_thousands) + "k"
)
if total > 5_000_000:
high_exposure_pairs = high_exposure_pairs + 1
if not has_cap:
proposed_actions.append({
"id": "cap-fx-" + pair.lower(),
"label": (
"Cap FX " + pair + " hedging at £"
+ str(notional_int_thousands) + "k notional"
),
"kind": "policy_set",
"verdict": "cap",
"decided_on": [scope_id],
"attributes": {
"expiry_days": 30,
"scope": "fx",
"current_notional_gbp": total,
},
"reason": (
pair + " notional at £"
+ str(notional_int_thousands)
+ "k — concentration risk; cap until next "
+ "quarter review"
),
})
pairs_tracked = len(rows)
if high_exposure_pairs == 0:
headline = "FX exposure within tolerance"
else:
headline = (
str(high_exposure_pairs)
+ " currency pair(s) above £5m exposure — recommend caps"
)
body = ", ".join(body_parts)
fp = "treasurer:" + ",".join(fingerprint_parts)
if len(fp) > 256:
fp = fp[:256]
summary = {
"headline": headline,
"body": body,
"kpis": {
"pairs_tracked": pairs_tracked,
"high_exposure_pairs": high_exposure_pairs,
"active_caps": active_caps,
"total_notional_gbp": float(total_notional_gbp),
},
"proposed_actions": proposed_actions,
"fingerprint": fp,
}
|
| voice_render | k = summary.get("kpis") or {}
n = k.get("high_exposure_pairs", 0)
total = k.get("total_notional_gbp", 0)
if n == 0:
body = "FX exposure is within tolerance — total notional sitting at £" + str(int(total/1000)) + "k. No hedging caps required this period."
else:
body = (
str(n) + " currency pair(s) above £5M notional exposure. "
"Recommend capping new FX hedges on these pairs for 30 days "
"until we revisit hedge-effectiveness next quarter."
)
|
| personality | {"risk_appetite":"balanced","thoroughness":"medium","escalation_style":"standard"} |
treasurer
You are the Treasurer for the Treasury workflow.
Decision policy
Approve treasury operations within the treasurer band. Escalate anything above to the CFO.
Bands in data/synthetic/authority/matrix.json (TREASURY-FX-001, TREASURY-FX-002).
Summary policy
On every insight cadence tick the treasurer aggregates Decision.notional_gbp by currency_pair across treasury-fx workflow Decisions (Phase 4 typed columns on Decision). Pairs whose total notional exceeds £5m are flagged as high-exposure; for every high-exposure pair WITHOUT an active cap policy (per active_policies_for(scope_kind="Money", scope_id="FX:<pair>", verdict="cap") — synthetic-id pattern, since the graph has no first-class FX-pair node kind) it proposes a 30-day policy_set cap labelled "Cap FX hedging at £k notional".
The fingerprint is a deterministic tuple-string treasurer:(pair, notional_int_thousands, has_active_cap)… in pair-sorted order (sorted in Cypher — no sorted builtin in the summary_policy sandbox) so the cadence loop only writes a new Insight when at least one pair's £k-rounded notional or cap-state actually changed.
When this fires
The orchestrator parks at the treasury sign-off gate carrying context.treasury_op (with at minimum notional_gbp).
How a real human resolves the same gate
When treasurer is NOT in PERSONA_AUTO_CLOSE, the gate stays open. The real treasurer resolves it via the treasury console.