DEVELOPERS
Certified infrastructure-context API, v2.0
Three primitives: Attestation · Regime · Delta
The fastest way to integrate Invarians. Handles authentication, retry, STALE policy, and calibration metadata automatically.
inv_ key before running the examples below.
pip install "invarians[requests]>=0.5.0" # v2.0 client (Attestation + Regime + Drift)
from invarians import InvariansClient
client = InvariansClient(api_key="inv_your_key_here")
# v2.0 panel with full diagnostic (regime + Delta per metric)
panel = client.get_panel_v2(
chains=["ethereum", "arbitrum"],
bridges=["cctp"],
include="diagnostic", # core | diagnostic | full
)
eth = panel.l1_by_chain("ethereum")
arb = panel.l2_by_chain("arbitrum")
br = panel.bridge_by_id("arbitrum-ethereum/cctp")
# Primitive 2: regime classification (12 codes per chain)
if eth.regime.startswith("S2") or arb.regime.startswith("S2"):
pause_agent_execution() # structural stress detected
# Primitive 3: Delta (per-chain calibrated precursors, v3 preview)
for p in arb.precursors:
if p.is_firing and p.baseline_lift >= 1.5:
defer_action() # calibrated precursor fired, defer for p.lead_hours
# Per-metric inspection (only available in diagnostic mode)
print(eth.demand.tx.shift) # current deviation vs 30d baseline
print(eth.demand.tx.is_drifting_away) # True if deviation amplifying
print(eth.demand.tx.is_reverting) # True if returning to nominal
# Primitive 1: HMAC integrity
ok = client.verify_panel_v2(
panel_dict, panel.signed_execution_context.signature
)
For raw HTTP access (JavaScript, curl, other languages), see sections below.
https://api.invarians.comAuth:
Authorization: Bearer inv_your_api_keyEndpoint 1, Panel:
GET /v2/panelReturns the full state of every tracked L1, L2 and bridge in one signed response. Optional filters:
?chains=ethereum,arbitrum · ?bridges=ccip,cctp.Endpoint 2, Verify:
POST /v2/verifyVerifies the HMAC signature over a panel payload. Body:
{"payload": <panel without signed_execution_context>, "signature": "hmac-sha256:..."}.Endpoint 3, CCTP attestation retrieval:
GET /v2/cctp/attestation/{message_hash}Returns the per-message Circle ECDSA signature (secp256k1, 65 bytes) for a CCTP message, captured by the Invarians CCTP collector. The signature is independently verifiable against Circle's published attester public key. Use this for audit-trail or independent verification of any CCTP route signal exposed in the panel.
Endpoint 4, CCIP message retrieval:
GET /v2/ccip/message/{message_id}Returns the per-message CCIP row for a bytes32 messageId: source send metadata (sender, receiver, sequence_number, nonce, gas_limit, fee_token), and, when executed, destination metadata (dest_tx_hash, dest_block_number, dest_block_timestamp, execution_state). A
status field reads "pending" or "executed". crypto_anchor is null today; will become non-null once DON multi-sig capture is added.Panel coverage: L1
ethereum, polygon, avalanche · L2 arbitrum, base, optimism · 10 Circle CCTP routes (capability_level: per_message_attested, Circle ECDSA per message) · 10 Chainlink CCIP lanes (capability_level: per_message_attested, source-to-execute matched by messageId). Each bridge entry carries unified BS1 / BS2 state, a structured metrics block, and a crypto pointer to the verifiable attestation when applicable.Direction-agnostic: the panel exposes states, not routes. Your agent composes
from/to client-side by picking the relevant L1/L2/bridge entries.
# Python — raw HTTP, v2.0 panel with diagnostic mode
import requests
API_KEY = "inv_your_api_key"
BASE_URL = "https://api.invarians.com"
def get_panel(chains=None, bridges=None, include="core") -> dict:
params = {"include": include}
if chains: params["chains"] = ",".join(chains)
if bridges: params["bridges"] = ",".join(bridges)
r = requests.get(
f"{BASE_URL}/v2/panel",
headers={"Authorization": f"Bearer {API_KEY}"},
params=params,
)
r.raise_for_status()
return r.json()
panel = get_panel(chains=["ethereum"], bridges=["cctp"], include="diagnostic")
print(panel["version"]) # "2.0.0"
print(panel["oracle_status"]) # "OK" | "DEGRADED"
print(panel["panel"]["l1"][0]["regime"]) # 12 possible codes per chain
for p in panel["panel"]["l2"][0].get("precursors", []):
print(p["axis"], p["fires"], p["baseline_lift"]) # v3 per-chain calibrated precursors
print(panel["signed_execution_context"]["signature"]) # "hmac-sha256:..."
// JavaScript / Node — v2.0 panel
const getPanel = async (params = {}) => {
const qs = new URLSearchParams(params).toString();
const res = await fetch(
`https://api.invarians.com/v2/panel?${qs}`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
return res.json();
};
const panel = await getPanel({
chains: "ethereum,arbitrum",
bridges: "cctp",
include: "diagnostic",
});
console.log(panel.version); // "2.0.0"
console.log(panel.panel.l1[0].regime); // "S1D1" | "S1D2+" | ... (12 codes)
console.log(panel.panel.l1[0].demand.tx.shift); // per-metric deviation magnitude
console.log(panel.panel.l1[0].demand.tx.shift_magnitude_delta); // per-metric trend
console.log(panel.panel.bridges[0].state); // "BS1" | "BS2" | null
# Python — compose a route from the v2.0 panel (agent-side)
panel = get_panel(chains=["ethereum", "arbitrum"], bridges=["cctp"], include="diagnostic")
l1 = next(e for e in panel["panel"]["l1"] if e["chain"] == "ethereum")
l2 = next(e for e in panel["panel"]["l2"] if e["chain"] == "arbitrum")
br = next(b for b in panel["panel"]["bridges"] if b["id"] == "arbitrum-ethereum/cctp")
# Primitive 2: regime per chain (12 codes)
print(l1["regime"], l1["status"]) # e.g. "S1D2+" "OK"
print(l2["regime"], l2["status"]) # e.g. "S1D1" "OK"
# Primitive 3: Delta (v3 per-chain precursors + raw shift inputs)
for p in l2.get("precursors", []):
print(p["axis"], p["fires"],
p["lead_hours"], p["baseline_lift"]) # calibrated per-chain precursor
print(l1["demand"]["sigma"]["shift"]) # per-metric current deviation (raw)
print(l1["demand"]["sigma"]["shift_magnitude_delta"]) # per-metric trend (raw)
# Bridge state
print(br["state"], br["calibrated"], br["status"]) # e.g. "BS1" True "OK"
The default panel returns every tracked chain and bridge (~25 entries, ~30 KB JSON in diagnostic mode). Most agents only need a small subset. Always filter your queries.
chains=ethereum,arbitrum— filter to a subset of L1/L2 chainsbridges=cctp— filter bridge types (ccip,cctp)include=core | diagnostic | full— payload tier (defaultcore)
# Minimal panel for a single L1+L2 pair, CCTP route only
GET /v2/panel?chains=ethereum,arbitrum&bridges=cctp&include=core
# → ~2 KB, regime + status + precursors[] per chain
# Diagnostic mode adds per-metric raw shift fields
GET /v2/panel?chains=ethereum&bridges=cctp&include=diagnostic
# → ~3 KB, with shift + shift_delta + shift_magnitude_delta per metric
# Research / debugging (raw EMA baselines included)
GET /v2/panel?chains=ethereum&include=full
# → ~5 KB, with baseline_short + baseline_long
# Multi-bridge route comparison (CCIP + CCTP)
GET /v2/panel?chains=ethereum,arbitrum&bridges=ccip,cctp&include=core
# → ~6 KB
| Query | Approx size | Use case |
|---|---|---|
?chains=ethereum&bridges=cctp&include=core |
~1.5 KB | Most common: regime + status + precursors[] per chain, single chain pair |
?chains=ethereum&include=diagnostic |
~3 KB | Stateful agent reading shifts directly |
?include=core (no chain filter) |
~5 KB | Multi-chain dashboard, compact payload |
?include=diagnostic (no filter) |
~30 KB | Full panel dump (avoid in production polling) |
capability_level: per_message_attested). CCTP carries the Circle ECDSA signature per message (crypto.anchor: circle_ecdsa); CCIP matches source send and destination execute by bytes32 messageId (crypto.anchor: null today, DON multi-sig CommitReport capture is the next step). BS1/BS2 classification is preliminary on the per-message latency baseline.Polling cadence: the L1/L2 entries refresh every ~5 min, bridges every ~10 min. Polling more frequently than once per minute returns the same payload (cached behind
Cache-Control: max-age=60).
Full response for GET /v2/panel. One signed payload with every tracked L1, L2, and bridge, axis-grouped by chain. The agent picks the entries relevant to its planned action.
{
"version": "2.0.0",
"oracle_status": "OK", // "OK" | "DEGRADED"
"issued_at": "2026-04-30T12:30:00Z",
"panel": {
// L1 chains, axis-grouped (structural / demand) per chain
"l1": [
{
"chain": "ethereum",
"regime": "S1D1", // 12 codes possible: S1, S2+, S2- × D1, D2+, D2-, D2±
"status": "OK", // "OK" | "STALE" | "UNAVAILABLE" | "UNCALIBRATED"
"computed_at": "2026-04-30T12:25:00Z",
// Structural axis (S): rhythm, continuity, plus beacon_participation on Ethereum only
"structural": {
"rhythm": {
"ratio": 1.0013, // short EMA (~10h) vs short baseline
"ratio_long": 0.9996, // long EMA (~30d) vs long baseline (diagnostic+)
"shift": 0.0017, // = ratio - ratio_long, current deviation
"shift_delta": 0.0008, // = shift - shift_prev, direction of value movement
"shift_magnitude_delta": 0.0008 // = |shift| - |shift_prev|, deviation grows or shrinks
},
"continuity": {
"ratio": 1.0, "ratio_long": 1.0, "shift": 0.0, "shift_delta": 0.0, "shift_magnitude_delta": 0.0
},
"beacon_participation": { // Ethereum only (Beacon Chain validators)
"ratio": 0.998,
"epoch": 312547,
"shift_available": false // slow EMA stabilizing, ~30d post-launch
}
},
// Demand axis (D): sigma, size, tx (echo of activity on confirmed blocks)
"demand": {
"sigma": { "ratio": 1.003, "ratio_long": 0.99, "shift": 0.013, "shift_delta": 0.005, "shift_magnitude_delta": 0.005 },
"size": { "ratio": 1.0095, "ratio_long": 1.05, "shift": -0.04, "shift_delta": -0.012, "shift_magnitude_delta": 0.012 },
"tx": { "ratio": 1.2037, "ratio_long": 1.18, "shift": 0.024, "shift_delta": 0.018, "shift_magnitude_delta": 0.018 }
},
// Precursors per chain (v3 design preview; empty on ethereum, calibration registry is per-chain)
"precursors": []
}
],
// L2 rollups, axis-grouped. structural also includes sequencer_publish_latency.
// demand has 5 observables on L2 (sigma + size + tx + complexity + gas_complexity).
"l2": [
{
"chain": "arbitrum",
"regime": "S1D1",
"status": "OK",
"computed_at": "2026-04-30T12:25:00Z",
"structural": {
"rhythm": { "ratio": 1.001, "ratio_long": 0.992, "shift": 0.009, "shift_delta": 0.001, "shift_magnitude_delta": 0.001 },
"continuity": { "ratio": 1.0, "ratio_long": 1.006, "shift": -0.006, "shift_delta": 0.0, "shift_magnitude_delta": 0.0 },
"sequencer_publish_latency": { // L2 third structural observable
"ratio": null,
"seconds": 11.4, // raw delay between L2 batches inscribed on L1
"shift_available": false // slow EMA pending
}
},
"demand": {
"sigma": { "ratio": 1.0, "ratio_long": 1.0, "shift": 0.0, "shift_delta": 0.0, "shift_magnitude_delta": 0.0 },
"size": { "ratio": 0.94, "ratio_long": 0.97, "shift": -0.03, "shift_delta": -0.005, "shift_magnitude_delta": 0.005 },
"tx": { "ratio": 0.91, "ratio_long": 1.01, "shift": -0.10, "shift_delta": -0.02, "shift_magnitude_delta": 0.02 },
"complexity": { "ratio": 1.02, "ratio_long": 1.0, "shift": 0.02, "shift_delta": 0.005, "shift_magnitude_delta": 0.005 },
"gas_complexity": { "ratio": 1.01, "ratio_long": 1.0, "shift": 0.01, "shift_delta": 0.002, "shift_magnitude_delta": 0.002 }
},
// Precursors per chain (v3 design preview; 6 calibrated configurations on arbitrum from ETH-ARB-CCTP 2025)
"precursors": [
{
"axis": "arb_struct_seq_publish_latency_shift",
"fires": false,
"current_smd": 0.080,
"smd_threshold_value": 0.2258,
"k_consecutive_hours": 2,
"pctl_threshold": 0.90,
"lead_hours": 6,
"outcome_category": "latency_high_only",
"bridge_corridor": "ETH-ARB-CCTP",
"baseline_lift": 1.913,
"baseline_p_adj": 0.023,
"baseline_precision": 0.439,
"baseline_alert_rate": 0.0057,
"cross_chain_status": "FAIL_on_optimism",
"calibrated_at": "2026-05-19T00:00:00Z"
}
// + 5 other arbitrum precursors (seq_publish_latency at lead 3h/12h, demand_tx, demand_size, eth_demand_tx cross-substrate)
]
}
// + base, optimism (same shape, each with its own per-chain precursors[])
],
// Bridges. Unified state codes BS1/BS2 across all protocols (CCIP, CCTP, future fast bridges); type field distinguishes the protocol.
"bridges": [
{
"id": "arbitrum-ethereum/cctp",
"endpoints": ["ethereum", "arbitrum"],
"type": "cctp",
"state": "BS1",
"calibrated": true,
"status": "OK",
"observed_at": "2026-05-04T12:28:00Z",
"window": "1h",
"circle_api_status": "OK"
}
// + base-ethereum/cctp, optimism-ethereum/cctp, plus CCIP entries with calibrated:false (raw observability until throughput sustained)
]
},
"coverage": {
"l1_chains": ["ethereum"],
"l2_chains": ["arbitrum", "base", "optimism"],
"bridges_ccip": 10,
"bridges_ccip": 10,
"bridges_cctp": 10,
"include_mode": "diagnostic", // echoes the ?include= query param
"methodology_url": "https://invarians.com/methodology"
},
// HMAC-SHA256 over canonical JSON of everything above. anchor populated in v2.x with the on-chain Merkle anchor.
"signed_execution_context": {
"payload_hash": "0x84f549ab8c50cdf609a2a84bae3d23d37fc1496de23acddaebe4d78c6080b268",
"signature": "hmac-sha256:8ad60a8c399a1f779056fca4005841aa9257b9cf721da49a4a68c7173ac76a06",
"key_id": "invarians-v1",
"anchor": null
}
}
?include= param :
core(default) : each metric exposes onlyratio(+epoch/secondsfor beacon/sequencer)diagnostic: addsratio_long,shift,shift_delta,shift_magnitude_deltaper metricfull: adds raw EMA baselines (baseline_short,baseline_long) for research and external calibration
The v2.0
drift composite is exposed for backward compatibility regardless of mode. It is flagged deprecated_unvalidated in v3 and replaced by the per-chain precursors[] array (see Consuming precursors).Per-item
status: "OK" fresh and calibrated · "STALE" no fresh observation in the item's window · "UNAVAILABLE" no data at all · "UNCALIBRATED" raw signals exposed, no regime classification yet (chains pending July 2026 for SOL/AVAX, bridges CCIP/CCTP pending P3).Global
oracle_status: "OK" if every item is OK or UNCALIBRATED, else "DEGRADED". UNCALIBRATED is not an error, it is a known in-progress state.The
shift_available: false flag appears on metrics whose slow EMA is still stabilizing upstream (Ethereum beacon_participation and L2 sequencer_publish_latency during the v2.0 launch period). The flag flips to true automatically once the long EMA is operational, no API version change required.
The four base codes below classify chains against an upper threshold on each axis. On chains with calibrated lower bounds (since 2026-04-29: ETH, POL on L1; ARB, BASE, OP on L2), the API also emits signed regime codes with explicit direction suffixes (+, -, ±). The four base codes remain valid as aliases for chains without lower bounds yet calibrated. See foundations §03b for the full extended grid.
| State | Condition | Typical infrastructure context |
|---|---|---|
| S1D1 | Structural and demand layers within historical baseline | Both layers within historical norms. Standard operating conditions. |
| S1D2 | Demand elevated beyond calibrated historical percentiles | Elevated demand relative to historical baseline. |
| S2D2 | Structural stress AND demand elevated simultaneously | Both layers above historical baseline simultaneously. Structural strain coinciding with elevated demand. |
| S2D1 | Structural stress WITHOUT elevated demand | Structural divergence without corresponding activity conditions. Invisible to fee-based monitors. |
On chains with calibrated lower bounds, each axis carries an explicit direction suffix. Total: 12 codes on L1 and 12 codes on L2 (since v2.0, L2 also reaches S2 thanks to sequencer_publish_latency). Parse with a regex on the regime field, branch on the suffix.
| Code | Axis · Direction | Meaning |
|---|---|---|
D1 |
demand · nominal | All demand ratios within their two-sided bounds. |
D2+ |
demand · high | At least one ratio above its high threshold, none below. Legacy D2 alias. |
D2- |
demand · low (starvation) | At least one ratio below its low threshold, none above. Sequencer slowing user-side, censorship of small transfers, mempool gel. |
D2± |
demand · composition split | At least one ratio above AND at least one below. Composition asymmetry, agentic concentration signature (rsETH 2026-04-18 cascade was D2±). |
S1 |
structure · nominal | Rhythm and continuity within their two-sided bounds. |
S2+ |
structure · slowed | Rhythm slows abnormally, blocks taking longer than baseline. Legacy S2 alias. |
S2- |
structure · accelerated | Rhythm accelerates abnormally, blocks coming faster than baseline. Disabled on L2 rollups (rhythm too tight to be informative). |
Active chains as of 2026-04-29: ETH, POL on L1 (event-based + statistical lows); ARB, BASE, OP on L2 (multi-dim demand statistical). SOL and AVAX remain on the legacy 4-state grid until July 2026 calibration. Until per-chain activation, the API emits the four-state grid as today, additive and non-breaking.
"OK" → all panel items within their freshness window · "DEGRADED" → at least one L1/L2/bridge item is STALE or UNAVAILABLE. UNCALIBRATED items do not trigger DEGRADED.Per-item freshness: each entry in
panel.l1[] · panel.l2[] · panel.bridges[] carries its own status and timestamp (computed_at for L1/L2, 1h window; observed_at for bridges, 10m window). Always inspect the specific items you consume, not only oracle_status.
Two corpora were tested in 2025: ETH-ARB-CCTP and ETH-OP-CCTP. A 648-configuration grid with Benjamini-Hochberg FDR correction on each corpus produces validated precursor configurations on each chain (six on ARB with lift 1.53 to 2.36x, one on OP with lift 3.72x). Each set is disjoint from the other, and configurations transferred from one corpus to the other do not hold (ARB survivors fail on OP; the OP survivor fails on ARB). The empirical reading is that Delta calibration is chain-type-exclusive: each chain warrants its own discovery and validation. The v3 redesign replaces the composite Delta block with an explicit precursors[] array scoped per chain, with calibration metadata and cross-chain status carried in the payload. See the full analysis in the research note: Delta calibration is chain-type-exclusive: ETH-ARB-CCTP and ETH-OP-CCTP, 2025. The v2.0 composite remains exposed for now without a validated orientation claim; per-metric shift, shift_delta, shift_magnitude_delta remain raw inputs the agent can use freely.
The Regime classification answers what is the substrate doing now. The Delta answers is the deviation amplifying or reverting. Each classifying observable is exposed as a self-contained MetricBlock with the current ratio, the long-term reference, the deviation magnitude, and two trend signals.
{
"ratio": 1.094, // short EMA (~10h) vs short baseline
"ratio_long": 0.991, // long EMA (~30d) vs long baseline
"shift": 0.103, // = ratio - ratio_long, deviation magnitude (signed)
"shift_delta": 0.016, // = shift_now - shift_prev, raw direction of value movement
"shift_magnitude_delta": 0.016 // = |shift_now| - |shift_prev|, deviation growing or shrinking
}
shift_delta tells you the direction the value moved between cycles (positive = up, negative = down).shift_magnitude_delta tells you whether the deviation is amplifying or shrinking (positive = regime persists or worsens, negative = reverting toward nominal).The two values typically agree, except when shift crosses zero between cycles (regime transition through D1/S1 nominal).
Reading rules
| Situation | shift |
shift_magnitude_delta |
Lecture agent |
|---|---|---|---|
| Demand surge accelerating | +0.15 |
+0.07 |
D2+ persists or worsens, prepare for sustained gas competition |
| Demand surge plateau | +0.15 |
≈ 0 |
D2+ stable, regime sticky |
| Demand surge reverting | +0.15 |
−0.07 |
D2+ ending, return to D1 within hours likely |
| Starvation worsening | −0.10 |
+0.05 |
D2- deepens, depressed demand persists |
| Starvation recovering | −0.10 |
−0.05 |
D2- ending, return to D1 likely |
Each chain entry exposes a composite drift summary computed server-side. The block below is what the API returns today on /v2/panel. The empirical reading below the table is an initial heuristic that did not pass independent validation on the 2025 corpora (see the research note). The v3 design that replaces this block with calibrated precursors per chain is documented in the next subsection.
"drift": {
"structural": 0.0017, // max |shift| over structural observables
"structural_delta": 0.0008,
"structural_magnitude_delta": 0.0008,
"demand": 0.103, // max |shift| over demand observables
"demand_delta": 0.016,
"demand_magnitude_delta": 0.016
}
Magnitude drift.X | Initial heuristic reading (v2.0, unvalidated) |
|---|---|
< 0.005 | Substrate stable on this axis, regime expected to persist |
0.005 - 0.02 | Mild drift, transition within hours possible |
0.02 - 0.05 | Active drift, vigilance recommended |
> 0.05 | Transition probable on a multi-hour horizon |
"shift_available": false and the trend fields are absent. Once stabilized, the flag flips to true automatically without API version change.The thresholds above are initial heuristics, not validated as orientation signal. Independent testing across 648 configurations with Benjamini-Hochberg FDR correction on two corpora (ETH-ARB-CCTP and ETH-OP-CCTP, 2025) showed that the composite drift block does not carry a validated agent-orientation signal in this aggregation. The validated configurations are exposed as per-chain precursors in v3.
In v3 the composite drift block is replaced by a precursors[] array on each L1 and L2 entry. Each element is a single calibrated configuration that survived a 648-configuration Benjamini-Hochberg FDR exploration on its discovery corpus. The element carries everything the agent needs to act: the boolean fire flag, the current value of the signal, the calibration metadata (axis, threshold, lead, outcome, lift, precision), and the cross-chain test status. There is no composite Delta score and no aggregation across chains: an agent reads the precursors that belong to the chain it is acting on, applies its own decision policy, and routes accordingly.
"l2": [
{
"chain": "arbitrum",
"regime": "S1D1",
"structural": { ... },
"demand": { ... },
"precursors": [
{
"axis": "arb_struct_seq_publish_latency_shift",
"fires": false,
"current_smd": 0.080,
"smd_threshold_value": 0.2258,
"k_consecutive_hours": 2,
"pctl_threshold": 0.90,
"lead_hours": 6,
"outcome_category": "latency_high_only",
"bridge_corridor": "ETH-ARB-CCTP",
"baseline_lift": 1.913,
"baseline_p_adj": 0.023,
"baseline_precision": 0.439,
"baseline_alert_rate": 0.0057,
"cross_chain_status": "FAIL_on_optimism",
"cross_chain_lift": 0.000,
"cross_chain_placebo_p": 1.000,
"calibrated_at": "2026-05-19T00:00:00Z"
},
// 5 other precursors on arbitrum, one per calibrated configuration
]
}
]
The user does not configure thresholds. The calibration values (smd_threshold_value, k_consecutive_hours, pctl_threshold, lead_hours) are derived by Invarians from the discovery corpus and seeded server-side. The agent only chooses its own decision policy on top of fires and the metadata.
| Field | Meaning · how the agent uses it |
|---|---|
fires | Boolean. true when the current single-hour shift_magnitude_delta on axis meets or exceeds smd_threshold_value. To confirm a full K-consecutive condition the agent reads two successive panel responses and treats the configuration as fully engaged when both report fires=true. null means the upstream signal is unavailable this cycle. |
current_smd | The signal value the precursor reads at this cycle. Useful for the agent to compute its own K-consecutive logic or to log proximity to threshold. |
lead_hours | How far ahead of the outcome this configuration predicts. An agent acting now reads this as the horizon over which it should defer or route differently. |
outcome_category | What this precursor predicts: latency_high_only, bs2_only, bridge_stress_full, or a directional bridge_<src>_to_<dst>. |
bridge_corridor | Corridor on which the outcome was evaluated. If the agent is routing on a corridor not covered by any precursor, the precursors[] array carries no actionable entry for that corridor. |
baseline_lift | Lift achieved on the calibration corpus (precision divided by base rate). 1.5x means the precursor's precision is 50% above the unconditional outcome rate. Use this as the agent's gating threshold. |
baseline_precision | Precision on the calibration corpus. When fires=true, expect the outcome at this rate over the next lead_hours. |
baseline_alert_rate | Fraction of hours the precursor fired on the calibration corpus. Use this to size the expected alert volume. |
cross_chain_status | NOT_TESTED, PASS_on_<chain>, or FAIL_on_<chain>. Carries the result of testing this exact configuration on another chain's corpus. FAIL_on_X means the configuration is valid on its own chain but did not generalize to X. |
The same payload supports several decision policies. The choice is the agent's, not Invarians'. Below are three reference policies in increasing order of permissiveness.
# Suitable for RWA settlement with low alert tolerance.
# Defer only on precursors whose calibration also held on another chain.
# With the 2025 ETH-ARB-CCTP and ETH-OP-CCTP corpora, no precursor
# currently passes this filter (all carry FAIL_on_X). Policy A is
# aspirational: it documents the bar a future precursor must clear
# to be acted on in this mode.
for p in panel.l2_by_chain("arbitrum").precursors:
if p.is_firing and p.baseline_lift >= 2.0 and p.cross_chain_held:
agent.defer(
reason=f"strict precursor {p.axis} fired",
horizon_hours=p.lead_hours,
)
# Defer when any precursor on the relevant chain fires with lift >= 1.5.
# No cross-chain gating: the configuration is valid on the chain
# the agent is acting on, which is what matters operationally.
# This is the recommended default for most agent designs in 2025.
for p in panel.l2_by_chain("arbitrum").precursors:
if p.is_firing and p.baseline_lift >= 1.5:
agent.defer(
reason=f"{p.axis} expects {p.outcome_category} in {p.lead_hours}h",
expected_precision=p.baseline_precision,
corridor_scope=p.bridge_corridor,
)
# Flag any precursor with lift above unconditional baseline (lift > 1).
# Higher alert volume, lower precision per alert.
# Suitable for monitoring or human-in-the-loop scenarios where false
# positives are cheap and missed signals are expensive.
for p in panel.l2_by_chain("arbitrum").precursors:
if p.is_firing and p.baseline_lift > 1.0:
agent.flag(p) # surface to operator, do not auto-defer
The payload exposes a single-hour fire check. If the calibration says k_consecutive_hours: 2, an agent that wants the full K-consecutive condition reads two consecutive panel responses (or polls at 1h intervals) and treats the configuration as fully engaged only when both reports return fires=true. The agent can implement this with a small state buffer:
from collections import deque
fire_history = deque(maxlen=2) # for K=2; widen for larger K
def on_panel_response(panel):
p = next(
(p for p in panel.l2_by_chain("arbitrum").precursors
if p.axis == "arb_struct_seq_publish_latency_shift"
and p.lead_hours == 6),
None,
)
if p is None:
return
fire_history.append(p.is_firing)
if len(fire_history) == p.k_consecutive_hours and all(fire_history):
agent.defer(reason="K-consecutive condition met", horizon_hours=p.lead_hours)
v3 removes the drift.* composite block from each entry. Per-metric shift, shift_delta, shift_magnitude_delta remain exposed on every MetricBlock as raw inputs. v3 is announced one release cycle in advance with the v2 drift block flagged deprecated_unvalidated: true on each entry root. Integrations relying on drift.demand_magnitude_delta > threshold migrate to iterating on precursors[] with one of the three policies above.
| Field | Description | Notes |
|---|---|---|
| panel.l1[].regime · panel.l2[].regime | Current certified regime per chain | 4 base codes (S1D1 / S1D2 / S2D2 / S2D1) plus 11 signed codes (S1D2+, S1D2-, S1D2±, S2+D1, S2-D1, S2+D2±, S2-D2±, etc.) on chains with calibrated lower bounds since 2026-04-29. Parse with a regex on the structure prefix (S1, S2+, S2-) and demand prefix (D1, D2+, D2-, D2±). |
| panel.bridges[].state | Current certified state per bridge | Unified BS1 / BS2 across all variable-latency bridges (CCIP, CCTP, future fast bridges). The type field on the bridge entry distinguishes the protocol. CCTP routes carry preliminary BS1/BS2 since 2026-05-04. CCIP lanes remain state:null with calibrated:false until sustained throughput emerges. |
| oracle_status | Global panel status | OK / DEGRADED |
| panel.{l1,l2,bridges}[].status | Per-item freshness/availability | OK / STALE / UNAVAILABLE / UNCALIBRATED |
| panel.l1[].computed_at · panel.l2[].computed_at | Timestamp of last L1/L2 computation (~1h window) | ISO 8601 UTC |
| panel.bridges[].observed_at | Timestamp of last bridge observation (~10m window) | ISO 8601 UTC |
| panel.l1[].structural · panel.l2[].structural | Axis object grouping the structural observables (rhythm, continuity, plus beacon_participation on ETH or sequencer_publish_latency on L2) |
Each child is a MetricBlock |
| panel.l1[].demand · panel.l2[].demand | Axis object grouping the demand observables (sigma, size, tx, plus complexity and gas_complexity on L2) |
Each child is a MetricBlock |
| {axis}.{observable}.ratio | Short-term EMA ratio. Calibrated index, ≈1.0 nominal | core mode and above |
| {axis}.{observable}.ratio_long | Long-term EMA ratio (~30-day baseline) | diagnostic mode and above |
| {axis}.{observable}.shift | ratio - ratio_long. Current deviation magnitude, signed |
diagnostic mode and above |
| {axis}.{observable}.shift_delta | shift_now - shift_prev. Raw direction of value movement between cycles |
diagnostic mode and above |
| {axis}.{observable}.shift_magnitude_delta | |shift_now| - |shift_prev|. Whether the deviation is growing or shrinking |
diagnostic mode and above |
| {axis}.{observable}.shift_available | false during the pre-shift period when the long EMA is not yet stabilized for a newly added observable |
diagnostic mode and above |
| {axis}.{observable}.baseline_short · baseline_long | Raw EMA values | full mode only |
| structural.beacon_participation.epoch | Current Beacon Chain epoch | Ethereum only, all modes |
| structural.sequencer_publish_latency.seconds | Raw observed sequencer publish latency in seconds | L2 only, all modes |
| panel.l1[].drift · panel.l2[].drift | v2.0 composite drift per axis: structural, structural_delta, structural_magnitude_delta, demand, demand_delta, demand_magnitude_delta. Unvalidated as orientation signal in 2025 corpora. |
core mode and above. Removed in v3, replaced by precursors[]. |
| panel.l1[].precursors · panel.l2[].precursors v3 PREVIEW | Array of calibrated precursor configurations scoped to this chain. Each element carries axis, fires, current_smd, k_consecutive_hours, pctl_threshold, smd_threshold_value, lead_hours, outcome_category, bridge_corridor, baseline_lift, baseline_p_adj, baseline_precision, baseline_alert_rate, cross_chain_status, cross_chain_lift, cross_chain_placebo_p, calibrated_at. |
v3 design, deployment pending. See the Consuming precursors subsection. |
| signed_execution_context | HMAC envelope: payload_hash, signature, key_id, anchor |
Same on every panel response |
| version | Panel API version, e.g. "2.0.0" |
Negotiable via Accept-Version header |
| issued_at | When this panel snapshot was assembled | ISO 8601 UTC |
# Python, single-chain evaluation from the panel
def evaluate_chain(panel: dict, chain: str) -> str:
# First: global panel freshness
if panel["oracle_status"] != "OK":
return "PANEL_DEGRADED"
l1 = next((e for e in panel["panel"]["l1"] if e["chain"] == chain), None)
l2 = next((e for e in panel["panel"]["l2"] if e["chain"] == chain), None)
entry = l1 or l2
if entry is None or entry["status"] != "OK":
return "CHAIN_UNAVAILABLE"
return entry["regime"] # "S1D1" | "S1D2" | "S2D2" | "S2D1" | signed code (S1D2+, S1D2-, S1D2±, S2+D1, S2-D2±, ...)
import re
SIGNED_PATTERN = re.compile(r"^(S1|S2[+-])(D1|D2[+\-±])$")
def parse_regime(code):
# Returns (structure_axis, demand_axis) where each is the prefix-stripped direction.
# Legacy 4-state codes are mapped to their topological equivalent.
legacy = {"S1D1": ("S1", "D1"),
"S1D2": ("S1", "D2+"),
"S2D1": ("S2+", "D1"),
"S2D2": ("S2+", "D2+")}
if code in legacy: return legacy[code]
m = SIGNED_PATTERN.match(code)
return (m.group(1), m.group(2)) if m else (None, None)
# Your agent decides, Invarians only reads the substrate
panel = client.get_panel_v2(chains=["ethereum"], include="core")
state_code = evaluate_chain(panel, "ethereum")
struct, demand = parse_regime(state_code)
if struct == "S1" and demand == "D1":
handle_nominal()
elif demand == "D2+":
handle_demand_high() # elevated demand, organic surge or coordinated buying
elif demand == "D2-":
handle_demand_starvation() # depressed demand, sequencer slowing or censorship of small transfers
elif demand == "D2±":
handle_composition_split() # size/tx asymmetry, agentic concentration signature
elif struct == "S2+":
handle_structural_slowdown() # rhythm degraded, sequencer or validator-side
elif struct == "S2-":
handle_structural_acceleration() # rhythm faster than baseline, infra change or client optimisation
else:
fallback_behavior() # panel degraded, chain unavailable, or regime not yet calibrated
// JavaScript, multi-chain check from a single panel fetch
const checkAllChains = async () => {
const panel = await getPanel();
const all = [...panel.panel.l1, ...panel.panel.l2];
return all.reduce((acc, entry) => {
acc[entry.chain] = entry.status === "OK"
? { regime: entry.regime, status: entry.status }
: { regime: null, status: entry.status }; // STALE | UNAVAILABLE | UNCALIBRATED
return acc;
}, {});
};
# Python, cross-chain agent pattern, panel API
panel = get_panel()
if panel["oracle_status"] == "DEGRADED":
fallback()
l1 = next(e for e in panel["panel"]["l1"] if e["chain"] == "ethereum")
br = next(b for b in panel["panel"]["bridges"] if b["id"] == "arbitrum-ethereum/cctp")
# Your agent applies its own policy
if l1["regime"].startswith("S1") and br["state"] == "BS1":
proceed()
elif br["status"] == "UNCALIBRATED":
# Bridge classification deferred (CCIP), raw signal available in br["last_sequence_advance_seconds"]
proceed_with_observation_only()
else:
defer_execution()
Invarians produces three distinct primitives per query.
Primitive 1, Attestation: every panel response carries a HMAC-SHA256 envelope (payload_hash + signature + key_id + anchor) covering the full payload. Independently verifiable via POST /v2/verify. This is what makes the certified execution state provable, not just observable.
The execution policy is not prescribed. The agent reads the certified state and applies its own thresholds, risk tolerance, and fallback logic. Invarians certifies the infrastructure state, the decision belongs to the agent.
{
"action": "execution_deferred",
"reason": "L1 structural stress detected, ethereum regime S2D1",
"invarians_proof": {
// Excerpt of the panel entries relevant to the decision
"l1": { "chain": "ethereum", "regime": "S2D1", "status": "OK" },
"l2": { "chain": "arbitrum", "regime": "S1D1", "status": "OK" },
"bridge": { "id": "arbitrum-ethereum/cctp", "state": "BS1", "calibrated": true },
"signed_execution_context": {
"payload_hash": "0x714b64...",
"signature": "hmac-sha256:9b5b1d...",
"key_id": "invarians-v1",
"anchor": null
},
"issued_at": "2026-04-20T10:00:00Z"
}
}
Invarians tracks blockchain behavior under AI agent load and defines safe execution windows. Current monitoring scope:
| Layer | Coverage |
|---|---|
| L1 | Ethereum, Solana, Polygon, Avalanche |
| L2 (Ethereum-anchored) | Arbitrum, Base, Optimism |
| Chainlink CCIP lanes | Bidirectional, L1 ↔ L1 and L1 ↔ L2 |
| Circle CCTP routes | Bidirectional, L1 ↔ L1 and L1 ↔ L2 |
Bridge classification scope is variable-latency surfaces only (Chainlink CCIP, Circle CCTP). Institutional RWA settlement moves stablecoins via CCTP and tokens via CCIP, both bidirectional.
| Chain | Structural axis | Demand axis | 3rd structural observable | Calibration |
|---|---|---|---|---|
| Ethereum | rhythm + continuity | sigma (gas) + size + tx | beacon_participation (epoch + ratio live, threshold pending) | 12 signed codes (S2- on beacon pending) |
| Polygon | rhythm + continuity | sigma (gas) + size + tx | — | 12 signed codes since 2026-04-29 |
| Avalanche | rhythm + continuity | sigma (gas) + size + tx | — | 4 base codes (12 signed scheduled July 2026) |
| Solana | rhythm + continuity | size + tx (user only) | — | UNCALIBRATED (calibration scheduled July 2026) |
| L2 ROLLUPS, 3rd structural axis = sequencer_publish_latency (batch gap on L1) | ||||
| Arbitrum | rhythm + continuity | sigma + size + tx + complexity + gas_complexity | sequencer_publish_latency ✓ calibrated (600 s halt threshold) | 12 signed codes since 2026-05-01 |
| Base | rhythm + continuity | sigma + size + tx + complexity + gas_complexity | sequencer_publish_latency ✓ calibrated (480 s halt threshold) | 12 signed codes since 2026-05-01 |
| Optimism | rhythm + continuity | sigma + size + tx + complexity + gas_complexity | sequencer_publish_latency ✓ calibrated (1800 s halt threshold) | 12 signed codes since 2026-05-01 |
Delta (Primitive 3) shift per metric is computed against the long-term EMA (~30-day baseline). For newly added structural observables (beacon_participation on ETH, sequencer_publish_latency on L2), shift_available: false until the slow EMA stabilizes (~30 days post-launch). The regime trigger uses raw ratio or seconds against calibrated bounds during this period.
401 Unauthorized , invalid or missing API key
403 Forbidden , key does not have access to this chain
404 Not Found , no signal available for this chain
429 Too Many Requests, rate limit exceeded
500 Server Error , internal error, retry after 60s
"hmac-sha256:...")
under signed_execution_context.signature. The signature covers a canonical JSON
serialization of the entire payload with signed_execution_context stripped.
Any agent can independently verify that a panel was issued by Invarians and has not been
altered in transit.
Use the /verify endpoint, no additional secret required, only your API key.
# POST /v2/verify
# Returns: { "valid": true|false, "key_id": "invarians-v1", "version": "2.0.0" }
import requests
# 1. Fetch a panel
r = requests.get(
"https://api.invarians.com/v2/panel",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
panel = r.json()
sig = panel["signed_execution_context"]["signature"]
# 2. Verify the signature independently
# Send the panel with signed_execution_context stripped + the signature.
payload = {k: v for k, v in panel.items() if k != "signed_execution_context"}
verify = requests.post(
"https://api.invarians.com/v2/verify",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"payload": payload, "signature": sig}
)
print(verify.json())
# → { "valid": true, "key_id": "invarians-v1", "version": "2.0.0" }
"valid": true guaranteesThe panel was produced by Invarians infrastructure and has not been modified in transit. It does not guarantee the freshness of the underlying chains. Always check per-item
status ("OK" / "STALE" / "UNAVAILABLE") and the global
oracle_status ("OK" / "DEGRADED") before acting on a signal.Key rotation & roadmap
key_id identifies the current HMAC key (invarians-v1). V1.1+ populates
signed_execution_context.anchor with an on-chain Merkle anchor on Arbitrum,
adding a second independent verification path.
status and a per-item timestamp
(computed_at for L1/L2, observed_at for bridges). A global
oracle_status summarizes the panel. A signal is marked STALE
when its freshest observation is older than the item's window.Important: STALE does not mean the regime has changed, it means the current regime is unknown. Structural regimes (L1/L2) change slowly (~3h). A signal that became STALE 30 minutes ago is likely still valid, but carries uncertainty.
| Tier | Panel field | Window | STALE threshold | Why different |
|---|---|---|---|---|
| Tier 1, L1 / L2 | panel.l1[].status · panel.l2[].status |
window: "1h" |
now - computed_at > 3600s |
Regimes are integrals, change slowly (~3h) |
| Tier 2, Bridge | panel.bridges[].status |
window: "10m" |
now - observed_at > 900s |
Bridge is a liveness snapshot, changes instantly |
| Item age | status |
Confidence | Recommended behavior |
|---|---|---|---|
| L1/L2 < 3600s · Bridge < 900s | OK (or UNCALIBRATED for bridges) |
Full | Execute as per regime/state, signal is fresh |
| L1/L2 3600s – 7200s | STALE |
Degraded | Use last known regime with caution. Regime likely unchanged (structural change takes ~3h). Log the staleness. Avoid initiating new high-value positions. |
| L1/L2 > 7200s · Bridge > 900s | STALE or UNAVAILABLE |
Unknown | State unknown. For time-sensitive or low-value operations, proceed at agent's discretion with explicit staleness flag in the decision log. |
status is STALE or UNAVAILABLE, treat cross-chain
execution as unknown infrastructure state and defer.When
status: "UNCALIBRATED", state is null but the per-message
signal is exposed for observation (metrics.execute_latency_p90_s, message counts, etc.).
This applies when sustained throughput has not yet allowed P97 baseline calibration on that surface.
# Complete STALE handling, Python, panel API
from datetime import datetime, timezone
import requests
r = requests.get(
"https://api.invarians.com/v2/panel",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
panel = r.json()
def age_seconds(iso: str) -> float:
return (datetime.now(timezone.utc) - datetime.fromisoformat(iso.replace("Z", "+00:00"))).total_seconds()
l1 = next(e for e in panel["panel"]["l1"] if e["chain"] == "ethereum")
br = next(b for b in panel["panel"]["bridges"] if b["id"] == "arbitrum-ethereum/cctp")
l1_age = age_seconds(l1["computed_at"])
bridge_age = age_seconds(br["observed_at"]) if br["observed_at"] else 9999
if l1["status"] == "OK" and br["status"] in ("OK", "UNCALIBRATED"):
# Fresh. Apply your own policy.
if l1["regime"].startswith("S1") and br["state"] == "BS1":
proceed()
elif br["status"] == "UNCALIBRATED":
proceed_with_observation_only()
else:
defer_execution()
elif l1["status"] == "STALE" and l1_age <= 7200:
log_warning(f"L1 stale ({int(l1_age)}s), regime likely unchanged: {l1['regime']}")
else:
log_warning(f"Infrastructure state unknown, deferring, global: {panel['oracle_status']}")
# Always log the full item state in the decision record
decision_log = {
"oracle_status": panel["oracle_status"],
"l1": {"chain": l1["chain"], "regime": l1["regime"], "status": l1["status"], "computed_at": l1["computed_at"]},
"bridge": {"id": br["id"], "state": br["state"], "calibrated": br["calibrated"], "status": br["status"]},
"issued_at": panel["issued_at"],
"payload_hash": panel["signed_execution_context"]["payload_hash"],
}
/attestation/panel, /attestation/verify) remain live for a 60-day grace period and will sunset on 2026-06-30. Migrate before that date.
| v1.1.0 | v2.0 | Status |
|---|---|---|
GET /attestation/panel |
GET /v2/panel |
Live until 2026-06-30, then 410 Gone |
POST /attestation/verify |
POST /v2/verify |
Live until 2026-06-30, then 410 Gone |
| v1.1.0 (flat) | v2.0 (axis-grouped) | Reason |
|---|---|---|
panel.l1[].rhythm_ratio |
panel.l1[].structural.rhythm.ratio |
Axis-grouped layout (structural / demand) |
panel.l1[].sigma_ratio |
panel.l1[].demand.sigma.ratio |
Axis-grouped layout |
panel.l1[].structural_slow.rhythm_ratio_slow |
panel.l1[].structural.rhythm.ratio_long |
Naming consistency (long > slow) |
panel.l1[].shifts.rhythm_shift |
panel.l1[].structural.rhythm.shift |
Co-located with the metric it derives from |
| (no demand shifts exposed) | panel.l1[].demand.{sigma,size,tx}.shift |
Symmetric exposure on all classifying observables |
| (no trend signal) | shift_delta + shift_magnitude_delta per metric |
Primitive 3, drift trend (raw + magnitude) |
| (no L2 sequencer obs in regime) | panel.l2[].structural.sequencer_publish_latency |
3rd structural axis on L2 (sequencer halt detection) |
| (no Ethereum beacon in regime) | panel.l1[].structural.beacon_participation (ETH only) |
Beacon Chain participation enters S axis on Ethereum |
panel.l1[].execution_profile.index_a/b/c |
Removed, replaced by demand.{sigma,size,tx}.ratio |
Named fields are clearer than alphabetical indices |
(no drift composite) |
panel.l1[].drift.{structural,demand} + delta + magnitude_delta |
Primitive 3 summary per axis |
# v1.1.0 client (deprecated)
from invarians import InvariansClient
client = InvariansClient(api_key="inv_...")
panel = client.get_panel()
eth = panel.l1_by_chain("ethereum")
print(eth.regime, eth.shifts.rhythm_shift)
# v2.0 client (recommended)
panel = client.get_panel_v2(
chains=["ethereum"],
bridges=["cctp"],
include="diagnostic",
)
eth = panel.l1_by_chain("ethereum")
arb = panel.l2_by_chain("arbitrum")
print(eth.regime, eth.structural.rhythm.shift)
for p in arb.precursors: # v3 per-chain calibrated precursors
print(p.axis, p.is_firing, p.baseline_lift)
print(eth.demand.tx.is_drifting_away) # trend helper on raw shift
get_panel() still works against the v1.1.0 endpoint until sunset. After 2026-06-30, only get_panel_v2() will resolve.
- Upgrade SDK :
pip install --upgrade "invarians>=0.5.0" - Replace
client.get_panel(...)withclient.get_panel_v2(chains=..., bridges=..., include=...) - Update field accessors :
eth.structural.rhythm_ratio→eth.structural.rhythm.ratio,eth.shifts.rhythm_shift→eth.structural.rhythm.shift - If your agent uses
execution_profile.index_a/b/c, switch todemand.sigma.ratio/demand.size.ratio/demand.tx.ratio - If your agent uses long-term EMA:
structural_slow.rhythm_ratio_slow→structural.rhythm.ratio_long - Always specify
chains=andbridges=filters (see Filters & Best Practices) — default behavior is to return all entries - Replace
verify_panel()withverify_panel_v2() - Optional: consume the new
driftcomposite andshift_magnitude_deltaper-metric trends to make fitness-for-action decisions (see Primitive 3)
v2.0.0 (panel API), exposed as "version": "2.0.0" on every panel response. v1.1.0 endpoints (/attestation/panel, /attestation/verify) remain live until 2026-06-30 then return 410 Gone.Breaking changes are announced a minimum of 30 days in advance via a
Deprecation header in API responses and an entry in the changelog below.Non-breaking changes (new optional fields, new chain coverage, new bridge types) are additive and require no notice.
| Change type | Classification | Example |
|---|---|---|
| Field renamed or removed | Breaking | structural.rhythm_ratio (flat) → structural.rhythm.ratio (axis-grouped, v1.1.0 → v2.0) |
| Field semantics changed | Breaking | shift redefined relative to a different baseline |
| Endpoint URL changed | Breaking | /attestation/panel → /v2/panel (v1.1.0 → v2.0) |
| New optional field added | Non-breaking | structural.beacon_participation added for ETH only |
| New chain added | Non-breaking | New L2 endpoint deployed alongside existing ones |
Deprecation: date="2026-06-01"Sunset: date="2026-06-01"These headers appear at least 30 days before the change takes effect. Parse them in your integration to detect upcoming breaking changes programmatically.
Security-critical fixes may be applied immediately, in that case the header is present with the shortest possible notice and the changelog entry is dated retroactively.
| Version | Date | Changes |
|---|---|---|
| v2.0.0 | April 30, 2026 |
Three Primitives, axis-grouped payload (breaking) ·
Payload restructured by axis (structural · demand) per chain, with a MetricBlock per observable carrying ratio, ratio_long, shift, shift_delta, shift_magnitude_delta ·
beacon_participation promoted to a 3rd structural classifying observable on Ethereum (S2- on low-side participation drops) ·
sequencer_publish_latency promoted to a 3rd structural classifying observable on all L2s (S2+ on sequencer halts) ·
Demand axis gains shifts on sigma, size, tx (and complexity · gas_complexity on L2) ·
New composite drift object per axis with magnitude + raw delta + magnitude delta ·
Tiered exposure via ?include=core|diagnostic|full ·
Endpoints: GET /v2/panel, POST /v2/verify. v1.1.0 endpoints remain live until 2026-06-30 then return 410 Gone ·
Python SDK bumped to invarians>=0.5.0 with get_panel_v2(), verify_panel_v2(), MetricBlock dataclass and trend helpers
|
| v1.1.0 | April 29, 2026 |
Long-term EMA + shifts (additive, non-breaking) ·
Per-entry structural_slow object exposing rhythm_ratio_slow and continuity_ratio_slow (~30-day baseline) on L1 and L2 entries ·
Per-entry shifts object exposing rhythm_shift and continuity_shift (delta between short-term ~10h and long-term ~30d EMAs) ·
Calibration thresholds extracted from server-side code into dedicated Postgres tables (l1_thresholds, l2_thresholds) for transparency ·
Old v1.0.0 clients keep working unchanged ·
Python SDK bumped to invarians>=0.3.0 with StructuralSlow and Shifts dataclasses
|
| v1.0.0 | April 2026 |
Panel API (breaking) ·
Directional /attestation/execution-context?from=&to=, single-chain /attestation/{chain}, and L2 /attestation/l2/{chain} endpoints return 410 Gone ·
Replaced by GET /attestation/panel returning the full L1/L2/bridge panel in one signed payload ·
Direction is composed client-side by the agent (from / to no longer accepted) ·
proof_of_execution_context renamed to signed_execution_context (payload_hash + signature + key_id + anchor: null) ·
Per-item status (OK / STALE / UNAVAILABLE / UNCALIBRATED) and per-item timestamps (computed_at for L1/L2, observed_at for bridges) ·
Global oracle_status (OK / DEGRADED) ·
Canonical bridge IDs {chainA}-{chainB}/{type} ·
coverage block with per-type counts and methodology_url ·
Cache-Control: public, max-age=60 and ETag ·
Python SDK bumped to invarians>=0.2.0 with get_panel(), verify_panel(), and helpers
|
| v2 | March 2026 |
demand field replaced by execution_profile with named indices index_a–index_h ·
L2 endpoints added (/attestation/l2/{chain}) ·
calibration.version field introduced ·
divergence_index (F3) added
|
| v1 | January 2026 |
Initial release, structural, state, proof_of_execution_context · execution_window (removed in v2)
|
The instrument improves over time through closed-loop feedback: when agents log their decisions and outcomes (transaction success/failure, gas usage, timing), this data helps calibrate classification thresholds against real-world behavior.
During the beta period, feedback integration is available to select partners. If you are deploying an agent in production on any of the 7 supported chains and want to contribute, contact us to join the feedback program.