@@ -132,13 +132,17 @@ def _retention_days() -> int:
132132 served_by TEXT,
133133 ok BOOLEAN NOT NULL,
134134 latency_ms DOUBLE PRECISION,
135+ error_kind TEXT,
136+ http_status INTEGER,
135137 tools_requested BOOLEAN,
136138 tool_calls_emitted BOOLEAN
137139 )""" ,
138140 "CREATE INDEX IF NOT EXISTS idx_route_obs_ts ON route_observations(ts)" ,
139141 "CREATE INDEX IF NOT EXISTS idx_route_obs_route"
140142 " ON route_observations(provider_id, model_family, served_by, ts)" ,
141143 # #4c: learned tool capability is derived from these per-attempt signals.
144+ "ALTER TABLE route_observations ADD COLUMN IF NOT EXISTS error_kind TEXT" ,
145+ "ALTER TABLE route_observations ADD COLUMN IF NOT EXISTS http_status INTEGER" ,
142146 "ALTER TABLE route_observations ADD COLUMN IF NOT EXISTS tools_requested BOOLEAN" ,
143147 "ALTER TABLE route_observations ADD COLUMN IF NOT EXISTS tool_calls_emitted BOOLEAN" ,
144148 """CREATE TABLE IF NOT EXISTS settings_overrides (
@@ -224,11 +228,13 @@ def _retention_days() -> int:
224228 max_concurrency INTEGER,
225229 reputation DOUBLE PRECISION,
226230 last_seen BIGINT,
231+ last_reached_at BIGINT,
227232 observed_at BIGINT NOT NULL,
228233 first_seen BIGINT,
229234 fetched_at BIGINT,
230235 PRIMARY KEY (peer_id, service)
231236 )""" ,
237+ "ALTER TABLE peer_offers ADD COLUMN IF NOT EXISTS last_reached_at BIGINT" ,
232238 "CREATE INDEX IF NOT EXISTS idx_peer_offers_observed ON peer_offers(observed_at)" ,
233239 # The antseed buyer's status (escrow + session pin + wallet), one row per
234240 # buyer pid. WRITTEN by the antseed sidecar (write-status.js on the poll loop
@@ -901,12 +907,14 @@ def _insert_route_observation(row: dict[str, Any]) -> None:
901907 conn .execute (
902908 "INSERT INTO route_observations"
903909 " (ts, provider_id, model_family, served_by, ok, latency_ms,"
904- " tools_requested, tool_calls_emitted)"
905- " VALUES (%s,%s,%s,%s,%s,%s,%s,%s)" ,
910+ " error_kind, http_status, tools_requested, tool_calls_emitted)"
911+ " VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s )" ,
906912 (int (row .get ("ts" ) or time .time () * 1000 ),
907913 row .get ("provider_id" ), row .get ("model_family" ), row .get ("served_by" ),
908914 bool (row .get ("ok" )),
909915 float (row ["latency_ms" ]) if row .get ("latency_ms" ) is not None else None ,
916+ row .get ("error_kind" ),
917+ int (row ["http_status" ]) if row .get ("http_status" ) is not None else None ,
910918 bool (row .get ("tools_requested" )), bool (row .get ("tool_calls_emitted" ))))
911919 except Exception as exc : # noqa: BLE001 — the fold must never break a request
912920 _log .warning ("host_store route observation insert failed: %s" , exc )
@@ -942,6 +950,107 @@ def route_stats(window_ms: int = 900_000) -> dict[str, dict[str, Any]]:
942950 return {}
943951
944952
953+ # Failures in these classes describe the request rather than the route. They
954+ # remain in route_stats() for backwards-compatible measured reliability, but do
955+ # not put a marketplace seller into a durable cooldown.
956+ _ROUTE_HEALTH_NEUTRAL_ERRORS = frozenset ({
957+ "bad_request" , "content_filter" , "context_overflow" , "payment_required" ,
958+ })
959+
960+ # A failure in one service can prove the whole peer unhealthy only for transport,
961+ # capacity and server faults. A model_unavailable/404 is deliberately absent: it
962+ # quarantines that peer+family route without hiding the peer's other models.
963+ _PEER_HEALTH_FAILURE_ERRORS = frozenset ({
964+ "rate_limit" , "timeout" , "server_error" , "network_error" , "auth_error" ,
965+ "bad_response" , "unknown" ,
966+ })
967+
968+
969+ def _fold_health_rows (rows : list [tuple ], provider_id : str , * , peer : bool ) -> dict :
970+ """Fold newest-first observation rows into consecutive attributable failures.
971+
972+ The SQL caps each identity to a bounded recent sample. A success ends the
973+ current failure streak; client/request faults are ignored. Old observations
974+ have no error_kind, so they remain route evidence (the safe migration
975+ direction) but are not promoted to peer-wide blame.
976+ """
977+ out : dict [str , dict [str , Any ]] = {}
978+ for family , served_by , ts , ok , error_kind , http_status in rows :
979+ key = served_by if peer else f"{ provider_id } |{ family } |{ served_by } "
980+ state = out .setdefault (key , {
981+ "consecutive_failures" : 0 ,
982+ "last_failure_at" : None ,
983+ "last_success_at" : None ,
984+ "latest_error_kind" : None ,
985+ "latest_http_status" : None ,
986+ "sample_count" : 0 ,
987+ "_ended" : False ,
988+ })
989+ state ["sample_count" ] += 1
990+ if state ["_ended" ]:
991+ continue
992+ if ok :
993+ state ["last_success_at" ] = int (ts )
994+ state ["_ended" ] = True
995+ continue
996+ kind = str (error_kind ) if error_kind else None
997+ attributable = (
998+ kind in _PEER_HEALTH_FAILURE_ERRORS if peer
999+ else kind not in _ROUTE_HEALTH_NEUTRAL_ERRORS
1000+ )
1001+ if not attributable :
1002+ continue
1003+ state ["consecutive_failures" ] += 1
1004+ if state ["last_failure_at" ] is None :
1005+ state ["last_failure_at" ] = int (ts )
1006+ state ["latest_error_kind" ] = kind
1007+ state ["latest_http_status" ] = (
1008+ int (http_status ) if http_status is not None else None )
1009+ for key in list (out ):
1010+ state = out [key ]
1011+ state .pop ("_ended" , None )
1012+ # A group containing only neutral failures carries no health evidence.
1013+ if not state ["consecutive_failures" ] and state ["last_success_at" ] is None :
1014+ out .pop (key )
1015+ return out
1016+
1017+
1018+ def marketplace_route_health (provider_id : str , window_ms : int = 86_400_000 ,
1019+ sample_limit : int = 64 ) -> dict [str , dict ]:
1020+ """Bounded durable health for one marketplace provider.
1021+
1022+ Returns ``{"routes": {provider|family|peer: state}, "peers": {peer: state}}``.
1023+ Route state drives service-specific cooldowns; peer state is restricted to
1024+ failures that can safely be attributed across services. Both are derived
1025+ from the shared Postgres ledger, so replicas and restarts agree.
1026+ """
1027+ try :
1028+ cutoff = int (time .time () * 1000 ) - max (0 , window_ms )
1029+ limit = max (1 , min (int (sample_limit ), 512 ))
1030+
1031+ def read (partition : str , order_prefix : str ) -> list [tuple ]:
1032+ with _get_pool ().connection () as conn :
1033+ cur = conn .execute (
1034+ "SELECT model_family,served_by,ts,ok,error_kind,http_status"
1035+ " FROM (SELECT id,model_family,served_by,ts,ok,error_kind,http_status,"
1036+ f" row_number() OVER (PARTITION BY { partition } ORDER BY ts DESC,id DESC) AS rn"
1037+ " FROM route_observations WHERE provider_id=%s AND ts >= %s) recent"
1038+ " WHERE rn <= %s"
1039+ f" ORDER BY { order_prefix } ,ts DESC,id DESC" ,
1040+ (provider_id , cutoff , limit ))
1041+ return list (cur .fetchall ())
1042+
1043+ route_rows = read ("model_family,served_by" , "served_by,model_family" )
1044+ peer_rows = read ("served_by" , "served_by" )
1045+ return {
1046+ "routes" : _fold_health_rows (route_rows , provider_id , peer = False ),
1047+ "peers" : _fold_health_rows (peer_rows , provider_id , peer = True ),
1048+ }
1049+ except Exception as exc : # noqa: BLE001 — admission degrades to legacy behavior
1050+ _log .warning ("host_store marketplace_route_health failed: %s" , exc )
1051+ return {"routes" : {}, "peers" : {}}
1052+
1053+
9451054def provider_attempt_counts (provider_id : str , window_ms : int = 3_600_000 ) -> dict [str , int ]:
9461055 """{ok, failed, total} attempts for one provider over the last `window_ms`,
9471056 across every family and peer. The wallet keeper's "is this provider fully
@@ -1752,7 +1861,7 @@ def recent_logins(limit: int = 100) -> list[dict[str, Any]]:
17521861# window/housekeeping columns (observed_at/first_seen/fetched_at) stay internal.
17531862_PEER_OFFER_FIELDS = ("peer_id" , "service" , "price_in" , "price_out" ,
17541863 "price_cached_in" , "max_concurrency" , "reputation" ,
1755- "last_seen" )
1864+ "last_seen" , "last_reached_at" )
17561865
17571866
17581867def peer_offers (window_ms : int = 900_000 ) -> list [dict [str , Any ]]:
0 commit comments