Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ Adrian supports entirely offline, data sovereign deployments using just a handfu

Use the same `adrian.init` snippet as in the [Quickstart](#quickstart) above. The SDK defaults to `ws://localhost:8080/ws`, so a self-hosted setup needs nothing more than the API key - drop the `ws_url=` line.

### Classifier error policy

Adrian records classifier outages, malformed classifier responses, and unparseable classifier output as `verdict_status=error` with `mad_code=""`. These are operational classifier errors, not benign `M0` findings and not synthetic malicious activity.

The default policy remains availability-first: classifier errors fail open. In **Settings -> Policy**, enable **Fail closed on classifier error** to make BLOCK-mode tool calls return blocked responses when the classifier cannot produce a verdict. In HITL mode, actionable classifier errors are sent to the review queue and held until an operator approves or rejects them.

Fail-closed classifier-error enforcement requires the Python SDK version shipped with this repository update. Older SDKs ignore the additive protobuf `status` and policy fields, see an empty MAD code, and continue fail-open even when the dashboard toggle is enabled.

To [reset the admin password](https://docs.adrian.secureagentics.ai/reference/backend#reset-the-admin-password), [change the model](https://docs.adrian.secureagentics.ai/reference/backend#switch-the-local-gguf) and much more check out the dedicated [Docs site](https://docs.adrian.secureagentics.ai/).

## Why Adrian is different
Expand Down
16 changes: 11 additions & 5 deletions backend/internal/api/handlers_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,18 @@ func (s *Server) handleListEvents(w http.ResponseWriter, r *http.Request) {
since = t
}
}
if status := q.Get("verdict_status"); status != "" && !validVerdictStatus(status) {
writeError(w, http.StatusBadRequest, "invalid verdict_status")
return
}

filters := store.EventFilters{
Since: since,
AgentID: q.Get("agent_id"),
SessionID: q.Get("session_id"),
EventType: q.Get("event_type"),
MinMAD: q.Get("min_mad"),
Since: since,
AgentID: q.Get("agent_id"),
SessionID: q.Get("session_id"),
EventType: q.Get("event_type"),
MinMAD: q.Get("min_mad"),
VerdictStatus: q.Get("verdict_status"),
}

rows, total, err := s.store.ListEvents(r.Context(), filters, pg.PerPage, pg.Offset)
Expand Down Expand Up @@ -203,6 +208,7 @@ func eventToListItemResponse(r *store.EventListRow) eventListItemResponse {
ID: r.VerdictID,
MADCode: r.MADCode,
Classification: r.Classification,
VerdictStatus: r.VerdictStatus,
}
}
return resp
Expand Down
25 changes: 22 additions & 3 deletions backend/internal/api/handlers_reviews.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type reviewDetail struct {
reviewSummary
EventPayload json.RawMessage `json:"event_payload,omitempty"`
Classification string `json:"classification,omitempty"`
Reasoning string `json:"reasoning,omitempty"`
}

type reviewResolveResponse struct {
Expand All @@ -49,8 +50,14 @@ type reviewResolveResponse struct {

func (s *Server) handleListReviews(w http.ResponseWriter, r *http.Request) {
pg := parsePagination(r)
status := r.URL.Query().Get("status")
rows, total, err := s.store.ListHitlQueue(r.Context(), status, pg.PerPage, pg.Offset)
q := r.URL.Query()
status := q.Get("status")
verdictStatus := q.Get("verdict_status")
if verdictStatus != "" && !validVerdictStatus(verdictStatus) {
writeError(w, http.StatusBadRequest, "invalid verdict_status")
return
}
rows, total, err := s.store.ListHitlQueue(r.Context(), status, verdictStatus, pg.PerPage, pg.Offset)
if err != nil {
writeError(w, http.StatusInternalServerError, "query failed")
return
Expand Down Expand Up @@ -81,6 +88,7 @@ func (s *Server) handleGetReview(w http.ResponseWriter, r *http.Request) {
resp := reviewDetail{
reviewSummary: reviewToSummary(&row.HitlReview),
Classification: row.Classification,
Reasoning: row.Reasoning,
}
if row.EventPayloadJSON != "" {
resp.EventPayload = json.RawMessage(row.EventPayloadJSON)
Expand Down Expand Up @@ -128,7 +136,7 @@ func (s *Server) resolveReview(w http.ResponseWriter, r *http.Request, status st
EventId: row.EventID,
SessionId: row.SessionID,
MadCode: row.MADCode,
Status: pb.VerdictStatus_VERDICT_STATUS_OK,
Status: reviewVerdictStatusProto(row.VerdictStatus),
Policy: s.policySnapshotProto(pol),
Hitl: &pb.HitlResponse{ContinueExecution: continueExec},
}},
Expand Down Expand Up @@ -165,3 +173,14 @@ func reviewToSummary(r *store.HitlReview) reviewSummary {
}
return out
}

func reviewVerdictStatusProto(status string) pb.VerdictStatus {
switch status {
case "error":
return pb.VerdictStatus_VERDICT_STATUS_ERROR
case "ok":
return pb.VerdictStatus_VERDICT_STATUS_OK
default:
return pb.VerdictStatus_VERDICT_STATUS_UNSPECIFIED
}
}
26 changes: 14 additions & 12 deletions backend/internal/api/handlers_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ package api
import "net/http"

type overviewResponse struct {
TotalEvents int `json:"total_events"`
FlaggedVerdicts int `json:"flagged_verdicts"`
PendingReviews int `json:"pending_reviews"`
ActiveAgents int `json:"active_agents"`
VerdictsByMAD map[string]int `json:"verdicts_by_mad"`
Window string `json:"window"`
TotalEvents int `json:"total_events"`
FlaggedVerdicts int `json:"flagged_verdicts"`
ClassifierErrors int `json:"classifier_errors"`
PendingReviews int `json:"pending_reviews"`
ActiveAgents int `json:"active_agents"`
VerdictsByMAD map[string]int `json:"verdicts_by_mad"`
Window string `json:"window"`
}

type activityBucketEntry struct {
Expand All @@ -31,12 +32,13 @@ func (s *Server) handleStatsOverview(w http.ResponseWriter, r *http.Request) {
return
}
writeJSON(w, http.StatusOK, overviewResponse{
TotalEvents: o.TotalEvents,
FlaggedVerdicts: o.FlaggedVerdicts,
PendingReviews: o.PendingReviews,
ActiveAgents: o.ActiveAgents,
VerdictsByMAD: o.VerdictsByMAD,
Window: "24h",
TotalEvents: o.TotalEvents,
FlaggedVerdicts: o.FlaggedVerdicts,
ClassifierErrors: o.ClassifierErrors,
PendingReviews: o.PendingReviews,
ActiveAgents: o.ActiveAgents,
VerdictsByMAD: o.VerdictsByMAD,
Window: "24h",
})
}

Expand Down
Loading
Loading